DEV Community

toyster
toyster

Posted on

Tracing in Rust

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

With SpanTrace

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);
  } 
}
Enter fullscreen mode Exit fullscreen mode

Using eyre with tracing

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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"}

Enter fullscreen mode Exit fullscreen mode

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)
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);
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)