I very much like the idea of the tracing and tracing-error
However, these crates are not the complete solution for error handling.
You need to create your own error type and add tracing info there.
I'm used to using eyre, but it does not look like it would be easy to add tracing info there.
First of all, I am interested are there any crates that provide errors similar to anyhow
or eyre
but also use tracing-error
.
I also tried to create my own very simple error type. And I want some improvements:
- I want to know the exact file and line where the Error happened, not only the function.
- Also when I use
map_err
, it would be great not to write an error message at all, but to automatically have it like "call to [function_name] failed". - Maybe error messages can have a syntax similar to tracing events syntax, where you can add variables without mentioning them in a format string.
Here is some example code.
use tracing_subscriber::prelude::*;
struct Error {
message: String,
cause: Option<Box<dyn ToString>>,
span_trace: tracing_error::SpanTrace,
}
impl Error {
fn new(message: impl ToString) -> Self {
Self {
message: message.to_string(),
cause: None,
span_trace: tracing_error::SpanTrace::capture(),
}
}
fn with_cause(message: impl ToString, cause: impl ToString + 'static) -> Self {
Self {
message: message.to_string(),
cause: Some(Box::new(cause)),
span_trace: tracing_error::SpanTrace::capture(),
}
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Error: {}", self.message)?;
if let Some(cause) = &self.cause {
write!(f, "\nCause: {}", cause.to_string())?;
}
write!(f, "\n{}", self.span_trace)
}
}
type Result<T> = std::result::Result<T, Error>;
#[tracing::instrument]
fn try_div(a: u32, b: u32) -> Result<u32> {
if b == 0 {
return Err(Error::new("division by zero"));
}
Ok(a / b)
}
#[tracing::instrument]
fn try_parse_to_i32(s: &str) -> Result<i32> {
// just an example of using tracing event
let s_len = s.len();
tracing::info!(s_len, "string length");
s.parse::<i32>()
.map_err(|e| Error::with_cause("failed to parse i32", e))
}
fn init_tracing() {
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish()
.with(tracing_error::ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber).unwrap();
}
fn main() {
init_tracing();
println!("try_div(1, 0): {:?}", try_div(1, 0));
println!("try_parse_to_i32(\"abc\"): {:?}", try_parse_to_i32("abc"));
}
It will print:
try_div(1, 0): Err(Error: division by zero
0: tmp::try_div
with a=1 b=0
at src/main.rs:39)
INFO try_parse_to_i32{s="abc"}: tmp: string length s_len=3
try_parse_to_i32("abc"): Err(Error: failed to parse i32
Cause: invalid digit found in string
0: tmp::try_parse_to_i32
with s="abc"
at src/main.rs:47)