A Rust error tracking library that captures error propagation with file locations, and optional timestamps, and contextual messages.
Try the runnable example:
cargo run --example getting_startedHere's a realistic example of using TrackErr to track errors through multiple layers of an application:
use std::fs;
use std::path::Path;
use trackerr::{ErrorExt, Result, ResultExt};
// Define error types however preferred (using thiserror for convenience)
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
#[error("Failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid config format: missing required field '{0}'")]
InvalidFormat(String),
}
#[derive(thiserror::Error, Debug)]
pub enum DatabaseError {
#[error("Connection failed: {0}")]
Connection(String),
#[error("Query failed: {0}")]
Query(String),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
}
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] DatabaseError),
}
fn load_config(path: &Path) -> Result<String, ConfigError> {
// Convert std::io::Error to ConfigError and track the location
let content = fs::read_to_string(path)
.map_err(|e| ConfigError::from(e).track().msg("Reading database config"))?;
// Validate config format
if !content.contains("host") {
Err(ConfigError::InvalidFormat("host".into()).track())?
}
Ok(content)
}
fn connect_database(config_path: &Path) -> Result<(), DatabaseError> {
// .track() converts between error types and captures the location
let config = load_config(config_path)
.track()
.ts()?; // Add timestamp to the error frame
if config.trim().is_empty() {
// Track new errors with .track()
Err(DatabaseError::Connection("Empty configuration".into()).track())?
}
Ok(())
}
fn run_app() -> Result<(), AppError> {
connect_database(Path::new("config.json"))
.track()
.msg("Initializing application database")?;
Ok(())
}
fn main() {
match run_app() {
Ok(_) => println!("Application started successfully"),
Err(e) => {
// Simple display - just the error message
eprintln!("Error: {}", e);
// Detailed display - full error chain with locations and context
eprintln!("\nFull error trace:\n{:#}", e);
}
}
}Example output when an error occurs:
Error: Database error: Configuration error: Failed to read config file: No such file or directory (os error 2)
Full error trace:
Database error: Configuration error: Failed to read config file: No such file or directory (os error 2)
@ src/main.rs:65:10
Initializing application database
Configuration error: Failed to read config file: No such file or directory (os error 2)
@ src/main.rs:50:10
2026-02-07T10:30:45Z
Failed to read config file: No such file or directory (os error 2)
@ src/main.rs:37:43
Reading database config
No such file or directory (os error 2)
@ UNKNOWN:0:0
Add .track() to any error to capture its location:
.map_err(|e| e.track()) // Capture location when error occursMethods for Result<T, Error<E>>:
.track()- Propagate error, convert type, and capture location.ts()- Add timestamp to the current frame.msg(message)- Add contextual message to the current frame
some_result
.track() // Convert error type and capture location
.ts() // Add timestamp
.msg("context info")?; // Add message- Normal:
format!("{}", error)- Shows only the error message - Alternate:
format!("{:#}", error)- Shows full error chain with locations, timestamps, and messages
fn level_3() -> Result<(), IoError> {
file_operation().track()? // Frame 1: captured here
}
fn level_2() -> Result<(), ParseError> {
level_3().track().msg("parsing data")? // Frame 2: captured here + message
}
fn level_1() -> Result<(), AppError> {
level_2().track().ts()? // Frame 3: captured here + timestamp
}When printed with {:#}, shows the complete call stack with location tracking.