Tracing in Rust
Tags: rust
Published: 2021-12-01 11:22:30

- Tracing with custom error, it's best if you catch error in the final main and output it

## With `SpanTrace`

``` rust
use tracing_error::{ErrorLayer, SpanTrace};
use tracing_subscriber::prelude::*;
use tracing::{error, info};
	  
#[derive(Debug)]
struct FooError {
  message: &'static str,
  context: SpanTrace,
}
	  
impl FooError {
  fn new(message: &'static str) -> Self {
      Self {
          message,
          context: SpanTrace::capture(),
      }
  }
}
	  
impl std::error::Error for FooError {}
	  
impl std::fmt::Display for FooError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      f.pad(self.message)?;
      write!(f, "\n\nspan backtrace:\n{}", self.context)?;
      // write!(f, "\n\ndebug span backtrace: {:?}", self.context)?;
      // write!(f, "\n\nalt debug span backtrace: {:#?}", self.context)?;
      Ok(())
  }
}
	  
#[tracing::instrument]
fn do_something(foo: &str) -> Result<&'static str, impl std::error::Error + Send + Sync + 'static> {
  info!("Doing something");
  do_another_thing(42, false)
}
	  
#[tracing::instrument]
fn do_another_thing(
  answer: usize,
  will_succeed: bool,
) -> Result<&'static str, impl std::error::Error + Send + Sync + 'static> {
  info!("trying to do other thing");
  Err(FooError::new("something broke, lol"))
}
	  
#[tracing::instrument]
fn main() {
  let json_fmt_layer = tracing_subscriber::fmt::Layer::new().json();
  tracing_subscriber::registry()
      .with(json_fmt_layer)
      .with(ErrorLayer::default())
      .init();
  if let Err(e) = do_something("hello world") {
  	error!("error: {}", e);
  	std::process::exit(1);
  } 
}
```

## Using `eyre` with tracing

```rust
use color_eyre::eyre;
// use eyre::WrapErr;
use tracing::{error, info};
use tracing_subscriber::prelude::*;

fn try_main() -> eyre::Result<(), eyre::Report> {
  info!("I am trying to parse");
  let _sm_error = "abc".parse::<i32>()?;//.wrap_err("failed to parse")?;
  Ok(())
}

#[allow(unused)]
fn try_main2() -> Result<(), Box<dyn std::error::Error + 'static>> {
  info!("I am trying to parse");
  let _sm_error = "abc".parse::<i32>()?;
  Ok(())
}

// Can't have infallible main, because we want to handle it
fn main() {
  let json_fmt_layer = tracing_subscriber::fmt::Layer::new().json();
  tracing_subscriber::registry()
      .with(json_fmt_layer)
      .init();

  if let Err(e) = try_main() {
      error!("{:?}", e);
      std::process::exit(1);
  }
}
```

The code outputs
```
{"timestamp":"2021-11-30T00:59:07.786510Z","level":"INFO","fields":{"message":"I am trying to parse"},"target":"simpleerr"}
{"timestamp":"2021-11-30T00:59:07.786717Z","level":"ERROR","fields":{"message":"invalid digit found in string\n\nLocation:\n    /rustc/09c42c45858d5f3aedfa670698275303a3d19afa/library/core/src/result.rs:1915:27"},"target":"simpleerr"}

```


## Using `eyre` with `SpanTrace` and `TracedError`

- Two ways of accomplishing this. 

	- Create error structs with `context` field of type `SpanTrace`, and implement `std::fmt::Display` trait as show below for `FooError`

	- Any error type (that implements `std::error::Error`) can be converted to `TracedError<E>` using `in_current_span()`, this is enough for most use cases (no backtraces just error reporting using tracing)

```rust
use color_eyre::eyre::{self, Context};
use tracing::{error, info, instrument};
use tracing_error::{ErrorLayer, SpanTrace};
use tracing_subscriber::prelude::*;

#[derive(Debug)]
struct FooError {
  message: &'static str,
  // This struct captures the current `tracing` span context when it is
  // constructed. Later, when we display this error, we will format this
  // captured span trace.
  context: SpanTrace,
}

// Explicitly implementing all methods
// We can also use in_current_span()
impl FooError {
  fn new(message: &'static str) -> Self {
      Self {
          message,
          context: SpanTrace::capture(),
      }
  }
}

impl std::error::Error for FooError {}

impl std::fmt::Display for FooError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      f.pad(self.message)?;
      write!(f, "\n\nspan backtrace:\n{}", self.context)?;
      // write!(f, "\n\ndebug span backtrace: {:?}", self.context)?;
      // write!(f, "\n\nalt debug span backtrace: {:#?}", self.context)?;
      Ok(())
  }
}

#[instrument]
fn do_something(foo: &str) -> eyre::Result<(), eyre::Report> {
  info!("Doing something");
  do_another_thing(42, false)
}

#[instrument]
fn do_another_thing(answer: usize, will_succeed: bool) -> eyre::Result<&'static str, eyre::Report> {
  info!("trying to do other thing");


    // ***********************************************************
	// ***********************************************************
	// Converts any error to TracedError (awesome) and automatically converted to eyre
	
	let _k= std::fs::read_to_string("myfile.txt").in_current_span()?;  



	// ***********************************************************
	// ***********************************************************
	// Err(FooError::new("something is broken").in_current_span())?;


	// to wrap underlying error
	// let _k= std::fs::read_to_string("myfile.txt").in_current_span().wrap_err_with(|| format!("can't read file here"))?;
	
  // Err(FooError::new("something broke lol")).map_err(|e| eyre::eyre!(e)) // convert to eyre explicitly

  Err(FooError::new("something broke lol")).wrap_err_with(|| format!("this is broken"))
  // wrap_err_with will convert underlying error to eyre compatible
}

#[instrument]
fn main() {
  let json_fmt_layer = tracing_subscriber::fmt::Layer::new().json();
  tracing_subscriber::registry()
      .with(json_fmt_layer)
      .with(ErrorLayer::default())  // for tracing errors
      .init();

  if let Err(e) = do_something("hello world") {
      error!("error: {}", e);
      std::process::exit(1);
  }
}
```