rootcause 0.12.0: error reports with integrated tracing spans

52 points TethysSvensson 5 comments

Rootcause is a (relatively) new error handling library for Rust focused on ergonomics, inspectability, and the flexibility to use it however you want. Today I have released version 0.12.0, which includes rootcause-tracing to automatically capture tracing spans when creating a rootcause error report.

If you like anyhow but wish your errors carried more structured information and better diagnostics, this might be interesting to you.

Status update

Real-world validation

Since my last post in December, I have been porting most of the Rust code at my workplace to rootcause. This has allowed me to get a lot of first-hand experience using rootcause. Other people also appear to have slowly started using it as well, at least according to our download counts on crates.io and the number of users on GitHub.

rootcause-tracing

The biggest change since my last post is the new crate for integrating with the tracing crate #102. I expect many users will prefer this over the existing rootcause-backtrace crate, though I also expect many will want to use both.

This new crate allows tracing spans to be captured and shown as part of the error report. This is hard to explain in words, but very obvious when you see it. For example, suppose you have this code:

#[tracing::instrument(fields(query, table))]
fn query_database(_query: &str, _table: &str) -> Result<String, Report<DatabaseError>> {
    // Example code: We always fail here
    Err(report!(DatabaseError))?
}

#[instrument(fields(user_id, role))]
fn check_user_permission(_user_id: u64, _role: &str) -> Result<(), Report<PermissionError>> {
    query_database("SELECT permissions FROM users WHERE id = ?", "users")
        .attach("Failed to fetch user permissions")
        .context(PermissionError)?;

    Ok(())
}

With the right setup, this could result in a report such as this:

 ● permission denied
 ├ rootcause-tracing/examples/tracing_spans.rs:36
 ├ Tracing spans:
 │ │ check_user_permission{_user_id=12345 _role="admin"}
 │ │ handle_api_request{_request_id="req-abc-123" _endpoint="/api/admin/users"}
 │ ╰─
 ├ User lacks required permissions
 │
 ● database query failed
 ├ rootcause-tracing/examples/tracing_spans.rs:29
 ├ Tracing spans:
 │ │ query_database{_query="SELECT permissions FROM users WHERE id = ?" _table="users"}
 │ │ check_user_permission{_user_id=12345 _role="admin"}
 │ │ handle_api_request{_request_id="req-abc-123" _endpoint="/api/admin/users"}
 │ ╰─
 ╰ Failed to fetch user permissions

See the full example for details.

Small incremental improvements

Getting real-world data has also allowed me and other contributors to notice a bunch of minor points of friction. A lot of the work in this release has been small fixes to mitigate these issues:

  • We added an extension trait to make it easier to work with Option types #92
  • We added the type alias rootcause::Result #91
  • We added support for formatting the error sources for a context #94 example
  • We added support for mutating attachments #113
  • Contexts and attachments will by default use the same formatter (Display/Debug) as the one used to format the report #116
  • We fixed rootcause-backtrace so it works on Windows #118 and when cross-compiling #121

Next steps

A rootcause book

My biggest priority at the moment is to write a small mdbook. This is partly to document rootcause, but also to propose concrete solutions to some of the error handling discussions we've seen on /r/rust recently 1 2.

Towards a 1.0 release

I am planning to release version 1.0 within the next 3 months. I think that goal is fairly realistic, since my biggest concern was getting enough real-world validation. I think we have more or less accomplished that.

I am also finished with all of the major features I had planned. The only breaking change I am currently planning is to use a builder pattern for a few of the structs. This is to make it easier to change them in the future without requiring a new major version.

Questions / Discussion

Feel free to ask any questions you want about rootcause or my opinions on error handling here. You're also more than welcome to join our Discord.