thistrace-macros 0.1.0

Proc-macros for the thistrace crate
Documentation
  • Coverage
  • 0%
    0 out of 2 items documented0 out of 1 items with examples
  • Size
  • Source code size: 22.25 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 303.81 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 9s Average build duration of successful builds.
  • all releases: 9s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • acutis33/thistrace-rs
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • acutis33

thistrace

thistrace adds callsite provenance (file/line/column) to thiserror enums without requiring map_err(...) at each callsite.

It works by generating #[track_caller] From<T> impls for #[from] conversions, so ? captures the location where the conversion happened.

What it solves

With plain thiserror, you often end up with an error chain but not where your code wrapped/bubbled the error.

thistrace captures a small “provenance frame” at each #[from] conversion boundary.

Usage

Add dependencies:

[dependencies]
thistrace = "0.1"
thiserror = "2"

In larger codebases, you can keep thistrace usage mostly confined to your error module via:

use thistrace::prelude::*;

Annotate your thiserror enum with #[traceable] (or #[thistrace::traceable]):

use std::io;
use thistrace::prelude::*;

#[traceable]
#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("io")]
    Io(#[from] io::Error),
}

fn inner() -> Result<(), io::Error> {
    Err(io::Error::new(io::ErrorKind::Other, "boom"))
}

fn outer() -> Result<(), AppError> {
    inner()?; // provenance captured here
    Ok(())
}

Print with provenance:

let err = outer().unwrap_err();
eprintln!("{}", thistrace::DisplayTrace::new(&err));

Capturing the initiation (origin) location

By default, #[traceable] captures the location where a #[from] conversion happened (the ? site).

If you also want the location where an error was initiated, wrap it with thistrace::origin(...) and use thistrace::Origin<_> as the #[from] source type:

use std::io;
use thistrace::{origin, traceable};

#[traceable]
#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("io")]
    Io(#[from] thistrace::Origin<io::Error>),
}

fn inner() -> Result<(), thistrace::Origin<io::Error>> {
    Err(origin(io::Error::new(io::ErrorKind::Other, "boom")))
}

fn outer() -> Result<(), AppError> {
    inner()?; // bubble frame
    Ok(())
}

The resulting trace includes both the origin frame and the bubble frame.

Logging: Display vs Debug

  • Display ({} / %err) prints the thiserror message only.
  • Debug ({:?} / ?err) will include the raw trace field for rewritten #[from] variants (because #[traceable] turns them into struct-like variants { source, trace }), but it is not formatted as a pretty trace.
  • thistrace::DisplayTrace is the recommended way to include a readable at file:line:col trace in logs.
  • thistrace::OneLineTrace provides a single-line string that includes the trace and cause chain (useful when your log system treats newlines poorly).

Logging: structured frames (Splunk-friendly)

If you log to Splunk/ELK/etc, prefer structured fields (often via tracing + JSON output) and log the frames as a field:

use thistrace::{HasTrace, trace_frames};

tracing::error!(
    error = %err,
    trace = ?trace_frames(&err),
    "request failed"
);

Logging: common tracing patterns

One-line error string (good default):

tracing::error!(
    error = %thistrace::OneLineTrace::new(&err),
    "request failed"
);

Human-readable multi-line trace (nice in dev logs):

tracing::error!(
    error = %thistrace::DisplayTrace::new(&err),
    "request failed"
);

Example output:

io
  at src/config.rs:42:13
  at src/main.rs:18:5

caused by: No such file or directory (os error 2)

Both structured frames and one-line text:

tracing::error!(
    error = %err,
    trace = ?thistrace::trace_frames(&err),
    error_trace = %thistrace::OneLineTrace::new(&err),
    "request failed"
);

Capturing a bubble frame without core::ops::function noise

If you want to append a “bubble” frame using map_err, prefer a closure (or the helper macro) instead of passing thistrace::bubble directly. This ensures the captured location is your callsite:

// Good: captures this line in your code
result.map_err(thistrace::bubble_err!())?;

// Also good (equivalent)
result.map_err(|e| thistrace::bubble(e))?;

// Avoid: can capture inside core::ops::function.rs
// result.map_err(thistrace::bubble)?; // or bubble_any

If you want to bubble and convert back into your own error type in one step, use:

result.map_err(thistrace::bubble_into!(MyError))?;

If you're already returning Result<_, thistrace::Bubbled<MyError>>, use rebubble_err!() to append another frame without producing Bubbled<Bubbled<_>>:

result.map_err(thistrace::rebubble_err!())?;

Filling non-Default context fields (callsite helper)

If your error variant has extra fields that you want to fill at the callsite (instead of using Default::default()), use from_with_trace!:

from_with_trace!(
    some_fallible_call(),
    MyError::Io { path: my_path.clone() }
)?;

To set a field to Default::default() explicitly, use _:

from_with_trace!(
    some_fallible_call(),
    MyError::Io { path: _ }
)?;

Limitations

  • Only captures at conversion boundaries: If you ? the same error type upward (no From conversion), there is no stable way to auto-capture without an explicit wrapper step.
  • Limited #[from] shapes: supports tuple variants with exactly one #[from] field (e.g. Io(#[from] io::Error)), tuple variants with extra context fields (e.g. Io(#[from] io::Error, PathBuf)), and struct variants with a single #[from] field (e.g. Io { #[from] source: io::Error, path: PathBuf }). Extra fields are initialized with Default::default().
  • Generics: supported for #[traceable] enums, including where clauses.
  • Non-Default context fields: use from_with_trace!(...) to fill fields at the callsite while still capturing a trace frame. You can also use _ to set a field to Default::default().

License

Licensed under either of

  • Apache License, Version 2.0 (LICENSE-APACHE)
  • MIT license (LICENSE-MIT)

at your option.