diff --git a/examples/examples/fmt-error-chain.rs b/examples/examples/fmt-error-chain.rs new file mode 100644 index 0000000000..460761552e --- /dev/null +++ b/examples/examples/fmt-error-chain.rs @@ -0,0 +1,133 @@ +//! This example demonstrates using the `tracing-error` crate's `SpanTrace` type +//! to attach a trace context to a custom error type. +#![deny(rust_2018_idioms)] +#![feature(provide_any)] +use std::any::Requisition; +use std::fmt; +use std::{error::Error, path::Path}; +use tracing::{error, info}; +use tracing_error::{ErrorSubscriber, SpanTrace}; +use tracing_subscriber::prelude::*; + +#[derive(Debug)] +struct FileError { + context: SpanTrace, +} + +impl FileError { + fn new() -> Self { + Self { + context: SpanTrace::capture(), + } + } +} + +impl Error for FileError { + fn provide<'a>(&'a self, mut req: Requisition<'a, '_>) { + req.provide_ref(&self.context) + .provide_ref::(self.context.as_ref()); + } +} + +impl fmt::Display for FileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("file does not exist") + } +} + +#[tracing::instrument] +fn read_file(path: &Path) -> Result { + Err(FileError::new()) +} + +#[derive(Debug)] +struct ConfigError { + source: FileError, +} + +impl From for ConfigError { + fn from(source: FileError) -> Self { + Self { source } + } +} + +impl Error for ConfigError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("config file cannot be loaded") + } +} + +#[tracing::instrument] +fn load_config() -> Result { + let path = Path::new("my_config"); + let config = read_file(&path)?; + Ok(config) +} + +struct App { + // Imagine this is actually something we deserialize with serde + config: String, +} + +impl App { + fn run() -> Result<(), AppError> { + let this = Self::init()?; + this.start() + } + + fn init() -> Result { + let config = load_config()?; + Ok(Self { config }) + } + + fn start(&self) -> Result<(), AppError> { + // Pretend our actual application logic all exists here + info!("Loaded config: {}", self.config); + Ok(()) + } +} + +#[derive(Debug)] +struct AppError { + source: ConfigError, +} + +impl From for AppError { + fn from(source: ConfigError) -> Self { + Self { source } + } +} + +impl Error for AppError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("config invalid") + } +} + +#[tracing::instrument] +fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::subscriber()) + // The `ErrorSubscriber` subscriber layer enables the use of `SpanTrace`. + .with(ErrorSubscriber::default()) + .init(); + + if let Err(e) = App::run() { + error!( + error = &e as &(dyn Error + 'static), + "App exited unsuccessfully" + ); + } +} diff --git a/tracing-error/src/backtrace.rs b/tracing-error/src/backtrace.rs index 16335d17b4..be2a1d134a 100644 --- a/tracing-error/src/backtrace.rs +++ b/tracing-error/src/backtrace.rs @@ -147,6 +147,12 @@ impl SpanTrace { } } +impl AsRef for SpanTrace { + fn as_ref(&self) -> &Span { + &self.span + } +} + /// The current status of a SpanTrace, indicating whether it was captured or /// whether it is empty for some other reason. #[derive(Debug, PartialEq, Eq)] diff --git a/tracing-error/src/subscriber.rs b/tracing-error/src/subscriber.rs index f6840acc07..2a354d8121 100644 --- a/tracing-error/src/subscriber.rs +++ b/tracing-error/src/subscriber.rs @@ -58,16 +58,6 @@ where span.extensions_mut().insert(fields); } } - - unsafe fn downcast_raw(&self, id: TypeId) -> Option> { - match id { - id if id == TypeId::of::() => Some(NonNull::from(self).cast()), - id if id == TypeId::of::() => { - Some(NonNull::from(&self.get_context).cast()) - } - _ => None, - } - } } impl ErrorSubscriber diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 204df05fab..b6b47b43bd 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -1122,21 +1122,43 @@ impl<'a> field::Visit for DefaultVisitor<'a> { } fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { - if let Some(source) = value.source() { - let italic = self.writer.italic(); - self.record_debug( - field, - &format_args!( - "{} {}{}{}{}", - value, - italic.paint(field.name()), - italic.paint(".sources"), - self.writer.dimmed().paint("="), - ErrorSourceList(source) - ), - ) - } else { - self.record_debug(field, &format_args!("{}", value)) + let source = value.source(); + let spantrace = value.chain().find_map(|value| value.request_ref::()); + match (source, spantrace) { + (Some(source), None) => { + let italic = self.writer.italic(); + self.record_debug( + field, + &format_args!( + "{} {}{}{}{}", + value, + italic.paint(field.name()), + italic.paint(".sources"), + self.writer.dimmed().paint("="), + ErrorSourceList(source) + ), + ) + }, + (None, None) => self.record_debug(field, &format_args!("{}", value)), + (Some(source), Some(spantrace)) => { + let italic = self.writer.italic(); + self.record_debug( + field, + &format_args!( + "{} {}{}{}{} {}{}{}{:?}", + value, + italic.paint(field.name()), + italic.paint(".sources"), + self.writer.dimmed().paint("="), + ErrorSourceList(source), + italic.paint(field.name()), + italic.paint(".span_context"), + self.writer.dimmed().paint("="), + spantrace + ), + ) + }, + (None, Some(spantrace)) => unimplemented!(), } } diff --git a/tracing-subscriber/src/lib.rs b/tracing-subscriber/src/lib.rs index 490dc92b0f..7d280ae53a 100644 --- a/tracing-subscriber/src/lib.rs +++ b/tracing-subscriber/src/lib.rs @@ -138,6 +138,8 @@ // "needless". #![allow(clippy::needless_update)] #![cfg_attr(not(feature = "std"), no_std)] +#![feature(error_in_core)] +#![feature(error_iter)] #[cfg(feature = "alloc")] extern crate alloc;