tracing-error-0.2.0/.cargo_vcs_info.json0000644000000001120000000000100136030ustar { "git": { "sha1": "e65c78b8e07f693b8b0e03a633fecc9006454595" } } tracing-error-0.2.0/CHANGELOG.md000064400000000000000000000031240072674642500142420ustar 00000000000000# 0.2.0 (October 23, 2021) This is a breaking change release in order to update the `tracing-subscriber` dependency version to [the v0.3.x release series][v03]. ### Changed - Updated `tracing-subscriber` dependency to [v0.3.0][v03] ([#1677]) ### Fixed - Disabled default features of the `tracing` dependency so that proc-macro dependencies are not enabled ([#1144]) - Documentation fixes and improvements ([#635], [#695]) ### Added - **SpanTrace**: Added `SpanTrace::new` constructor for constructing a `SpanTrace` from a `Span` passed as an argument (rather than capturing the current span) ([#1492]) Thanks to @CAD97 for contributing to this release! [v03]: https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.0 [#635]: https://github.com/tokio-rs/tracing/pull/635 [#695]: https://github.com/tokio-rs/tracing/pull/695 [#1144]: https://github.com/tokio-rs/tracing/pull/1144 [#1492]: https://github.com/tokio-rs/tracing/pull/1492 [#1677]: https://github.com/tokio-rs/tracing/pull/1677 # 0.1.2 (March 3, 2020) ### Added - **TracedError**: `TracedError`, an error type wrapper that annotates an error with the current span. - **SpanTrace**:`SpanTrace::status` method and `SpanTraceStatus` type for determing whether a `SpanTrace` was successfully captured (#614) ### Changed - **SpanTrace**: Made backtrace formatting more consistent with upstream changes to `std::backtrace` (#584) # 0.1.1 (February 5, 2020) ### Fixed - Fixed a typo in the crate description ### Changed - the maintenance badge from active to experimental # 0.1.0 (February 5, 2020) - Initial release tracing-error-0.2.0/Cargo.toml0000644000000025100000000000100116050ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "tracing-error" version = "0.2.0" authors = ["Eliza Weisman ", "Jane Lusby ", "Tokio Contributors "] description = "Utilities for enriching errors with `tracing`.\n" homepage = "https://tokio.rs" readme = "README.md" keywords = ["tracing", "error-handling", "exception-reporting", "backtrace"] categories = ["development-tools::debugging", "rust-patterns"] license = "MIT" repository = "https://github.com/tokio-rs/tracing" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies.tracing] version = "0.1.12" features = ["std"] default-features = false [dependencies.tracing-subscriber] version = "0.3" features = ["registry", "fmt"] default-features = false [features] default = ["traced-error"] traced-error = [] [badges.maintenance] status = "experimental" tracing-error-0.2.0/Cargo.toml.orig000064400000000000000000000022400072674642500153160ustar 00000000000000[package] name = "tracing-error" # When releasing to crates.io: # - Remove path dependencies # - Update html_root_url. # - Update doc url # - Cargo.toml # - README.md # - Update CHANGELOG.md. # - Create "v0.2.x" git tag version = "0.2.0" authors = [ "Eliza Weisman ", "Jane Lusby ", "Tokio Contributors " ] license = "MIT" readme = "README.md" repository = "https://github.com/tokio-rs/tracing" homepage = "https://tokio.rs" description = """ Utilities for enriching errors with `tracing`. """ categories = [ "development-tools::debugging", "rust-patterns" ] keywords = [ "tracing", "error-handling", "exception-reporting", "backtrace" ] edition = "2018" [features] default = ["traced-error"] traced-error = [] [dependencies] tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "fmt"] } tracing = { path = "../tracing", version = "0.1.12", default-features = false, features = ["std"] } [badges] maintenance = { status = "experimental" } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] tracing-error-0.2.0/LICENSE000064400000000000000000000020460072674642500134400ustar 00000000000000Copyright (c) 2019 Tokio Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tracing-error-0.2.0/README.md000064400000000000000000000205320072674642500137120ustar 00000000000000![Tracing — Structured, application-level diagnostics][splash] [splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg # tracing-error Utilities for enriching error handling with [`tracing`] diagnostic information. [![Crates.io][crates-badge]][crates-url] [![Documentation][docs-badge]][docs-url] [![Documentation (master)][docs-master-badge]][docs-master-url] [![MIT licensed][mit-badge]][mit-url] [![Build Status][actions-badge]][actions-url] [![Discord chat][discord-badge]][discord-url] ![maintenance status][maint-badge] [Documentation (release)][docs-url] | [Documentation (master)][docs-master-url] | [Chat][discord-url] [crates-badge]: https://img.shields.io/crates/v/tracing-error.svg [crates-url]: https://crates.io/crates/tracing-error/0.2.0 [docs-badge]: https://docs.rs/tracing-error/badge.svg [docs-url]: https://docs.rs/tracing-error/0.2.0/tracing_error [docs-master-badge]: https://img.shields.io/badge/docs-master-blue [docs-master-url]: https://tracing-rs.netlify.com/tracing_error [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: LICENSE [actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg [actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI [discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white [discord-url]: https://discord.gg/EeF3cQw [maint-badge]: https://img.shields.io/badge/maintenance-experimental-blue.svg # Overview [`tracing`] is a framework for instrumenting Rust programs to collect scoped, structured, and async-aware diagnostics. This crate provides integrations between [`tracing`] instrumentation and Rust error handling. It enables enriching error types with diagnostic information from `tracing` [span] contexts, formatting those contexts when errors are displayed, and automatically generate `tracing` [events] when errors occur. The crate provides the following: * [`SpanTrace`], a captured trace of the current `tracing` [span] context * [`ErrorLayer`], a [subscriber layer] which enables capturing `SpanTrace`s **Note**: This crate is currently experimental. *Compiler support: [requires `rustc` 1.42+][msrv]* [msrv]: #supported-rust-versions ## Usage `tracing-error` provides the [`SpanTrace`] type, which captures the current `tracing` span context when it is constructed and allows it to be displayed at a later time. For example: ```rust use std::{fmt, error::Error}; use tracing_error::SpanTrace; #[derive(Debug)] pub struct MyError { context: SpanTrace, // ... } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // ... format other parts of the error ... self.context.fmt(f)?; // ... format other error context information, cause chain, etc ... # Ok(()) } } impl Error for MyError {} impl MyError { pub fn new() -> Self { Self { context: SpanTrace::capture(), // ... other error information ... } } } ``` This crate also provides [`TracedError`], for attaching a [`SpanTrace`] to an existing error. The easiest way to wrap errors in `TracedError` is to either use the [`InstrumentResult`] and [`InstrumentError`] traits or the `From`/`Into` traits. ```rust use tracing_error::prelude::*; std::fs::read_to_string("myfile.txt").in_current_span()?; ``` Once an error has been wrapped with with a [`TracedError`], the [`SpanTrace`] can be extracted one of three ways: either via [`TracedError`]'s `Display`/`Debug` implementations, or via the [`ExtractSpanTrace`] trait. For example, here is how one might print the errors but specialize the printing when the error is a placeholder for a wrapping [`SpanTrace`]: ```rust use std::error::Error; use tracing_error::ExtractSpanTrace as _; fn print_extracted_spantraces(error: &(dyn Error + 'static)) { let mut error = Some(error); let mut ind = 0; eprintln!("Error:"); while let Some(err) = error { if let Some(spantrace) = err.span_trace() { eprintln!("found a spantrace:\n{}", spantrace); } else { eprintln!("{:>4}: {}", ind, err); } error = err.source(); ind += 1; } } ``` Whereas here, we can still display the content of the `SpanTraces` without any special casing by simply printing all errors in our error chain. ```rust use std::error::Error; fn print_naive_spantraces(error: &(dyn Error + 'static)) { let mut error = Some(error); let mut ind = 0; eprintln!("Error:"); while let Some(err) = error { eprintln!("{:>4}: {}", ind, err); error = err.source(); ind += 1; } } ``` Applications that wish to use `tracing-error`-enabled errors should construct an [`ErrorLayer`] and add it to their [`Subscriber`] in order to enable capturing [`SpanTrace`]s. For example: ```rust use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; fn main() { let subscriber = tracing_subscriber::Registry::default() // any number of other subscriber layers may be added before or // after the `ErrorLayer`... .with(ErrorLayer::default()); // set the subscriber as the default for the application tracing::subscriber::set_global_default(subscriber); } ``` ## Feature Flags - `traced-error` - Enables the [`TracedError`] type and related traits - [`InstrumentResult`] and [`InstrumentError`] extension traits, which provide an [`in_current_span()`] method for bundling errors with a [`SpanTrace`]. - [`ExtractSpanTrace`] extension trait, for extracting `SpanTrace`s from behind `dyn Error` trait objects. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported version is 1.42. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable compiler version is 1.45, the minimum supported version will not be increased past 1.42, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. ## Related Crates In addition to this repository, here are also several third-party crates which are not maintained by the `tokio` project. These include: - [`color-spantrace`] provides a formatter for rendering SpanTrace in the style of `color-backtrace` - [`color-eyre`] provides a customized version of `eyre::Report` for capturing span traces and backtraces with new errors and pretty printing them in error reports. [`color-spantrace`]: https://github.com/yaahc/color-spantrace [`color-eyre`]: https://github.com/yaahc/color-eyre ## License This project is licensed under the [MIT license](LICENSE). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Tracing by you, shall be licensed as MIT, without any additional terms or conditions. [`SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html [`ErrorLayer`]: https://docs.rs/tracing-error/*/tracing_error/struct.ErrorLayer.html [`TracedError`]: https://docs.rs/tracing-error/*/tracing_error/struct.TracedError.html [`InstrumentResult`]: https://docs.rs/tracing-error/*/tracing_error/trait.InstrumentResult.html [`InstrumentError`]: https://docs.rs/tracing-error/*/tracing_error/trait.InstrumentError.html [`ExtractSpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/trait.ExtractSpanTrace.html [`in_current_span()`]: https://docs.rs/tracing-error/*/tracing_error/trait.InstrumentResult.html#tymethod.in_current_span [span]: https://docs.rs/tracing/latest/tracing/span/index.html [events]: https://docs.rs/tracing/latest/tracing/struct.Event.html [`Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html [subscriber layer]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/trait.Layer.html [`tracing`]: https://docs.rs/tracing [`std::error::Error`]: https://doc.rust-lang.org/stable/std/error/trait.Error.html [`SpanTrace`]: https://docs.rs/tracing-error/0.2.0/tracing_error/struct.SpanTrace.html [`ErrorLayer`]: https://docs.rs/tracing-error/0.2.0/tracing_error/struct.ErrorLayer.html tracing-error-0.2.0/src/backtrace.rs000064400000000000000000000245220072674642500155120ustar 00000000000000use crate::layer::WithContext; use std::fmt; use tracing::{Metadata, Span}; /// A captured trace of [`tracing`] spans. /// /// This type can be thought of as a relative of /// [`std::backtrace::Backtrace`][`Backtrace`]. /// However, rather than capturing the current call stack when it is /// constructed, a `SpanTrace` instead captures the current [span] and its /// [parents]. /// /// In many cases, span traces may be as useful as stack backtraces useful in /// pinpointing where an error occurred and why, if not moreso: /// /// * A span trace captures only the user-defined, human-readable `tracing` /// spans, rather than _every_ frame in the call stack, often cutting out a /// lot of noise. /// * Span traces include the [fields] recorded by each span in the trace, as /// well as their names and source code location, so different invocations of /// a function can be distinguished, /// * In asynchronous code, backtraces for errors that occur in [futures] often /// consist not of the stack frames that _spawned_ a future, but the stack /// frames of the executor that is responsible for running that future. This /// means that if an `async fn` calls another `async fn` which generates an /// error, the calling async function will not appear in the stack trace (and /// often, the callee won't either!). On the other hand, when the /// [`tracing-futures`] crate is used to instrument async code, the span trace /// will represent the logical application context a future was running in, /// rather than the stack trace of the executor that was polling a future when /// an error occurred. /// /// Finally, unlike stack [`Backtrace`]s, capturing a `SpanTrace` is fairly /// lightweight, and the resulting struct is not large. The `SpanTrace` struct /// is formatted lazily; instead, it simply stores a copy of the current span, /// and allows visiting the spans in that span's trace tree by calling the /// [`with_spans` method][`with_spans`]. /// /// # Formatting /// /// The `SpanTrace` type implements `fmt::Display`, formatting the span trace /// similarly to how Rust formats panics. For example: /// /// ```text /// 0: custom_error::do_another_thing /// with answer=42 will_succeed=false /// at examples/examples/custom_error.rs:42 /// 1: custom_error::do_something /// with foo="hello world" /// at examples/examples/custom_error.rs:37 /// ``` /// /// Additionally, if custom formatting is desired, the [`with_spans`] method can /// be used to visit each span in the trace, formatting them in order. /// /// [`tracing`]: https://docs.rs/tracing /// [`Backtrace`]: https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html /// [span]: https://docs.rs/tracing/latest/tracing/span/index.html /// [parents]: https://docs.rs/tracing/latest/tracing/span/index.html#span-relationships /// [fields]: https://docs.rs/tracing/latest/tracing/field/index.html /// [futures]: https://doc.rust-lang.org/std/future/trait.Future.html /// [`tracing-futures`]: https://docs.rs/tracing-futures/ /// [`with_spans`]: #method.with_spans #[derive(Clone)] pub struct SpanTrace { span: Span, } // === impl SpanTrace === impl SpanTrace { /// Create a new span trace with the given span as the innermost span. pub fn new(span: Span) -> Self { SpanTrace { span } } /// Capture the current span trace. /// /// # Examples /// ```rust /// use tracing_error::SpanTrace; /// /// pub struct MyError { /// span_trace: SpanTrace, /// // ... /// } /// /// # fn some_error_condition() -> bool { true } /// /// #[tracing::instrument] /// pub fn my_function(arg: &str) -> Result<(), MyError> { /// if some_error_condition() { /// return Err(MyError { /// span_trace: SpanTrace::capture(), /// // ... /// }); /// } /// /// // ... /// # Ok(()) /// } /// ``` pub fn capture() -> Self { SpanTrace::new(Span::current()) } /// Apply a function to all captured spans in the trace until it returns /// `false`. /// /// This will call the provided function with a reference to the /// [`Metadata`] and a formatted representation of the [fields] of each span /// captured in the trace, starting with the span that was current when the /// trace was captured. The function may return `true` or `false` to /// indicate whether to continue iterating over spans; if it returns /// `false`, no additional spans will be visited. /// /// [fields]: https://docs.rs/tracing/latest/tracing/field/index.html /// [`Metadata`]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html pub fn with_spans(&self, f: impl FnMut(&'static Metadata<'static>, &str) -> bool) { self.span.with_subscriber(|(id, s)| { if let Some(getcx) = s.downcast_ref::() { getcx.with_context(s, id, f); } }); } /// Returns the status of this `SpanTrace`. /// /// The status indicates one of the following: /// * the current subscriber does not support capturing `SpanTrace`s /// * there was no current span, so a trace was not captured /// * a span trace was successfully captured pub fn status(&self) -> SpanTraceStatus { let inner = if self.span.is_none() { SpanTraceStatusInner::Empty } else { let mut status = None; self.span.with_subscriber(|(_, s)| { if s.downcast_ref::().is_some() { status = Some(SpanTraceStatusInner::Captured); } }); status.unwrap_or(SpanTraceStatusInner::Unsupported) }; SpanTraceStatus(inner) } } /// The current status of a SpanTrace, indicating whether it was captured or /// whether it is empty for some other reason. #[derive(Debug, PartialEq, Eq)] pub struct SpanTraceStatus(SpanTraceStatusInner); impl SpanTraceStatus { /// Formatting a SpanTrace is not supported, likely because there is no /// ErrorLayer or the ErrorLayer is from a different version of /// tracing_error pub const UNSUPPORTED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Unsupported); /// The SpanTrace is empty, likely because it was captured outside of any /// `span`s pub const EMPTY: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Empty); /// A span trace has been captured and the `SpanTrace` should print /// reasonable information when rendered. pub const CAPTURED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Captured); } #[derive(Debug, PartialEq, Eq)] enum SpanTraceStatusInner { Unsupported, Empty, Captured, } macro_rules! try_bool { ($e:expr, $dest:ident) => {{ let ret = $e.unwrap_or_else(|e| $dest = Err(e)); if $dest.is_err() { return false; } ret }}; } impl fmt::Display for SpanTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut err = Ok(()); let mut span = 0; self.with_spans(|metadata, fields| { if span > 0 { try_bool!(write!(f, "\n",), err); } try_bool!( write!(f, "{:>4}: {}::{}", span, metadata.target(), metadata.name()), err ); if !fields.is_empty() { try_bool!(write!(f, "\n with {}", fields), err); } if let Some((file, line)) = metadata .file() .and_then(|file| metadata.line().map(|line| (file, line))) { try_bool!(write!(f, "\n at {}:{}", file, line), err); } span += 1; true }); err } } impl fmt::Debug for SpanTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { struct DebugSpan<'a> { metadata: &'a Metadata<'a>, fields: &'a str, } impl<'a> fmt::Debug for DebugSpan<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{{ target: {:?}, name: {:?}", self.metadata.target(), self.metadata.name() )?; if !self.fields.is_empty() { write!(f, ", fields: {:?}", self.fields)?; } if let Some((file, line)) = self .metadata .file() .and_then(|file| self.metadata.line().map(|line| (file, line))) { write!(f, ", file: {:?}, line: {:?}", file, line)?; } write!(f, " }}")?; Ok(()) } } write!(f, "SpanTrace ")?; let mut dbg = f.debug_list(); self.with_spans(|metadata, fields| { dbg.entry(&DebugSpan { metadata, fields }); true }); dbg.finish() } } #[cfg(test)] mod tests { use super::*; use crate::ErrorLayer; use tracing::subscriber::with_default; use tracing::{span, Level}; use tracing_subscriber::{prelude::*, registry::Registry}; #[test] fn capture_supported() { let subscriber = Registry::default().with(ErrorLayer::default()); with_default(subscriber, || { let span = span!(Level::ERROR, "test span"); let _guard = span.enter(); let span_trace = SpanTrace::capture(); dbg!(&span_trace); assert_eq!(SpanTraceStatus::CAPTURED, span_trace.status()) }); } #[test] fn capture_empty() { let subscriber = Registry::default().with(ErrorLayer::default()); with_default(subscriber, || { let span_trace = SpanTrace::capture(); dbg!(&span_trace); assert_eq!(SpanTraceStatus::EMPTY, span_trace.status()) }); } #[test] fn capture_unsupported() { let subscriber = Registry::default(); with_default(subscriber, || { let span = span!(Level::ERROR, "test span"); let _guard = span.enter(); let span_trace = SpanTrace::capture(); dbg!(&span_trace); assert_eq!(SpanTraceStatus::UNSUPPORTED, span_trace.status()) }); } } tracing-error-0.2.0/src/error.rs000064400000000000000000000216020072674642500147200ustar 00000000000000use crate::SpanTrace; use std::error::Error; use std::fmt::{self, Debug, Display}; struct Erased; /// A wrapper type for `Error`s that bundles a `SpanTrace` with an inner `Error` /// type. /// /// This type is a good match for the error-kind pattern where you have an error /// type with an inner enum of error variants and you would like to capture a /// span trace that can be extracted during printing without formatting the span /// trace as part of your display impl. /// /// An example of implementing an error type for a library using `TracedError` /// might look like this /// /// ```rust,compile_fail /// #[derive(Debug, thiserror::Error)] /// enum Kind { /// // ... /// } /// /// #[derive(Debug)] /// pub struct Error { /// source: TracedError, /// backtrace: Backtrace, /// } /// /// impl std::error::Error for Error { /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { /// self.source.source() /// } /// /// fn backtrace(&self) -> Option<&Backtrace> { /// Some(&self.backtrace) /// } /// } /// /// impl fmt::Display for Error { /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt::Display::fmt(&self.source, fmt) /// } /// } /// /// impl From for Error /// where /// Kind: From, /// { /// fn from(source: E) -> Self { /// Self { /// source: Kind::from(source).into(), /// backtrace: Backtrace::capture(), /// } /// } /// } /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))] pub struct TracedError { inner: ErrorImpl, } impl From for TracedError where E: Error + Send + Sync + 'static, { fn from(error: E) -> Self { // # SAFETY // // This function + the repr(C) on the ErrorImpl make the type erasure throughout the rest // of this struct's methods safe. This saves a function pointer that is parameterized on the Error type // being stored inside the ErrorImpl. This lets the object_ref function safely cast a type // erased `ErrorImpl` back to its original type, which is needed in order to forward our // error/display/debug impls to the internal error type from the type erased error type. // // The repr(C) is necessary to ensure that the struct is layed out in the order we // specified it so that we can safely access the vtable and spantrace fields thru a type // erased pointer to the original object. let vtable = &ErrorVTable { object_ref: object_ref::, }; Self { inner: ErrorImpl { vtable, span_trace: SpanTrace::capture(), error, }, } } } #[repr(C)] struct ErrorImpl { vtable: &'static ErrorVTable, span_trace: SpanTrace, // NOTE: Don't use directly. Use only through vtable. Erased type may have // different alignment. error: E, } impl ErrorImpl { pub(crate) fn error(&self) -> &(dyn Error + Send + Sync + 'static) { // # SAFETY // // this function is used to cast a type-erased pointer to a pointer to error's // original type. the `ErrorImpl::error` method, which calls this function, requires that // the type this function casts to be the original erased type of the error; failure to // uphold this is UB. since the `From` impl is parameterized over the original error type, // the function pointer we construct here will also retain the original type. therefore, // when this is consumed by the `error` method, it will be safe to call. unsafe { &*(self.vtable.object_ref)(self) } } } struct ErrorVTable { object_ref: unsafe fn(&ErrorImpl) -> &(dyn Error + Send + Sync + 'static), } // # SAFETY // // This function must be parameterized on the type E of the original error that is being stored // inside of the `ErrorImpl`. When it is parameterized by the correct type, it safely // casts the erased `ErrorImpl` pointer type back to the original pointer type. unsafe fn object_ref(e: &ErrorImpl) -> &(dyn Error + Send + Sync + 'static) where E: Error + Send + Sync + 'static, { // Attach E's native Error vtable onto a pointer to e.error. &(*(e as *const ErrorImpl as *const ErrorImpl)).error } impl Error for TracedError where E: std::error::Error + 'static, { // # SAFETY // // This function is safe so long as all functions on `ErrorImpl` uphold the invariant // that the wrapped error is only ever accessed by the `error` method. This method uses the // function in the vtable to safely convert the pointer type back to the original type, and // then returns the reference to the erased error. // // This function is necessary for the `downcast_ref` in `ExtractSpanTrace` to work, because it // needs a concrete type to downcast to and we cannot downcast to ErrorImpls parameterized on // errors defined in other crates. By erasing the type here we can always cast back to the // Erased version of the ErrorImpl pointer, and still access the internal error type safely // through the vtable. fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> { let erased = unsafe { &*(&self.inner as *const ErrorImpl as *const ErrorImpl) }; Some(erased) } } impl Debug for TracedError where E: std::error::Error, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&self.inner.error, f) } } impl Display for TracedError where E: std::error::Error, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.inner.error, f) } } impl Error for ErrorImpl { fn source(&self) -> Option<&(dyn Error + 'static)> { self.error().source() } } impl Debug for ErrorImpl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("span backtrace:\n")?; Debug::fmt(&self.span_trace, f) } } impl Display for ErrorImpl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("span backtrace:\n")?; Display::fmt(&self.span_trace, f) } } /// Extension trait for instrumenting errors with `SpanTrace`s #[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))] pub trait InstrumentError { /// The type of the wrapped error after instrumentation type Instrumented; /// Instrument an Error by bundling it with a SpanTrace /// /// # Examples /// /// ```rust /// use tracing_error::{TracedError, InstrumentError}; /// /// fn wrap_error(e: E) -> TracedError /// where /// E: std::error::Error + Send + Sync + 'static /// { /// e.in_current_span() /// } /// ``` fn in_current_span(self) -> Self::Instrumented; } /// Extension trait for instrumenting errors in `Result`s with `SpanTrace`s #[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))] pub trait InstrumentResult { /// The type of the wrapped error after instrumentation type Instrumented; /// Instrument an Error by bundling it with a SpanTrace /// /// # Examples /// /// ```rust /// # use std::{io, fs}; /// use tracing_error::{TracedError, InstrumentResult}; /// /// # fn fallible_fn() -> io::Result<()> { fs::read_dir("......").map(drop) }; /// /// fn do_thing() -> Result<(), TracedError> { /// fallible_fn().in_current_span() /// } /// ``` fn in_current_span(self) -> Result; } impl InstrumentResult for Result where E: InstrumentError, { type Instrumented = ::Instrumented; fn in_current_span(self) -> Result { self.map_err(E::in_current_span) } } /// A trait for extracting SpanTraces created by `in_current_span()` from `dyn /// Error` trait objects #[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))] pub trait ExtractSpanTrace { /// Attempts to downcast to a `TracedError` and return a reference to its /// SpanTrace /// /// # Examples /// /// ```rust /// use tracing_error::ExtractSpanTrace; /// use std::error::Error; /// /// fn print_span_trace(e: &(dyn Error + 'static)) { /// let span_trace = e.span_trace(); /// if let Some(span_trace) = span_trace { /// println!("{}", span_trace); /// } /// } /// ``` fn span_trace(&self) -> Option<&SpanTrace>; } impl InstrumentError for E where TracedError: From, { type Instrumented = TracedError; fn in_current_span(self) -> Self::Instrumented { TracedError::from(self) } } impl ExtractSpanTrace for dyn Error + 'static { fn span_trace(&self) -> Option<&SpanTrace> { self.downcast_ref::>() .map(|inner| &inner.span_trace) } } tracing-error-0.2.0/src/layer.rs000064400000000000000000000106010072674642500147000ustar 00000000000000use std::any::{type_name, TypeId}; use std::fmt; use std::marker::PhantomData; use tracing::{span, Dispatch, Metadata, Subscriber}; use tracing_subscriber::fmt::format::{DefaultFields, FormatFields}; use tracing_subscriber::{ fmt::FormattedFields, layer::{self, Layer}, registry::LookupSpan, }; /// A subscriber [`Layer`] that enables capturing [`SpanTrace`]s. /// /// Optionally, this type may be constructed with a [field formatter] to use /// when formatting the fields of each span in a trace. When no formatter is /// provided, the [default format] is used instead. /// /// [`Layer`]: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html /// [`SpanTrace`]: ../struct.SpanTrace.html /// [field formatter]: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/fmt/trait.FormatFields.html /// [default format]: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/fmt/format/struct.DefaultFields.html pub struct ErrorLayer { format: F, get_context: WithContext, _subscriber: PhantomData, } // this function "remembers" the types of the subscriber and the formatter, // so that we can downcast to something aware of them without knowing those // types at the callsite. pub(crate) struct WithContext( fn(&Dispatch, &span::Id, f: &mut dyn FnMut(&'static Metadata<'static>, &str) -> bool), ); impl Layer for ErrorLayer where S: Subscriber + for<'span> LookupSpan<'span>, F: for<'writer> FormatFields<'writer> + 'static, { /// Notifies this layer that a new span was constructed with the given /// `Attributes` and `Id`. fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) { let span = ctx.span(id).expect("span must already exist!"); if span.extensions().get::>().is_some() { return; } let mut fields = FormattedFields::::new(String::new()); if self.format.format_fields(fields.as_writer(), attrs).is_ok() { span.extensions_mut().insert(fields); } } unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { match id { id if id == TypeId::of::() => Some(self as *const _ as *const ()), id if id == TypeId::of::() => { Some(&self.get_context as *const _ as *const ()) } _ => None, } } } impl ErrorLayer where F: for<'writer> FormatFields<'writer> + 'static, S: Subscriber + for<'span> LookupSpan<'span>, { /// Returns a new `ErrorLayer` with the provided [field formatter]. /// /// [field formatter]: https://docs.rs/tracing-subscriber/0.2.10/tracing_subscriber/fmt/trait.FormatFields.html pub fn new(format: F) -> Self { Self { format, get_context: WithContext(Self::get_context), _subscriber: PhantomData, } } fn get_context( dispatch: &Dispatch, id: &span::Id, f: &mut dyn FnMut(&'static Metadata<'static>, &str) -> bool, ) { let subscriber = dispatch .downcast_ref::() .expect("subscriber should downcast to expected type; this is a bug!"); let span = subscriber .span(id) .expect("registry should have a span for the current ID"); for span in span.scope() { let cont = if let Some(fields) = span.extensions().get::>() { f(span.metadata(), fields.fields.as_str()) } else { f(span.metadata(), "") }; if !cont { break; } } } } impl WithContext { pub(crate) fn with_context<'a>( &self, dispatch: &'a Dispatch, id: &span::Id, mut f: impl FnMut(&'static Metadata<'static>, &str) -> bool, ) { (self.0)(dispatch, id, &mut f) } } impl Default for ErrorLayer where S: Subscriber + for<'span> LookupSpan<'span>, { fn default() -> Self { Self::new(DefaultFields::default()) } } impl fmt::Debug for ErrorLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ErrorLayer") .field("format", &self.format) .field("subscriber", &format_args!("{}", type_name::())) .finish() } } tracing-error-0.2.0/src/lib.rs000064400000000000000000000177240072674642500143470ustar 00000000000000//! Utilities for enriching error handling with [`tracing`] diagnostic //! information. //! //! # Overview //! //! [`tracing`] is a framework for instrumenting Rust programs to collect //! scoped, structured, and async-aware diagnostics. This crate provides //! integrations between [`tracing`] instrumentation and Rust error handling. It //! enables enriching error types with diagnostic information from `tracing` //! [span] contexts, formatting those contexts when errors are displayed, and //! automatically generate `tracing` [events] when errors occur. //! //! The crate provides the following: //! //! * [`SpanTrace`], a captured trace of the current `tracing` [span] context //! //! * [`ErrorLayer`], a [subscriber layer] which enables capturing `SpanTrace`s //! //! **Note**: This crate is currently experimental. //! //! *Compiler support: [requires `rustc` 1.42+][msrv]* //! //! [msrv]: #supported-rust-versions //! //! ## Feature Flags //! //! - `traced-error` - Enables the [`TracedError`] type and related Traits //! - [`InstrumentResult`] and [`InstrumentError`] extension traits, which //! provide an [`in_current_span()`] method for bundling errors with a //! [`SpanTrace`]. //! - [`ExtractSpanTrace`] extension trait, for extracting `SpanTrace`s from //! behind `dyn Error` trait objects. //! //! ## Usage //! //! `tracing-error` provides the [`SpanTrace`] type, which captures the current //! `tracing` span context when it is constructed and allows it to be displayed //! at a later time. //! //! For example: //! //! ```rust //! use std::{fmt, error::Error}; //! use tracing_error::SpanTrace; //! //! #[derive(Debug)] //! pub struct MyError { //! context: SpanTrace, //! // ... //! } //! //! impl fmt::Display for MyError { //! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { //! // ... format other parts of the error ... //! //! self.context.fmt(f)?; //! //! // ... format other error context information, cause chain, etc ... //! # Ok(()) //! } //! } //! //! impl Error for MyError {} //! //! impl MyError { //! pub fn new() -> Self { //! Self { //! context: SpanTrace::capture(), //! // ... other error information ... //! } //! } //! } //! ``` //! //! This crate also provides [`TracedError`], for attaching a [`SpanTrace`] to //! an existing error. The easiest way to wrap errors in `TracedError` is to //! either use the [`InstrumentResult`] and [`InstrumentError`] traits or the //! `From`/`Into` traits. //! //! ```rust //! # use std::error::Error; //! use tracing_error::prelude::*; //! //! # fn fake_main() -> Result<(), Box> { //! std::fs::read_to_string("myfile.txt").in_current_span()?; //! # Ok(()) //! # } //! ``` //! //! Once an error has been wrapped with with a [`TracedError`] the [`SpanTrace`] //! can be extracted one of 3 ways: either via [`TracedError`]'s //! `Display`/`Debug` implementations, or via the [`ExtractSpanTrace`] trait. //! //! For example, here is how one might print the errors but specialize the //! printing when the error is a placeholder for a wrapping [`SpanTrace`]: //! //! ```rust //! use std::error::Error; //! use tracing_error::ExtractSpanTrace as _; //! //! fn print_extracted_spantraces(error: &(dyn Error + 'static)) { //! let mut error = Some(error); //! let mut ind = 0; //! //! eprintln!("Error:"); //! //! while let Some(err) = error { //! if let Some(spantrace) = err.span_trace() { //! eprintln!("found a spantrace:\n{}", spantrace); //! } else { //! eprintln!("{:>4}: {}", ind, err); //! } //! //! error = err.source(); //! ind += 1; //! } //! } //! //! ``` //! //! Whereas here, we can still display the content of the `SpanTraces` without //! any special casing by simply printing all errors in our error chain. //! //! ```rust //! use std::error::Error; //! //! fn print_naive_spantraces(error: &(dyn Error + 'static)) { //! let mut error = Some(error); //! let mut ind = 0; //! //! eprintln!("Error:"); //! //! while let Some(err) = error { //! eprintln!("{:>4}: {}", ind, err); //! error = err.source(); //! ind += 1; //! } //! } //! ``` //! //! Applications that wish to use `tracing-error`-enabled errors should //! construct an [`ErrorLayer`] and add it to their [`Subscriber`] in order to //! enable capturing [`SpanTrace`]s. For example: //! //! ```rust //! use tracing_error::ErrorLayer; //! use tracing_subscriber::prelude::*; //! //! fn main() { //! let subscriber = tracing_subscriber::Registry::default() //! // any number of other subscriber layers may be added before or //! // after the `ErrorLayer`... //! .with(ErrorLayer::default()); //! //! // set the subscriber as the default for the application //! tracing::subscriber::set_global_default(subscriber); //! } //! ``` //! //! [`SpanTrace`]: struct.SpanTrace.html //! [`ErrorLayer`]: struct.ErrorLayer.html //! [`TracedError`]: struct.TracedError.html //! [`InstrumentResult`]: trait.InstrumentResult.html //! [`InstrumentError`]: trait.InstrumentError.html //! [`ExtractSpanTrace`]: trait.ExtractSpanTrace.html //! [`in_current_span()`]: trait.InstrumentResult.html#tymethod.in_current_span //! [span]: https://docs.rs/tracing/latest/tracing/span/index.html //! [events]: https://docs.rs/tracing/latest/tracing/struct.Event.html //! [`Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html //! [subscriber layer]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/trait.Layer.html //! [`tracing`]: https://docs.rs/tracing //! [`std::error::Error`]: https://doc.rust-lang.org/stable/std/error/trait.Error.html //! //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported //! version is 1.42. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current //! stable compiler version is 1.45, the minimum supported version will not be //! increased past 1.42, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! #![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))] #![doc(html_root_url = "https://docs.rs/tracing-error/0.2.0")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" )] #![warn( missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub, bad_style, const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, no_mangle_generic_items, overflowing_literals, path_statements, patterns_in_fns_without_body, private_in_public, unconditional_recursion, unused, unused_allocation, unused_comparisons, unused_parens, while_true )] mod backtrace; #[cfg(feature = "traced-error")] mod error; mod layer; pub use self::backtrace::{SpanTrace, SpanTraceStatus}; #[cfg(feature = "traced-error")] pub use self::error::{ExtractSpanTrace, InstrumentError, InstrumentResult, TracedError}; pub use self::layer::ErrorLayer; #[cfg(feature = "traced-error")] #[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))] pub mod prelude { //! The `tracing-error` prelude. //! //! This brings into scope the `InstrumentError, `InstrumentResult`, and `ExtractSpanTrace` //! extension traits. These traits allow attaching `SpanTrace`s to errors and //! subsequently retrieving them from `dyn Error` trait objects. pub use crate::{ExtractSpanTrace as _, InstrumentError as _, InstrumentResult as _}; }