eyre_span/
lib.rs

1#![warn(missing_docs)]
2/*!
3A more lightweight alternative to [color-eyre], which simply grants access
4to the span where an error happened, allowing them to be printed into standard logging facilityies.
5
6To use, [`install`] the handler, after which you can get the span with [`ReportSpan::span`]
7or immediately log a `Result` with [`emit`] or its method alias [`Emit::emit`].
8
9This may not work correctly with all subscriber, but it works fine with the standard `tracing_subscriber::fmt`.
10
11If the `tracing-error` feature is enabled (default), the `Display` implementation will show a span trace.
12
13[color-eyre]: https://docs.rs/color-eyre/latest/color_eyre/
14*/
15
16use eyre::Report;
17use tracing::Span;
18
19#[derive(Debug)]
20struct Handler {
21	span: Span,
22}
23
24impl eyre::EyreHandler for Handler {
25	fn debug(&self, error: &dyn std::error::Error, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26		std::fmt::Debug::fmt(error, f)
27	}
28
29	#[cfg(feature = "tracing-error")]
30	fn display(&self, e: &dyn std::error::Error, f: &mut std::fmt::Formatter) -> std::fmt::Result {
31		use std::fmt::Write;
32
33		std::fmt::Display::fmt(e, f)?;
34
35		if f.alternate() {
36			let mut s = String::new();
37			tracing_error::SpanTrace::new(self.span.clone())
38				.with_spans(|meta, fields| {
39					write!(s, "\n• {}::{}", meta.target(), meta.name()).unwrap();
40					if !fields.is_empty() {
41						write!(s, "{{{}}}", strip_ansi(fields.to_owned())).unwrap();
42					}
43					true
44				});
45			f.write_str(&s)?;
46		}
47		Ok(())
48	}
49}
50
51fn strip_ansi(mut s: String) -> String {
52	let mut keep = true;
53	s.retain(|c| match c {
54		'\x1B' => { keep = false; false }
55		'm' if !keep => { keep = true; false }
56		_ => keep
57	});
58	s
59}
60
61mod seal {
62	pub trait Sealed {}
63}
64
65impl<T> seal::Sealed for eyre::Result<T> {}
66impl seal::Sealed for Report {}
67
68/// Extension trait for the [`span`](ReportSpan::span) method.
69pub trait ReportSpan: seal::Sealed {
70	/// Returns the span the error occurred in.
71	///
72	/// Panics if the handler was not installed.
73	fn span(&self) -> &Span;
74}
75
76impl ReportSpan for Report {
77	fn span(&self) -> &Span {
78		&self.handler()
79			.downcast_ref::<Handler>()
80			.expect("eyre-span handler")
81			.span
82	}
83}
84
85/// Extension trait for the [`emit`](Emit::emit) method.
86pub trait Emit<T>: seal::Sealed {
87	/// Method syntax for [`emit`].
88	fn emit(self) -> Option<T>;
89}
90
91impl<T> Emit<T> for Result<T, Report> {
92	fn emit(self) -> Option<T> {
93		emit(self)
94	}
95}
96
97/// Sends a [`tracing::error!`] event if an error happened.
98///
99/// Panics if the handler was not installed.
100pub fn emit<T>(e: Result<T, Report>) -> Option<T> {
101	match e {
102		Ok(v) => Some(v),
103		Err(e) => {
104			e.span().in_scope(|| tracing::error!("{e}"));
105			None
106		}
107	}
108}
109
110/// Installs the hook into Eyre. Required for this crate to function.
111pub fn install() -> Result<(), eyre::InstallError> {
112	eyre::set_hook(Box::new(|_| Box::new(Handler { span: tracing::Span::current() })))
113}