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:
[]
= "0.1"
= "2"
In larger codebases, you can keep thistrace usage mostly confined to your error module via:
use *;
Annotate your thiserror enum with #[traceable] (or #[thistrace::traceable]):
use io;
use *;
Print with provenance:
let err = outer.unwrap_err;
eprintln!;
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 io;
use ;
The resulting trace includes both the origin frame and the bubble frame.
Logging: Display vs Debug
Display({}/%err) prints thethiserrormessage only.Debug({:?}/?err) will include the rawtracefield for rewritten#[from]variants (because#[traceable]turns them into struct-like variants{ source, trace }), but it is not formatted as a pretty trace.thistrace::DisplayTraceis the recommended way to include a readableat file:line:coltrace in logs.thistrace::OneLineTraceprovides 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 ;
error!;
Logging: common tracing patterns
One-line error string (good default):
error!;
Human-readable multi-line trace (nice in dev logs):
error!;
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:
error!;
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?;
// Also good (equivalent)
result.map_err?;
// 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?;
If you're already returning Result<_, thistrace::Bubbled<MyError>>, use rebubble_err!() to
append another frame without producing Bubbled<Bubbled<_>>:
result.map_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!?;
To set a field to Default::default() explicitly, use _:
from_with_trace!?;
Limitations
- Only captures at conversion boundaries: If you
?the same error type upward (noFromconversion), 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 withDefault::default(). - Generics: supported for
#[traceable]enums, includingwhereclauses. - Non-
Defaultcontext fields: usefrom_with_trace!(...)to fill fields at the callsite while still capturing a trace frame. You can also use_to set a field toDefault::default().
License
Licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)
at your option.