# TrackErr
A Rust error tracking library that captures error propagation with file locations, and optional timestamps, and contextual messages.
## Getting Started
Try the runnable example:
```bash
cargo run --example getting_started
```
Here's a realistic example of using TrackErr to track errors through multiple layers of an application:
```rust
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
```
## API Overview
### ErrorExt Trait
Add `.track()` to any error to capture its location:
```rust
.map_err(|e| e.track()) // Capture location when error occurs
```
### ResultExt Trait
Methods 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
```rust
some_result
.track() // Convert error type and capture location
.ts() // Add timestamp
.msg("context info")?; // Add message
```
### Display Formats
- **Normal**: `format!("{}", error)` - Shows only the error message
- **Alternate**: `format!("{:#}", error)` - Shows full error chain with locations, timestamps, and messages
## Error Flow Example
```rust
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.