Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I print a backtrace without panicking using thiserror?

I am running a Rust warp webserver and I need more descriptive error messages. I'd like to print a backtrace or something similar so I can tell where the error started.

I was using the Failure crate, but it is now deprecated so I migrated to thiserror.

Is it possible (without using nightly), to print a backtrace without panicking?

like image 244
Marty Avatar asked Nov 15 '22 11:11

Marty


1 Answers

There are ways to get to the backtrace information - but it relies on what are currently "nightly only" APIs. If you're happy to use nightly and stick with thiserror, here's what you do... (If not then see the ASIDE at the end for other ideas).

If you're creating the error from scratch the steps are:

  1. add a backtrace: Backtrace field to your error type.
  2. set the backtrace when you create an error using backtrace::force_captue()

If you're creating this error due to another error and want to use its backtrace instead, then just add #[backtrace] to the source field where you keep the original error.

Then to get the backtrace information you can just use the backtrace() function on std::Error.

An example looks like this:

#![feature(backtrace)]

extern crate thiserror;

use std::backtrace::Backtrace;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    //#[error("data store disconnected")]
    //Disconnect(#[from] io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader {
        expected: String,
        found: String,
        backtrace: Backtrace,
    },
    #[error("unknown data store error")]
    Unknown,
}

pub fn explode() -> Result<(), DataStoreError> {
    Err(DataStoreError::InvalidHeader {
        expected: "A".to_owned(),
        found: "B".to_owned(),
        backtrace: Backtrace::force_capture(),
    })
}

fn main() {
    use std::error::Error;
    let e = explode().err().unwrap();
    let b = e.backtrace();
    println!("e = {}", e);
    println!("b = {:#?}", b);
}

This outputs something like:

e = invalid header (expected "A", found "B")
b = Some(
    Backtrace [
        { fn: "playground::explode", file: "./src/main.rs", line: 28 },
        { fn: "playground::main", file: "./src/main.rs", line: 34 },
        { fn: "core::ops::function::FnOnce::call_once", file: "/rustc/879aff385a5fe0af78f3d45fd2f0b8762934e41e/library/core/src/ops/function.rs", line: 248 },
        ...

You can see a working version in the playground


ASIDE

If you're not tied to thiserror and need to use the stable version of the compiler, you could instead use snafu which has support for backtraces using the backtrace crate, rather than std::backtrace, and so works on stable.

There may also be ways to make thiserror work with the backtrace crate rather than std::backtrace but I've not tried that.

like image 103
Michael Anderson Avatar answered Dec 15 '22 00:12

Michael Anderson