charon-error 0.4.0

Structured error and panic handling with rich reports and bug reporting functionality
Documentation

charon-error

Crates.io Rust Docs License License

Structured error handling for Rust with rich error reports, data sensitivity labels, panic capture, and GitLab issue integration.

Named after Charon, the Greek ferryman of the dead across the river Styx. This crate ferries your program from a running state to a graceful death, collecting every detail along the way.

Features

  • Error chains — Stack multiple errors with ErrorReport to preserve the full context from root cause to top-level failure
  • Panic hooks — Capture panics and display human-readable error messages with setup_panic! and setup_panic_simple!
  • Issue submission — Generate pre-filled GitLab issue URLs directly from errors so users can report bugs with few clicks
  • Sensitivity labels — Tag data as Public, Private, Internal, or Confidential to control what information gets shared in reports
  • Tracing integration — Works with the tracing crate. Use #[instrument] and span traces are captured automatically in error frames
  • Flexible formatting — Configure output detail level (Compact, Medium, Full, Debug) and indentation style

Example Output

PANIC: A panic occurred during execution.
* Message: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`'
* Path: 'src/main.rs:58:21'
PANIC: The application encountered an error it could not recover from.
{
  last_error: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`',
  unique_id: 'IpRUIEGnbEh1r58c14jc',
  frames: [
    {
      message: 'No such file or directory (os error 2)',
      source_location: src/cli/config.rs:15:10,
      span_trace: [
        charon_demo::cli::config::read_config at (src/cli/config.rs:7:0),
        charon_demo::cli::start_server at (src/cli.rs:82:0),
        charon_demo::cli::start at (src/cli.rs:35:0),
      ],
      attachments: {},
      date_time: 2026-02-27T21:56:38.787453133+00:00,
    },
    {
      message: 'The config file failed to load correctly.',
      source_location: src/cli/config.rs:15:10,
      span_trace: [
        charon_demo::cli::config::read_config at (src/cli/config.rs:7:0),
        charon_demo::cli::start_server at (src/cli.rs:82:0),
        charon_demo::cli::start at (src/cli.rs:35:0),
      ],
      attachments: {
        file_path: '../config.toml',
      },
      date_time: 2026-02-27T21:56:38.787580742+00:00,
    },
    {
      message: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`',
      source_location: src/main.rs:58:21,
      span_trace: [],
      attachments: {},
      date_time: 2026-02-27T21:56:38.787617912+00:00,
    },
  ],
}
┌──────────────────────────┐
│  Open/Submit Bug Report │
└──────────────────────────┘

NOTE: For span_trace to be filled in, you need to register a tracing::Subscriber This can be done though the tracing-subscriber crate or by using charon-logging.

PANIC: A panic occurred during execution.
* Message: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`'
* Path: 'src/main.rs:58:21'
PANIC: The application encountered an error it could not recover from.
If you report this we might be able to fix this in the future.
Error: `Called `ResultExt::unwrap_error()` on an `Err` value`
Panic Info:
## Summary:
```hcl
{
last_error: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`',
unique_id: 'zyzIoan0VCBur1AxeQZx',
frames: [
{
	message: 'No such file or directory (os error 2)',
	source_location: src/cli/config.rs:15:10,
	span_trace: [
		charon_demo::cli::config::read_config at (src/cli/config.rs:7:0),
		charon_demo::cli::start_server at (src/cli.rs:82:0),
		charon_demo::cli::start at (src/cli.rs:35:0),
	],
	attachments: {},
},
{
	message: 'The config file failed to load correctly.',
	source_location: src/cli/config.rs:15:10,
	span_trace: [
		charon_demo::cli::config::read_config at (src/cli/config.rs:7:0),
		charon_demo::cli::start_server at (src/cli.rs:82:0),
		charon_demo::cli::start at (src/cli.rs:35:0),
	],
	attachments: {
		file_path: '../config.toml',
	},
},
{
	message: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`',
	source_location: src/main.rs:58:21,
	span_trace: [],
	attachments: {},
},
],
}
```

## Debug info:
### Backtrace:
```
1: ErrorFrame::new
2: ErrorReport::push_error
3: charon_demo::main
4: backtrace::__rust_begin_short_backtrace
5: lang_start::{{closure}}
6: rt::lang_start_internal
7: main
8: __libc_start_call_main => ./csu/../sysdeps/nptl/libc_start_call_main.h:58
9: __libc_start_main_impl => ./csu/../csu/libc-start.c:360
10: _start

```

### System:
* Application version: 0.3.0
* System: linux x86_64

Please report this error. Issue title: `Error: `Called `ResultExt::unwrap_error()` on an `Err` value``
	- Step 1: Check if error report already exists: https://gitlab.com/%3Cproject_path%3E/%3Cproject_name%3E/-/issues/?sort=created_date&state=all&in=TITLE&search=Error%3A+%60Called+%60ResultExt%3A%3Aunwrap_error%28%29%60+on+an+%60Err%60+value%60
	- Step 2: Report issue (if it does not already exists):
	- Open Pre-created Report Link: 
---
https://gitlab.com/%3Cproject_path%3E/%3Cproject_name%3E/issues/new?...
```

Panic Output in Debug Panic Output in Release

Installation

Add to your Cargo.toml:

[dependencies]
charon-error = "0.3"

Quick Start

use charon_error::prelude::*;

#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("failed to load configuration")]
    ConfigLoad,
    #[error("failed to start server")]
    ServerStart,
}

#[instrument]
fn load_config() -> ResultER<String> {
    std::fs::read_to_string("config.toml")
        .change_context(AppError::ConfigLoad)
}

#[instrument]
fn start_server() -> ResultER<()> {
    let _config = load_config()
        .change_context(AppError::ServerStart)?;
    Ok(())
}

ResultER<T> is a type alias for Result<T, ErrorReport>. The ResultExt trait (imported via the prelude) adds .change_context(), .error_attach(), and .unwrap_error() to any Result or Option.

Error Context and Attachments

Adding Context

Use .change_context() to wrap errors with higher-level meaning:

use charon_error::prelude::*;

#[instrument]
fn read_user(id: u64) -> ResultER<String> {
    std::fs::read_to_string(format!("users/{id}.json"))
        .change_context(StringError::new("failed to read user"))
}

Attaching Data

Attach diagnostic data to errors with sensitivity labels:

use charon_error::prelude::*;

#[instrument]
fn process_order(order_id: &str) -> ResultER<()> {
    do_work()
        .error_attach_public_string("order_id", order_id.to_owned())?;
    Ok(())
}

fn do_work() -> ResultER<()> { Ok(()) }

Sensitivity Labels

ErrorSensitivityLabel controls who can see attached data:

Level Data Owner Other Users Internal Encrypted
Public yes yes yes no
Private yes no yes no
PrivateConfidential yes no no yes
Internal no no yes no
Confidential no no yes yes
HighlyConfidential no no yes yes

Note: Encryption is not implemented yet.

Panic Hooks

Full Panic Hook with Issue Submission

use charon_error::prelude::*;
use charon_error::prelude::gitlab_er::*;

#[allow(dead_code)]
fn check_if_common_errors(error_message: &str) -> Option<String> {
    // use colored::*;
    match error_message {
        "Address/Port binding error." => {
            Some("We got a Address/Port binding error.\n\
            You most likely already have the application already open somewhere. \
            Because the port is already in use. If this is not the case an other app \
            is using this port. You can change the port in the config file.\n\
            Change the port `config.json` -> server -> port.\n\
            Change the value to 20001 and try again.".to_owned())
        },
        _ => {
            None
        },
    }
}

fn main() -> ResultER<()> {
    // Configure global error report settings
    ERGlobalSettings::set_global_settings(ERGlobalSettings {
        check_known_error_types_fn: check_if_common_errors,
        submit_error_reporter_fn: |er| Box::new(GitLabErrorReport::new(er)),
        ..Default::default()
    })
    .unwrap_error();

    // Configure GitLab integration
    GitLabERGlobalSettings::set_global_settings(GitLabERGlobalSettings {
        domain: "gitlab.com".to_owned(),
        project_path: "my-group/my-project".to_owned(),
        labels: vec!["bug".to_owned(), "auto-generated".to_owned()],
        url_char_limit: 6000,
        ..Default::default()
    })
    .unwrap_error();

    // Install panic hook
    setup_panic!();

    // Your application code here
    Ok(())
}

When a panic occurs:

  • Known errors: Displays a friendly message from check_if_common_errors
  • Unknown errors (debug build): Shows the full error report with a clickable link to create a GitLab issue
  • Unknown errors (release build): Shows a user-friendly message with report instructions

Simple Panic Hook

For applications that do not need issue submission:

use charon_error::prelude::*;

fn main() {
    setup_panic_simple!();

    // Your application code here
}

This panic hook is mainly for when the more full featured panic hook is to much and just want to setup a simple hook. This hook can also be used as a fallback panic hook. Output is much simpler and faster, for example:

PANIC: The application encountered an error it could not recover from.
* Message: 'Error: `Called `ResultExt::unwrap_error()` on an `Err` value`'
* Path: 'src/main.rs:72:21'

Tracing Integration

charon-error integrates with the tracing crate. Annotate functions with #[instrument] and span traces are automatically captured in each error frame:

use charon_error::prelude::*;

#[instrument]
fn process_request(user_id: u64) -> ResultER<()> {
    validate_user(user_id)?;
    Ok(())
}

#[instrument]
fn validate_user(id: u64) -> ResultER<()> {
    Err(StringError::new("user not found"))?
}

The error report will include the span trace showing the call chain through process_request and validate_user.

Roadmap

  • GitHub issue submission support
  • Encryption for Confidential and HighlyConfidential data
  • Configurable global formatting settings

Use of AI in this project

This project was hand written before AI. We will be using AI to assist in development. However ALL code is manually reviewed and improve. Example uses of AI: documentation, verification, testing, as double check, ...


License

The code in this project is licensed under the MIT or Apache 2.0 license.

All contributions, code and documentation, to this project will be similarly licensed.