Merge branch 'master' into fix-ambiguous-methods

This commit is contained in:
Freja Roberts 2024-03-28 22:01:37 +01:00 committed by GitHub
commit 2b32acdd03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 4918 additions and 19 deletions

View File

@ -44,9 +44,9 @@ jobs:
features:
- # default
- --no-default-features
- --features track-caller
- --no-default-features --features track-caller
- --no-default-features --features auto-install
- --features pyo3
- --features auto-install
- --all-features
steps:
- uses: actions/checkout@v1
@ -67,8 +67,8 @@ jobs:
features:
- # default
- --no-default-features
- --features track-caller
- --features auto-install
- --no-default-features --features track-caller
- --no-default-features --features auto-install
# skip `--features pyo3` and `--all-features` because pyo3 doesn't support this msrv
steps:
- uses: actions/checkout@v1
@ -136,6 +136,7 @@ jobs:
with:
command: clippy
args: --all-targets --all-features -- -D warnings
miri:
name: Miri
runs-on: ubuntu-latest

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"color-eyre",
"color-spantrace",
"eyre"
]
@ -15,3 +16,9 @@ rust-version = "1.65.0"
[workspace.dependencies]
indenter = "0.3.0"
once_cell = "1.18.0"
owo-colors = "3.2.0"
[profile.dev.package.backtrace]
opt-level = 3

View File

@ -1,13 +1,16 @@
eyre
====
# eyre
[![Build Status][actions-badge]][actions-url]
[![Latest Version](https://img.shields.io/crates/v/eyre.svg)](https://crates.io/crates/eyre)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/eyre)
[![Latest Version][version-badge]][version-url]
[![Rust Documentation][docs-badge]][docs-url]
[![Discord chat][discord-badge]][discord-url]
[actions-badge]: https://github.com/eyre-rs/eyre/workflows/Continuous%20integration/badge.svg
[actions-url]: https://github.com/eyre-rs/eyre/actions?query=workflow%3A%22Continuous+integration%22
[version-badge]: https://img.shields.io/crates/v/eyre.svg
[version-url]: https://crates.io/crates/eyre
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-url]: https://docs.rs/eyre
[discord-badge]: https://img.shields.io/discord/960645145018110012?label=eyre%20community%20discord
[discord-url]: https://discord.gg/z94RqmUTKB
@ -255,10 +258,10 @@ implements `context` for options which you can import to make existing
[`anyhow`]: https://github.com/dtolnay/anyhow
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
[`stable-eyre`]: https://github.com/eyre-rs/stable-eyre
[`color-eyre`]: https://github.com/eyre-rs/color-eyre
[`color-eyre`]: https://github.com/eyre-rs/eyre/tree/master/color-eyre
[`jane-eyre`]: https://github.com/yaahc/jane-eyre
[`simple-eyre`]: https://github.com/eyre-rs/simple-eyre
[`color-spantrace`]: https://github.com/eyre-rs/color-spantrace
[`color-spantrace`]: https://github.com/eyre-rs/eyre/tree/master/color-spantrace
[`color-backtrace`]: https://github.com/athre0z/color-backtrace
[^1]: example and explanation of breakage https://github.com/eyre-rs/eyre/issues/30#issuecomment-647650361

135
color-eyre/.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,135 @@
on:
push:
branches:
- master
pull_request: {}
name: Continuous integration
jobs:
check:
name: Check
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
with:
command: check
test-features:
name: Test Features
runs-on: ubuntu-latest
strategy:
matrix:
features:
-
- --all-features
- --no-default-features
- --no-default-features --features issue-url
- --no-default-features --features capture-spantrace
- --no-default-features --features track-caller
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
args: ${{ matrix.features }}
test-versions:
name: Test Versions
runs-on: ubuntu-latest
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- wasm32-unknown-unknown
rust:
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
target: ${{ matrix.target }}
toolchain: ${{ matrix.rust }}
override: true
- name: install test runner for wasm
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
if: ${{ matrix.target == 'wasm32-unknown-unknown' }}
- uses: actions-rs/cargo@v1
with:
command: test
target: ${{ matrix.target }}
toolchain: ${{ matrix.rust }}
if: ${{ matrix.target != 'wasm32-unknown-unknown' }}
- name: run wasm tests
run: wasm-pack test --node
if: ${{ matrix.target == 'wasm32-unknown-unknown' }}
test-os:
name: Test Operating Systems
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
with:
command: test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings

88
color-eyre/CHANGELOG.md Normal file
View File

@ -0,0 +1,88 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!-- next-header -->
## [Unreleased] - ReleaseDate
## [0.6.2] - 2022-07-11
### Added
- Option to disable display of location section in error reports
## [0.6.1] - 2022-02-24
### Changed
- Collapsed backtrace help text into fewer lines
## [0.6.0] - 2022-01-12
### Changed
- Updated dependencies to match newest tracing versions
## [0.5.11] - 2021-04-13
## [0.5.10] - 2020-12-02
### Added
- Support custom themes
## [0.5.9] - 2020-12-02
### Fixed
- Bumped color-spantrace dependency version to fix a panic
## [0.5.8] - 2020-11-23
### Added
- Exposed internal interfaces for the panic handler so that it can be wrapped
by consumers to customize the behaviour of the panic hook.
## [0.5.7] - 2020-11-05
### Fixed
- Added missing `cfg`s that caused compiler errors when only enabling the
`issue-url` feature
## [0.5.6] - 2020-10-02
### Added
- Add support for track caller added in eyre 0.6.1 and print original
callsites of errors in all `eyre::Reports` by default
## [0.5.5] - 2020-09-21
### Added
- add `issue_filter` method to `HookBuilder` for disabling issue generation
based on the error encountered.
## [0.5.4] - 2020-09-17
### Added
- Add new "issue-url" feature for generating issue creation links in error
reports pre-populated with information about the error
## [0.5.3] - 2020-09-14
### Added
- add `panic_section` method to `HookBuilder` for overriding the printer for
the panic message at the start of panic reports
## [0.5.2] - 2020-08-31
### Added
- make it so all `Section` trait methods can be called on `Report` in
addition to the already supported usage on `Result<T, E: Into<Report>>`
- panic_section to `HookBuilder` to add custom sections to panic reports
- display_env_section to `HookBuilder` to disable the output indicating what
environment variables can be set to manipulate the error reports
### Changed
- switched from ansi_term to owo-colors for colorizing output, allowing for
better compatibility with the Display trait
<!-- next-url -->
[Unreleased]: https://github.com/eyre-rs/color-eyre/compare/v0.6.2...HEAD
[0.6.2]: https://github.com/eyre-rs/color-eyre/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/eyre-rs/color-eyre/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/eyre-rs/color-eyre/compare/v0.5.11...v0.6.0
[0.5.11]: https://github.com/eyre-rs/color-eyre/compare/v0.5.10...v0.5.11
[0.5.10]: https://github.com/eyre-rs/color-eyre/compare/v0.5.9...v0.5.10
[0.5.9]: https://github.com/eyre-rs/color-eyre/compare/v0.5.8...v0.5.9
[0.5.8]: https://github.com/eyre-rs/color-eyre/compare/v0.5.7...v0.5.8
[0.5.7]: https://github.com/eyre-rs/color-eyre/compare/v0.5.6...v0.5.7
[0.5.6]: https://github.com/eyre-rs/color-eyre/compare/v0.5.5...v0.5.6
[0.5.5]: https://github.com/eyre-rs/color-eyre/compare/v0.5.4...v0.5.5
[0.5.4]: https://github.com/eyre-rs/color-eyre/compare/v0.5.3...v0.5.4
[0.5.3]: https://github.com/eyre-rs/color-eyre/compare/v0.5.2...v0.5.3
[0.5.2]: https://github.com/eyre-rs/color-eyre/releases/tag/v0.5.2

83
color-eyre/Cargo.toml Normal file
View File

@ -0,0 +1,83 @@
[package]
name = "color-eyre"
version = "0.6.2"
description = "An error report handler for panics and eyre::Reports for colorful, consistent, and well formatted error reports for all kinds of errors."
documentation = "https://docs.rs/color-eyre"
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
readme = { workspace = true }
rust-version = { workspace = true }
[features]
default = ["track-caller", "capture-spantrace"]
capture-spantrace = ["tracing-error", "color-spantrace"]
issue-url = ["url"]
track-caller = []
[dependencies]
eyre = "0.6.1"
tracing-error = { version = "0.2.0", optional = true }
backtrace = { version = "0.3.48", features = ["gimli-symbolize"] }
indenter = { workspace = true }
owo-colors = { workspace = true }
color-spantrace = { version = "0.2", optional = true }
once_cell = { workspace = true }
url = { version = "2.1.1", optional = true }
[dev-dependencies]
tracing-subscriber = { version = "0.3.0", features = ["env-filter"] }
tracing = "0.1.13"
pretty_assertions = "1.0.0"
thiserror = "1.0.19"
ansi-parser = "0.8.0"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.15"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.release]
dev-version = false
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "Unreleased"
replace="{{version}}"
[[package.metadata.release.pre-release-replacements]]
file = "src/lib.rs"
search = "#!\\[doc\\(html_root_url.*"
replace = "#![doc(html_root_url = \"https://docs.rs/{{crate_name}}/{{version}}\")]"
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "\\.\\.\\.HEAD"
replace="...{{tag_name}}"
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "ReleaseDate"
replace="{{date}}"
[[package.metadata.release.pre-release-replacements]]
file="CHANGELOG.md"
search="<!-- next-header -->"
replace="<!-- next-header -->\n\n## [Unreleased] - ReleaseDate"
exactly=1
[[package.metadata.release.pre-release-replacements]]
file="CHANGELOG.md"
search="<!-- next-url -->"
replace="<!-- next-url -->\n[Unreleased]: https://github.com/eyre-rs/{{crate_name}}/compare/{{tag_name}}...HEAD"
exactly=1
[[example]]
name = "color-eyre-usage"
path = "examples/usage.rs"

1
color-eyre/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
color-eyre/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

231
color-eyre/README.md Normal file
View File

@ -0,0 +1,231 @@
# color-eyre
[![Build Status][actions-badge]][actions-url]
[![Latest Version][version-badge]][version-url]
[![Rust Documentation][docs-badge]][docs-url]
[actions-badge]: https://github.com/eyre-rs/eyre/workflows/Continuous%20integration/badge.svg
[actions-url]: https://github.com/eyre-rs/eyre/actions?query=workflow%3A%22Continuous+integration%22
[version-badge]: https://img.shields.io/crates/v/color-eyre.svg
[version-url]: https://crates.io/crates/color-eyre
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-url]: https://docs.rs/color-eyre
An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
formatted error reports for all kinds of errors.
## TLDR
`color_eyre` helps you build error reports that look like this:
![custom section example](./pictures/custom_section.png)
## Setup
Add the following to your toml file:
```toml
[dependencies]
color-eyre = "0.6"
```
And install the panic and error report handlers:
```rust
use color_eyre::eyre::Result;
fn main() -> Result<()> {
color_eyre::install()?;
// ...
# Ok(())
}
```
### Disabling tracing support
If you don't plan on using `tracing_error` and `SpanTrace` you can disable the
tracing integration to cut down on unused dependencies:
```toml
[dependencies]
color-eyre = { version = "0.6", default-features = false }
```
### Disabling SpanTrace capture by default
color-eyre defaults to capturing span traces. This is because `SpanTrace`
capture is significantly cheaper than `Backtrace` capture. However, like
backtraces, span traces are most useful for debugging applications, and it's
not uncommon to want to disable span trace capture by default to keep noise out
developer.
To disable span trace capture you must explicitly set one of the env variables
that regulate `SpanTrace` capture to `"0"`:
```rust
if std::env::var("RUST_SPANTRACE").is_err() {
std::env::set_var("RUST_SPANTRACE", "0");
}
```
### Improving perf on debug builds
In debug mode `color-eyre` behaves noticably worse than `eyre`. This is caused
by the fact that `eyre` uses `std::backtrace::Backtrace` instead of
`backtrace::Backtrace`. The std version of backtrace is precompiled with
optimizations, this means that whether or not you're in debug mode doesn't
matter much for how expensive backtrace capture is, it will always be in the
10s of milliseconds to capture. A debug version of `backtrace::Backtrace`
however isn't so lucky, and can take an order of magnitude more time to capture
a backtrace compared to its std counterpart.
Cargo [profile
overrides](https://doc.rust-lang.org/cargo/reference/profiles.html#overrides)
can be used to mitigate this problem. By configuring your project to always
build `backtrace` with optimizations you should get the same performance from
`color-eyre` that you're used to with `eyre`. To do so add the following to
your Cargo.toml:
```toml
[profile.dev.package.backtrace]
opt-level = 3
```
## Features
### Multiple report format verbosity levels
`color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
and `Backtrace`, minimal, short, and full. Take the below snippets of the output produced by [`examples/usage.rs`]:
---
Running `cargo run --example usage` without `RUST_LIB_BACKTRACE` set will produce a minimal
report like this:
![minimal report format](./pictures/minimal.png)
<br>
Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short
format, which additionally capture a [`backtrace::Backtrace`]:
![short report format](./pictures/short.png)
<br>
Finally, running `RUST_LIB_BACKTRACE=full cargo run --example usage` tells `color-eyre` to use
the full format, which in addition to the above will attempt to include source lines where the
error originated from, assuming it can find them on the disk.
![full report format](./pictures/full.png)
### Custom `Section`s for error reports via [`Section`] trait
The `section` module provides helpers for adding extra sections to error
reports. Sections are disinct from error messages and are displayed
independently from the chain of errors. Take this example of adding sections
to contain `stderr` and `stdout` from a failed command, taken from
[`examples/custom_section.rs`]:
```rust
use color_eyre::{eyre::eyre, SectionExt, Section, eyre::Report};
use std::process::Command;
use tracing::instrument;
trait Output {
fn output2(&mut self) -> Result<String, Report>;
}
impl Output for Command {
#[instrument]
fn output2(&mut self) -> Result<String, Report> {
let output = self.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(eyre!("cmd exited with non-zero status code"))
.with_section(move || stdout.trim().to_string().header("Stdout:"))
.with_section(move || stderr.trim().to_string().header("Stderr:"))
} else {
Ok(stdout.into())
}
}
}
```
---
Here we have an function that, if the command exits unsuccessfully, creates a
report indicating the failure and attaches two sections, one for `stdout` and
one for `stderr`.
Running `cargo run --example custom_section` shows us how these sections are
included in the output:
![custom section example](./pictures/custom_section.png)
Only the `Stderr:` section actually gets included. The `cat` command fails,
so stdout ends up being empty and is skipped in the final report. This gives
us a short and concise error report indicating exactly what was attempted and
how it failed.
### Aggregating multiple errors into one report
It's not uncommon for programs like batched task runners or parsers to want
to return an error with multiple sources. The current version of the error
trait does not support this use case very well, though there is [work being
done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
For now however one way to work around this is to compose errors outside the
error trait. `color-eyre` supports such composition in its error reports via
the `Section` trait.
For an example of how to aggregate errors check out [`examples/multiple_errors.rs`].
### Custom configuration for `color-backtrace` for setting custom filters and more
The pretty printing for backtraces and span traces isn't actually provided by
`color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
[`color-spantrace`]. `color-backtrace` in particular has many more features
than are exported by `color-eyre`, such as customized color schemes, panic
hooks, and custom frame filters. The custom frame filters are particularly
useful when combined with `color-eyre`, so to enable their usage we provide
the `install` fn for setting up a custom `BacktracePrinter` with custom
filters installed.
For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
[`eyre`]: https://docs.rs/eyre
[`tracing-error`]: https://docs.rs/tracing-error
[`color-backtrace`]: https://docs.rs/color-backtrace
[`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html
[`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
[`color-spantrace`]: https://github.com/eyre-rs/eyre/tree/master/color-spantrace
[`Section`]: https://docs.rs/color-eyre/*/color_eyre/section/trait.Section.html
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
[`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
[`Handler`]: https://docs.rs/color-eyre/*/color_eyre/struct.Handler.html
[`examples/usage.rs`]: https://github.com/eyre-rs/color-eyre/blob/master/examples/usage.rs
[`examples/custom_filter.rs`]: https://github.com/eyre-rs/eyre/tree/master/color-eyre/blob/master/examples/custom_filter.rs
[`examples/custom_section.rs`]: https://github.com/eyre-rs/eyre/tree/master/color-eyre/blob/master/examples/custom_section.rs
[`examples/multiple_errors.rs`]: https://github.com/eyre-rs/eyre/tree/master/color-eyre/blob/master/examples/multiple_errors.rs
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@ -0,0 +1,61 @@
use color_eyre::{eyre::Report, eyre::WrapErr, Section};
use tracing::{info, instrument};
#[instrument]
fn main() -> Result<(), Report> {
std::env::set_var("RUST_BACKTRACE", "1");
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::config::HookBuilder::default()
.add_frame_filter(Box::new(|frames| {
let filters = &["custom_filter::main"];
frames.retain(|frame| {
!filters.iter().any(|f| {
let name = if let Some(name) = frame.name.as_ref() {
name.as_str()
} else {
return true;
};
name.starts_with(f)
})
});
}))
.install()
.unwrap();
read_config()
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) -> Result<(), Report> {
info!("Reading file");
Ok(std::fs::read_to_string(path).map(drop)?)
}
#[instrument]
fn read_config() -> Result<(), Report> {
read_file("fake_file")
.wrap_err("Unable to read config")
.suggestion("try using a file that exists next time")
}

View File

@ -0,0 +1,68 @@
use color_eyre::{
eyre::Report,
eyre::{eyre, WrapErr},
Section, SectionExt,
};
use std::process::Command;
use tracing::instrument;
trait Output {
fn output2(&mut self) -> Result<String, Report>;
}
impl Output for Command {
#[instrument]
fn output2(&mut self) -> Result<String, Report> {
let output = self.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(eyre!("cmd exited with non-zero status code"))
.with_section(move || stdout.trim().to_string().header("Stdout:"))
.with_section(move || stderr.trim().to_string().header("Stderr:"))
} else {
Ok(stdout.into())
}
}
}
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::install()?;
read_config().map(drop)
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) -> Result<String, Report> {
Command::new("cat").arg(path).output2()
}
#[instrument]
fn read_config() -> Result<String, Report> {
read_file("fake_file")
.wrap_err("Unable to read config")
.suggestion("try using a file that exists next time")
}

View File

@ -0,0 +1,56 @@
//! example for manually testing the perf of color-eyre in debug vs release
use color_eyre::{
eyre::Report,
eyre::{eyre, WrapErr},
Section,
};
use tracing::instrument;
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::install()?;
time_report();
Ok(())
}
#[instrument]
fn time_report() {
time_report_inner()
}
#[instrument]
fn time_report_inner() {
let start = std::time::Instant::now();
let report = Err::<(), Report>(eyre!("fake error"))
.wrap_err("wrapped error")
.suggestion("try using a file that exists next time")
.unwrap_err();
println!("Error: {:?}", report);
drop(report);
let end = std::time::Instant::now();
dbg!(end - start);
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}

View File

@ -0,0 +1,74 @@
#![allow(dead_code, unused_imports)]
use color_eyre::eyre;
use eyre::{Report, Result};
use tracing::instrument;
#[instrument]
#[cfg(feature = "issue-url")]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::config::HookBuilder::default()
.issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new"))
.add_issue_metadata("version", env!("CARGO_PKG_VERSION"))
.issue_filter(|kind| match kind {
color_eyre::ErrorKind::NonRecoverable(_) => false,
color_eyre::ErrorKind::Recoverable(_) => true,
})
.install()?;
let report = read_config().unwrap_err();
eprintln!("Error: {:?}", report);
read_config2();
Ok(())
}
#[cfg(not(feature = "issue-url"))]
fn main() {
unimplemented!("this example requires the \"issue-url\" feature")
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) -> Result<String> {
Ok(std::fs::read_to_string(path)?)
}
#[instrument]
fn read_config() -> Result<()> {
read_file("fake_file")?;
Ok(())
}
#[instrument]
fn read_file2(path: &str) {
if let Err(e) = std::fs::read_to_string(path) {
panic!("{}", e);
}
}
#[instrument]
fn read_config2() {
read_file2("fake_file")
}

View File

@ -0,0 +1,55 @@
use color_eyre::{eyre::eyre, eyre::Report, Section};
use thiserror::Error;
fn main() -> Result<(), Report> {
color_eyre::install()?;
let errors = get_errors();
join_errors(errors)
}
fn join_errors(results: Vec<Result<(), SourceError>>) -> Result<(), Report> {
if results.iter().all(|r| r.is_ok()) {
return Ok(());
}
let err = results
.into_iter()
.filter(Result::is_err)
.map(Result::unwrap_err)
.fold(eyre!("encountered multiple errors"), |report, e| {
report.error(e)
});
Err(err)
}
/// Helper function to generate errors
fn get_errors() -> Vec<Result<(), SourceError>> {
vec![
Err(SourceError {
source: StrError("The task you ran encountered an error"),
msg: "The task could not be completed",
}),
Err(SourceError {
source: StrError("The machine you're connecting to is actively on fire"),
msg: "The machine is unreachable",
}),
Err(SourceError {
source: StrError("The file you're parsing is literally written in c++ instead of rust, what the hell"),
msg: "The file could not be parsed",
}),
]
}
/// Arbitrary error type for demonstration purposes
#[derive(Debug, Error)]
#[error("{0}")]
struct StrError(&'static str);
/// Arbitrary error type for demonstration purposes with a source error
#[derive(Debug, Error)]
#[error("{msg}")]
struct SourceError {
msg: &'static str,
source: StrError,
}

View File

@ -0,0 +1,50 @@
use color_eyre::eyre::Report;
use tracing::instrument;
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks();
eyre_hook.install()?;
std::panic::set_hook(Box::new(move |pi| {
tracing::error!("{}", panic_hook.panic_report(pi));
}));
read_config();
Ok(())
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) {
if let Err(e) = std::fs::read_to_string(path) {
panic!("{}", e);
}
}
#[instrument]
fn read_config() {
read_file("fake_file")
}

View File

@ -0,0 +1,46 @@
use color_eyre::eyre::Report;
use tracing::instrument;
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::config::HookBuilder::default()
.panic_section("consider reporting the bug on github")
.install()?;
read_config();
Ok(())
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) {
if let Err(e) = std::fs::read_to_string(path) {
panic!("{}", e);
}
}
#[instrument]
fn read_config() {
read_file("fake_file")
}

View File

@ -0,0 +1,62 @@
use color_eyre::{config::Theme, eyre::Report, owo_colors::style, Section};
/// To experiment with theme values, edit `theme()` below and execute `cargo run --example theme`
fn theme() -> Theme {
Theme::dark()
// ^ use `new` to derive from a blank theme, or `light` to derive from a light theme.
// Now configure your theme (see the docs for all options):
.line_number(style().blue())
.help_info_suggestion(style().red())
}
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct TestError(&'static str);
#[tracing::instrument]
fn get_error(msg: &'static str) -> Report {
fn create_report(msg: &'static str) -> Report {
Report::msg(msg)
.note("note")
.warning("warning")
.suggestion("suggestion")
.error(TestError("error"))
}
// Using `Option` to add dependency code.
// See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56
None::<Option<()>>
.ok_or_else(|| create_report(msg))
.unwrap_err()
}
fn main() {
setup();
println!("{:?}", get_error("test"));
}
fn setup() {
std::env::set_var("RUST_LIB_BACKTRACE", "full");
#[cfg(feature = "capture-spantrace")]
{
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(tracing_error::ErrorLayer::default())
.init();
}
color_eyre::config::HookBuilder::new()
.theme(theme())
.install()
.expect("Failed to install `color_eyre`");
}

View File

@ -0,0 +1,62 @@
//! Nothing interesting here. This is just a small helper used in a test.
//! This needs to be an "example" until binaries can declare separate dependencies (see https://github.com/rust-lang/cargo/issues/1982)
//! See "tests/theme.rs" for more information.
use color_eyre::{eyre::Report, Section};
#[rustfmt::skip]
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct TestError(&'static str);
#[rustfmt::skip]
fn get_error(msg: &'static str) -> Report {
#[rustfmt::skip]
#[inline(never)]
fn create_report(msg: &'static str) -> Report {
Report::msg(msg)
.note("note")
.warning("warning")
.suggestion("suggestion")
.error(TestError("error"))
}
// Getting regular `Report`. Using `Option` to trigger `is_dependency_code`.
// See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56
None::<Option<()>>.ok_or_else(|| create_report(msg)).unwrap_err()
}
fn main() {
setup();
let msg = "test";
let span = tracing::info_span!("get_error", msg);
let _guard = span.enter();
let error = get_error(msg);
std::panic::panic_any(error)
}
fn setup() {
std::env::set_var("RUST_BACKTRACE", "1");
#[cfg(feature = "capture-spantrace")]
{
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(tracing_error::ErrorLayer::default())
.init();
}
color_eyre::install().expect("Failed to install `color_eyre`");
}

View File

@ -0,0 +1,43 @@
use color_eyre::{eyre::Report, eyre::WrapErr, Section};
use tracing::{info, instrument};
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::install()?;
read_config()
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) -> Result<(), Report> {
info!("Reading file");
Ok(std::fs::read_to_string(path).map(drop)?)
}
#[instrument]
fn read_config() -> Result<(), Report> {
read_file("fake_file")
.wrap_err("Unable to read config")
.suggestion("try using a file that exists next time")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View File

@ -0,0 +1,13 @@
{ pkgs ? import <nixpkgs> { } }:
let
inherit (pkgs) stdenv lib python38;
py = python38.withPackages (pypkgs: with pypkgs; [ beautifulsoup4 ]);
in stdenv.mkDerivation {
pname = "color-eyre-scripts";
version = "0.0.0";
src = ./.;
buildInputs = [ py ];
}

View File

@ -0,0 +1,126 @@
#! /usr/bin/env python3.8
from __future__ import annotations
import argparse
from argparse import FileType, ArgumentParser
import enum
import sys
from bs4 import BeautifulSoup, Tag
class LineType(enum.Enum):
OUTER_DOC = enum.auto()
INNER_DOC = enum.auto()
SOURCE = enum.auto()
@classmethod
def from_line(cls, line: str) -> (LineType, str):
if line.startswith("//!"):
return (cls.OUTER_DOC, line[len("//!") :])
elif line.startswith("///"):
return (cls.INNER_DOC, line[len("///") :])
else:
return (cls.SOURCE, line)
def prefix(self) -> str:
if self == LineType.OUTER_DOC:
return "//!"
elif self == LineType.INNER_DOC:
return "///"
else:
return ""
def fix_gnome_html(fh: file) -> str:
"""Tweaks for fixing "Copy as HTML" output from gnome-terminal
Reads source from a Rust file.
"""
anything_changed = False
line_type = LineType.SOURCE
# Lines of current HTML <pre> chunk
pre_chunk = []
# Lines of processed file
ret = []
for (line_type, stripped_line), line in map(
lambda line: (LineType.from_line(line), line), fh.readlines()
):
if line_type == LineType.SOURCE:
ret.append(line)
elif stripped_line.lstrip().startswith("<pre"):
pre_chunk = [stripped_line]
elif stripped_line.rstrip().endswith("</pre>"):
pre_chunk.append(stripped_line)
if any("<font" in line for line in pre_chunk):
joined_chunk = "".join(pre_chunk)
fixed_chunk = fix_pre(joined_chunk, prefix=line_type.prefix())
anything_changed = joined_chunk != fixed_chunk
ret.append(fixed_chunk)
pre_chunk = []
else:
prefix = line_type.prefix()
ret.extend(line_type.prefix() + line for line in pre_chunk)
elif pre_chunk:
pre_chunk.append(stripped_line)
else:
ret.append(line)
return "".join(ret) if anything_changed else None
def fix_pre(html: str, prefix: str = "") -> str:
"""Fixes an individual <pre> tag from Gnome.
Optionally prepends a given prefix to each line in the returned output.
"""
soup = BeautifulSoup(html, "html.parser")
for pre in soup.find_all("pre"):
for tag in pre.find_all("font"):
# <font color=xxx> -> <span style="color: xxx">
tag.name = "span"
color = tag.attrs.pop("color")
tag["style"] = f"color: {color}"
return "".join(prefix + line for line in str(soup).splitlines(keepends=True))
def main():
parser = ArgumentParser(
description="""Convert HTML from Gnome terminal's 'Copy as HTML' feature
to use modern <span> tags and inline CSS.
This script is idempotent, i.e. multiple invocations will not change
the output past the first invocation."""
)
parser.add_argument(
"file",
nargs="+",
type=FileType("r+", encoding="utf-8"),
help="""Rust file to update <pre> blocks in.""",
)
args = parser.parse_args()
for fh in args.file:
if not fh.name.endswith(".rs"):
print(
"This script only fixes Rust source files; you probably didn't mean to include",
fh.name,
"so I'll skip processing it.",
)
new_content = fix_gnome_html(fh)
if new_content is not None:
print("Updated example colored output in", fh.name)
fh.seek(0)
fh.write(new_content)
else:
print("Nothing to fix in", fh.name)
fh.close()
if __name__ == "__main__":
main()

1221
color-eyre/src/config.rs Normal file

File diff suppressed because it is too large Load Diff

25
color-eyre/src/fmt.rs Normal file
View File

@ -0,0 +1,25 @@
//! Module for new types that isolate complext formatting
use std::fmt;
use owo_colors::OwoColorize;
pub(crate) struct LocationSection<'a>(
pub(crate) Option<&'a std::panic::Location<'a>>,
pub(crate) crate::config::Theme,
);
impl fmt::Display for LocationSection<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let theme = self.1;
// If known, print panic location.
if let Some(loc) = self.0 {
write!(f, "{}", loc.file().style(theme.panic_file))?;
write!(f, ":")?;
write!(f, "{}", loc.line().style(theme.panic_line_number))?;
} else {
write!(f, "<unknown>")?;
}
Ok(())
}
}

188
color-eyre/src/handler.rs Normal file
View File

@ -0,0 +1,188 @@
use crate::{
config::BacktraceFormatter,
section::help::HelpInfo,
writers::{EnvSection, WriterExt},
Handler,
};
use backtrace::Backtrace;
use indenter::{indented, Format};
use std::fmt::Write;
#[cfg(feature = "capture-spantrace")]
use tracing_error::{ExtractSpanTrace, SpanTrace};
impl std::fmt::Debug for Handler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("redacted")
}
}
impl Handler {
/// Return a reference to the captured `Backtrace` type
pub fn backtrace(&self) -> Option<&Backtrace> {
self.backtrace.as_ref()
}
/// Return a reference to the captured `SpanTrace` type
#[cfg(feature = "capture-spantrace")]
#[cfg_attr(docsrs, doc(cfg(feature = "capture-spantrace")))]
pub fn span_trace(&self) -> Option<&SpanTrace> {
self.span_trace.as_ref()
}
pub(crate) fn format_backtrace<'a>(
&'a self,
trace: &'a backtrace::Backtrace,
) -> BacktraceFormatter<'a> {
BacktraceFormatter {
filters: &self.filters,
inner: trace,
theme: self.theme,
}
}
}
impl eyre::EyreHandler for Handler {
fn debug(
&self,
error: &(dyn std::error::Error + 'static),
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
if f.alternate() {
return core::fmt::Debug::fmt(error, f);
}
#[cfg(feature = "capture-spantrace")]
let errors = || {
eyre::Chain::new(error)
.filter(|e| e.span_trace().is_none())
.enumerate()
};
#[cfg(not(feature = "capture-spantrace"))]
let errors = || eyre::Chain::new(error).enumerate();
for (n, error) in errors() {
writeln!(f)?;
write!(indented(f).ind(n), "{}", self.theme.error.style(error))?;
}
let mut separated = f.header("\n\n");
#[cfg(feature = "track-caller")]
if self.display_location_section {
write!(
separated.ready(),
"{}",
crate::SectionExt::header(
crate::fmt::LocationSection(self.location, self.theme),
"Location:"
)
)?;
}
for section in self
.sections
.iter()
.filter(|s| matches!(s, HelpInfo::Error(_, _)))
{
write!(separated.ready(), "{}", section)?;
}
for section in self
.sections
.iter()
.filter(|s| matches!(s, HelpInfo::Custom(_)))
{
write!(separated.ready(), "{}", section)?;
}
#[cfg(feature = "capture-spantrace")]
let span_trace = self
.span_trace
.as_ref()
.or_else(|| get_deepest_spantrace(error));
#[cfg(feature = "capture-spantrace")]
{
if let Some(span_trace) = span_trace {
write!(
&mut separated.ready(),
"{}",
crate::writers::FormattedSpanTrace(span_trace)
)?;
}
}
if !self.suppress_backtrace {
if let Some(backtrace) = self.backtrace.as_ref() {
let fmted_bt = self.format_backtrace(backtrace);
write!(
indented(&mut separated.ready())
.with_format(Format::Uniform { indentation: " " }),
"{}",
fmted_bt
)?;
}
}
let f = separated.ready();
let mut h = f.header("\n");
let mut f = h.in_progress();
for section in self
.sections
.iter()
.filter(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_, _)))
{
write!(&mut f, "{}", section)?;
f = h.ready();
}
if self.display_env_section {
let env_section = EnvSection {
bt_captured: &self.backtrace.is_some(),
#[cfg(feature = "capture-spantrace")]
span_trace,
};
write!(&mut separated.ready(), "{}", env_section)?;
}
#[cfg(feature = "issue-url")]
if self.issue_url.is_some() && (*self.issue_filter)(crate::ErrorKind::Recoverable(error)) {
let url = self.issue_url.as_ref().unwrap();
let mut payload = String::from("Error: ");
for (n, error) in errors() {
writeln!(&mut payload)?;
write!(indented(&mut payload).ind(n), "{}", error)?;
}
let issue_section = crate::section::github::IssueSection::new(url, &payload)
.with_backtrace(self.backtrace.as_ref())
.with_metadata(&self.issue_metadata);
#[cfg(feature = "capture-spantrace")]
let issue_section = issue_section.with_span_trace(span_trace);
write!(&mut separated.ready(), "{}", issue_section)?;
}
Ok(())
}
#[cfg(feature = "track-caller")]
fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {
self.location = Some(location);
}
}
#[cfg(feature = "capture-spantrace")]
pub(crate) fn get_deepest_spantrace<'a>(
error: &'a (dyn std::error::Error + 'static),
) -> Option<&'a SpanTrace> {
eyre::Chain::new(error)
.rev()
.flat_map(|error| error.span_trace())
.next()
}

460
color-eyre/src/lib.rs Normal file
View File

@ -0,0 +1,460 @@
//! An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
//! formatted error reports for all kinds of errors.
//!
//! ## TLDR
//!
//! `color_eyre` helps you build error reports that look like this:
//!
//! <pre><span style="color: #06989A"><b>color-eyre</b></span> on <span style="color: #75507B"><b> hooked</b></span> <span style="color: #CC0000"><b>[$!] </b></span>is <span style="color: #FF8700"><b>📦 v0.5.0</b></span> via <span style="color: #CC0000"><b>🦀 v1.44.0</b></span>
//! <span style="color: #4E9A06"><b></b></span> cargo run --example custom_section
//! <span style="color: #4E9A06"><b> Finished</b></span> dev [unoptimized + debuginfo] target(s) in 0.04s
//! <span style="color: #4E9A06"><b> Running</b></span> `target/debug/examples/custom_section`
//! Error:
//! 0: <span style="color: #F15D22">Unable to read config</span>
//! 1: <span style="color: #F15D22">cmd exited with non-zero status code</span>
//!
//! Stderr:
//! cat: fake_file: No such file or directory
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//!
//! 0: <span style="color: #F15D22">custom_section::output2</span> with <span style="color: #34E2E2">self=&quot;cat&quot; &quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">14</span>
//! 1: <span style="color: #F15D22">custom_section::read_file</span> with <span style="color: #34E2E2">path=&quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">58</span>
//! 2: <span style="color: #F15D22">custom_section::read_config</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">63</span>
//!
//! <span style="color: #34E2E2">Suggestion</span>: try using a file that exists next time</pre>
//!
//! ## Setup
//!
//! Add the following to your toml file:
//!
//! ```toml
//! [dependencies]
//! color-eyre = "0.6"
//! ```
//!
//! And install the panic and error report handlers:
//!
//! ```rust
//! use color_eyre::eyre::Result;
//!
//! fn main() -> Result<()> {
//! color_eyre::install()?;
//!
//! // ...
//! # Ok(())
//! }
//! ```
//!
//! ### Disabling tracing support
//!
//! If you don't plan on using `tracing_error` and `SpanTrace` you can disable the
//! tracing integration to cut down on unused dependencies:
//!
//! ```toml
//! [dependencies]
//! color-eyre = { version = "0.6", default-features = false }
//! ```
//!
//! ### Disabling SpanTrace capture by default
//!
//! color-eyre defaults to capturing span traces. This is because `SpanTrace`
//! capture is significantly cheaper than `Backtrace` capture. However, like
//! backtraces, span traces are most useful for debugging applications, and it's
//! not uncommon to want to disable span trace capture by default to keep noise out
//! developer.
//!
//! To disable span trace capture you must explicitly set one of the env variables
//! that regulate `SpanTrace` capture to `"0"`:
//!
//! ```rust
//! if std::env::var("RUST_SPANTRACE").is_err() {
//! std::env::set_var("RUST_SPANTRACE", "0");
//! }
//! ```
//!
//! ### Improving perf on debug builds
//!
//! In debug mode `color-eyre` behaves noticably worse than `eyre`. This is caused
//! by the fact that `eyre` uses `std::backtrace::Backtrace` instead of
//! `backtrace::Backtrace`. The std version of backtrace is precompiled with
//! optimizations, this means that whether or not you're in debug mode doesn't
//! matter much for how expensive backtrace capture is, it will always be in the
//! 10s of milliseconds to capture. A debug version of `backtrace::Backtrace`
//! however isn't so lucky, and can take an order of magnitude more time to capture
//! a backtrace compared to its std counterpart.
//!
//! Cargo [profile
//! overrides](https://doc.rust-lang.org/cargo/reference/profiles.html#overrides)
//! can be used to mitigate this problem. By configuring your project to always
//! build `backtrace` with optimizations you should get the same performance from
//! `color-eyre` that you're used to with `eyre`. To do so add the following to
//! your Cargo.toml:
//!
//! ```toml
//! [profile.dev.package.backtrace]
//! opt-level = 3
//! ```
//!
//! ## Features
//!
//! ### Multiple report format verbosity levels
//!
//! `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
//! and `Backtrace`, minimal, short, and full. Take the below snippets of the output produced by [`examples/usage.rs`]:
//!
//! ---
//!
//! Running `cargo run --example usage` without `RUST_LIB_BACKTRACE` set will produce a minimal
//! report like this:
//!
//! <pre><span style="color: #06989A"><b>color-eyre</b></span> on <span style="color: #75507B"><b> hooked</b></span> <span style="color: #CC0000"><b>[$!] </b></span>is <span style="color: #FF8700"><b>📦 v0.5.0</b></span> via <span style="color: #CC0000"><b>🦀 v1.44.0</b></span> took <span style="color: #C4A000"><b>2s</b></span>
//! <span style="color: #CC0000"><b></b></span> cargo run --example usage
//! <span style="color: #4E9A06"><b> Finished</b></span> dev [unoptimized + debuginfo] target(s) in 0.04s
//! <span style="color: #4E9A06"><b> Running</b></span> `target/debug/examples/usage`
//! <span style="color: #A1A1A1">Jul 05 19:15:58.026 </span><span style="color: #4E9A06"> INFO</span> <b>read_config</b>:<b>read_file{</b>path=&quot;fake_file&quot;<b>}</b>: Reading file
//! Error:
//! 0: <span style="color: #F15D22">Unable to read config</span>
//! 1: <span style="color: #F15D22">No such file or directory (os error 2)</span>
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//!
//! 0: <span style="color: #F15D22">usage::read_file</span> with <span style="color: #34E2E2">path=&quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">32</span>
//! 1: <span style="color: #F15D22">usage::read_config</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">38</span>
//!
//! <span style="color: #34E2E2">Suggestion</span>: try using a file that exists next time</pre>
//!
//! <br>
//!
//! Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short
//! format, which additionally capture a [`backtrace::Backtrace`]:
//!
//! <pre><span style="color: #06989A"><b>color-eyre</b></span> on <span style="color: #75507B"><b> hooked</b></span> <span style="color: #CC0000"><b>[$!] </b></span>is <span style="color: #FF8700"><b>📦 v0.5.0</b></span> via <span style="color: #CC0000"><b>🦀 v1.44.0</b></span>
//! <span style="color: #CC0000"><b></b></span> RUST_LIB_BACKTRACE=1 cargo run --example usage
//! <span style="color: #4E9A06"><b> Finished</b></span> dev [unoptimized + debuginfo] target(s) in 0.04s
//! <span style="color: #4E9A06"><b> Running</b></span> `target/debug/examples/usage`
//! <span style="color: #A1A1A1">Jul 05 19:16:02.853 </span><span style="color: #4E9A06"> INFO</span> <b>read_config</b>:<b>read_file{</b>path=&quot;fake_file&quot;<b>}</b>: Reading file
//! Error:
//! 0: <span style="color: #F15D22">Unable to read config</span>
//! 1: <span style="color: #F15D22">No such file or directory (os error 2)</span>
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//!
//! 0: <span style="color: #F15D22">usage::read_file</span> with <span style="color: #34E2E2">path=&quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">32</span>
//! 1: <span style="color: #F15D22">usage::read_config</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">38</span>
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//! <span style="color: #34E2E2"> ⋮ 5 frames hidden ⋮ </span>
//! 6: <span style="color: #F15D22">usage::read_file</span><span style="color: #88807C">::haee210cb22460af3</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">35</span>
//! 7: <span style="color: #F15D22">usage::read_config</span><span style="color: #88807C">::ha649ef4ec333524d</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">40</span>
//! 8: <span style="color: #F15D22">usage::main</span><span style="color: #88807C">::hbe443b50eac38236</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">11</span>
//! <span style="color: #34E2E2"> ⋮ 10 frames hidden ⋮ </span>
//!
//! <span style="color: #34E2E2">Suggestion</span>: try using a file that exists next time</pre>
//!
//! <br>
//!
//! Finally, running `RUST_LIB_BACKTRACE=full cargo run --example usage` tells `color-eyre` to use
//! the full format, which in addition to the above will attempt to include source lines where the
//! error originated from, assuming it can find them on the disk.
//!
//! <pre><span style="color: #06989A"><b>color-eyre</b></span> on <span style="color: #75507B"><b> hooked</b></span> <span style="color: #CC0000"><b>[$!] </b></span>is <span style="color: #FF8700"><b>📦 v0.5.0</b></span> via <span style="color: #CC0000"><b>🦀 v1.44.0</b></span>
//! <span style="color: #CC0000"><b></b></span> RUST_LIB_BACKTRACE=full cargo run --example usage
//! <span style="color: #4E9A06"><b> Finished</b></span> dev [unoptimized + debuginfo] target(s) in 0.05s
//! <span style="color: #4E9A06"><b> Running</b></span> `target/debug/examples/usage`
//! <span style="color: #A1A1A1">Jul 05 19:16:06.335 </span><span style="color: #4E9A06"> INFO</span> <b>read_config</b>:<b>read_file{</b>path=&quot;fake_file&quot;<b>}</b>: Reading file
//! Error:
//! 0: <span style="color: #F15D22">Unable to read config</span>
//! 1: <span style="color: #F15D22">No such file or directory (os error 2)</span>
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//!
//! 0: <span style="color: #F15D22">usage::read_file</span> with <span style="color: #34E2E2">path=&quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">32</span>
//! 30 │ }
//! 31 │
//! <b> 32 &gt; #[instrument]</b>
//! 33 │ fn read_file(path: &amp;str) -&gt; Result&lt;(), Report&gt; {
//! 34 │ info!(&quot;Reading file&quot;);
//! 1: <span style="color: #F15D22">usage::read_config</span>
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">38</span>
//! 36 │ }
//! 37 │
//! <b> 38 &gt; #[instrument]</b>
//! 39 │ fn read_config() -&gt; Result&lt;(), Report&gt; {
//! 40 │ read_file(&quot;fake_file&quot;)
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//! <span style="color: #34E2E2"> ⋮ 5 frames hidden ⋮ </span>
//! 6: <span style="color: #F15D22">usage::read_file</span><span style="color: #88807C">::haee210cb22460af3</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">35</span>
//! 33 │ fn read_file(path: &amp;str) -&gt; Result&lt;(), Report&gt; {
//! 34 │ info!(&quot;Reading file&quot;);
//! <span style="color: #D3D7CF"><b> 35 &gt; Ok(std::fs::read_to_string(path).map(drop)?)</b></span>
//! 36 │ }
//! 37 │
//! 7: <span style="color: #F15D22">usage::read_config</span><span style="color: #88807C">::ha649ef4ec333524d</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">40</span>
//! 38 │ #[instrument]
//! 39 │ fn read_config() -&gt; Result&lt;(), Report&gt; {
//! <span style="color: #D3D7CF"><b> 40 &gt; read_file(&quot;fake_file&quot;)</b></span>
//! 41 │ .wrap_err(&quot;Unable to read config&quot;)
//! 42 │ .suggestion(&quot;try using a file that exists next time&quot;)
//! 8: <span style="color: #F15D22">usage::main</span><span style="color: #88807C">::hbe443b50eac38236</span>
//! at <span style="color: #75507B">/home/jlusby/git/yaahc/color-eyre/examples/usage.rs</span>:<span style="color: #75507B">11</span>
//! 9 │ color_eyre::install()?;
//! 10 │
//! <span style="color: #D3D7CF"><b> 11 &gt; Ok(read_config()?)</b></span>
//! 12 │ }
//! 13 │
//! <span style="color: #34E2E2"> ⋮ 10 frames hidden ⋮ </span>
//!
//! <span style="color: #34E2E2">Suggestion</span>: try using a file that exists next time</pre>
//!
//! ### Custom `Section`s for error reports via [`Section`] trait
//!
//! The `section` module provides helpers for adding extra sections to error
//! reports. Sections are disinct from error messages and are displayed
//! independently from the chain of errors. Take this example of adding sections
//! to contain `stderr` and `stdout` from a failed command, taken from
//! [`examples/custom_section.rs`]:
//!
//! ```rust
//! use color_eyre::{eyre::eyre, SectionExt, Section, eyre::Report};
//! use std::process::Command;
//! use tracing::instrument;
//!
//! trait Output {
//! fn output2(&mut self) -> Result<String, Report>;
//! }
//!
//! impl Output for Command {
//! #[instrument]
//! fn output2(&mut self) -> Result<String, Report> {
//! let output = self.output()?;
//!
//! let stdout = String::from_utf8_lossy(&output.stdout);
//!
//! if !output.status.success() {
//! let stderr = String::from_utf8_lossy(&output.stderr);
//! Err(eyre!("cmd exited with non-zero status code"))
//! .with_section(move || stdout.trim().to_string().header("Stdout:"))
//! .with_section(move || stderr.trim().to_string().header("Stderr:"))
//! } else {
//! Ok(stdout.into())
//! }
//! }
//! }
//! ```
//!
//! ---
//!
//! Here we have an function that, if the command exits unsuccessfully, creates a
//! report indicating the failure and attaches two sections, one for `stdout` and
//! one for `stderr`.
//!
//! Running `cargo run --example custom_section` shows us how these sections are
//! included in the output:
//!
//! <pre><span style="color: #06989A"><b>color-eyre</b></span> on <span style="color: #75507B"><b> hooked</b></span> <span style="color: #CC0000"><b>[$!] </b></span>is <span style="color: #FF8700"><b>📦 v0.5.0</b></span> via <span style="color: #CC0000"><b>🦀 v1.44.0</b></span> took <span style="color: #C4A000"><b>2s</b></span>
//! <span style="color: #CC0000"><b></b></span> cargo run --example custom_section
//! <span style="color: #4E9A06"><b> Finished</b></span> dev [unoptimized + debuginfo] target(s) in 0.04s
//! <span style="color: #4E9A06"><b> Running</b></span> `target/debug/examples/custom_section`
//! Error:
//! 0: <span style="color: #F15D22">Unable to read config</span>
//! 1: <span style="color: #F15D22">cmd exited with non-zero status code</span>
//!
//! Stderr:
//! cat: fake_file: No such file or directory
//!
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//!
//! 0: <span style="color: #F15D22">custom_section::output2</span> with <span style="color: #34E2E2">self=&quot;cat&quot; &quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">14</span>
//! 1: <span style="color: #F15D22">custom_section::read_file</span> with <span style="color: #34E2E2">path=&quot;fake_file&quot;</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">58</span>
//! 2: <span style="color: #F15D22">custom_section::read_config</span>
//! at <span style="color: #75507B">examples/custom_section.rs</span>:<span style="color: #75507B">63</span>
//!
//! <span style="color: #34E2E2">Suggestion</span>: try using a file that exists next time</pre>
//!
//! Only the `Stderr:` section actually gets included. The `cat` command fails,
//! so stdout ends up being empty and is skipped in the final report. This gives
//! us a short and concise error report indicating exactly what was attempted and
//! how it failed.
//!
//! ### Aggregating multiple errors into one report
//!
//! It's not uncommon for programs like batched task runners or parsers to want
//! to return an error with multiple sources. The current version of the error
//! trait does not support this use case very well, though there is [work being
//! done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
//!
//! For now however one way to work around this is to compose errors outside the
//! error trait. `color-eyre` supports such composition in its error reports via
//! the `Section` trait.
//!
//! For an example of how to aggregate errors check out [`examples/multiple_errors.rs`].
//!
//! ### Custom configuration for `color-backtrace` for setting custom filters and more
//!
//! The pretty printing for backtraces and span traces isn't actually provided by
//! `color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
//! [`color-spantrace`]. `color-backtrace` in particular has many more features
//! than are exported by `color-eyre`, such as customized color schemes, panic
//! hooks, and custom frame filters. The custom frame filters are particularly
//! useful when combined with `color-eyre`, so to enable their usage we provide
//! the `install` fn for setting up a custom `BacktracePrinter` with custom
//! filters installed.
//!
//! For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
//!
//! [`eyre`]: https://docs.rs/eyre
//! [`tracing-error`]: https://docs.rs/tracing-error
//! [`color-backtrace`]: https://docs.rs/color-backtrace
//! [`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html
//! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
//! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
//! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
//! [`Section`]: https://docs.rs/color-eyre/*/color_eyre/trait.Section.html
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
//! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
//! [`Handler`]: https://docs.rs/color-eyre/*/color_eyre/struct.Handler.html
//! [`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs
//! [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs
//! [`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs
//! [`examples/multiple_errors.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/multiple_errors.rs
#![doc(html_root_url = "https://docs.rs/color-eyre/0.6.2")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
missing_docs,
rustdoc::missing_doc_code_examples,
rust_2018_idioms,
unreachable_pub,
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)]
#![allow(clippy::try_err)]
use std::sync::Arc;
use backtrace::Backtrace;
pub use eyre;
#[doc(hidden)]
pub use eyre::Report;
#[doc(hidden)]
pub use eyre::Result;
pub use owo_colors;
use section::help::HelpInfo;
#[doc(hidden)]
pub use section::Section as Help;
pub use section::{IndentedSection, Section, SectionExt};
#[cfg(feature = "capture-spantrace")]
use tracing_error::SpanTrace;
#[doc(hidden)]
pub use Handler as Context;
pub mod config;
mod fmt;
mod handler;
pub(crate) mod private;
pub mod section;
mod writers;
/// A custom handler type for [`eyre::Report`] which provides colorful error
/// reports and [`tracing-error`] support.
///
/// # Details
///
/// This type is not intended to be used directly, prefer using it via the
/// [`color_eyre::Report`] and [`color_eyre::Result`] type aliases.
///
/// [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
/// [`tracing-error`]: https://docs.rs/tracing-error
/// [`color_eyre::Report`]: type.Report.html
/// [`color_eyre::Result`]: type.Result.html
pub struct Handler {
filters: Arc<[Box<config::FilterCallback>]>,
backtrace: Option<Backtrace>,
suppress_backtrace: bool,
#[cfg(feature = "capture-spantrace")]
span_trace: Option<SpanTrace>,
sections: Vec<HelpInfo>,
display_env_section: bool,
#[cfg(feature = "track-caller")]
display_location_section: bool,
#[cfg(feature = "issue-url")]
issue_url: Option<String>,
#[cfg(feature = "issue-url")]
issue_metadata:
std::sync::Arc<Vec<(String, Box<dyn std::fmt::Display + Send + Sync + 'static>)>>,
#[cfg(feature = "issue-url")]
issue_filter: std::sync::Arc<config::IssueFilterCallback>,
theme: crate::config::Theme,
#[cfg(feature = "track-caller")]
location: Option<&'static std::panic::Location<'static>>,
}
/// The kind of type erased error being reported
#[cfg(feature = "issue-url")]
#[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
pub enum ErrorKind<'a> {
/// A non recoverable error aka `panic!`
NonRecoverable(&'a dyn std::any::Any),
/// A recoverable error aka `impl std::error::Error`
Recoverable(&'a (dyn std::error::Error + 'static)),
}
/// Install the default panic and error report hooks
///
/// # Details
///
/// This function must be called to enable the customization of `eyre::Report`
/// provided by `color-eyre`. This function should be called early, ideally
/// before any errors could be encountered.
///
/// Only the first install will succeed. Calling this function after another
/// report handler has been installed will cause an error. **Note**: This
/// function _must_ be called before any `eyre::Report`s are constructed to
/// prevent the default handler from being installed.
///
/// Installing a global theme in `color_spantrace` manually (by calling
/// `color_spantrace::set_theme` or `color_spantrace::colorize` before
/// `install` is called) will result in an error if this function is called.
///
/// # Examples
///
/// ```rust
/// use color_eyre::eyre::Result;
///
/// fn main() -> Result<()> {
/// color_eyre::install()?;
///
/// // ...
/// # Ok(())
/// }
/// ```
pub fn install() -> Result<(), crate::eyre::Report> {
config::HookBuilder::default().install()
}

View File

@ -0,0 +1,5 @@
use crate::eyre::Report;
pub trait Sealed {}
impl<T, E> Sealed for std::result::Result<T, E> where E: Into<Report> {}
impl Sealed for Report {}

View File

@ -0,0 +1,190 @@
use crate::writers::DisplayExt;
use backtrace::Backtrace;
use std::{fmt, panic::Location};
#[cfg(feature = "capture-spantrace")]
use tracing_error::SpanTrace;
use url::Url;
type Display<'a> = Box<dyn std::fmt::Display + Send + Sync + 'a>;
pub(crate) struct IssueSection<'a> {
url: &'a str,
msg: &'a str,
location: Option<&'a Location<'a>>,
backtrace: Option<&'a Backtrace>,
#[cfg(feature = "capture-spantrace")]
span_trace: Option<&'a SpanTrace>,
metadata: &'a [(String, Display<'a>)],
}
impl<'a> IssueSection<'a> {
pub(crate) fn new(url: &'a str, msg: &'a str) -> Self {
IssueSection {
url,
msg,
location: None,
backtrace: None,
#[cfg(feature = "capture-spantrace")]
span_trace: None,
metadata: &[],
}
}
pub(crate) fn with_location(mut self, location: impl Into<Option<&'a Location<'a>>>) -> Self {
self.location = location.into();
self
}
pub(crate) fn with_backtrace(mut self, backtrace: impl Into<Option<&'a Backtrace>>) -> Self {
self.backtrace = backtrace.into();
self
}
#[cfg(feature = "capture-spantrace")]
pub(crate) fn with_span_trace(mut self, span_trace: impl Into<Option<&'a SpanTrace>>) -> Self {
self.span_trace = span_trace.into();
self
}
pub(crate) fn with_metadata(mut self, metadata: &'a [(String, Display<'a>)]) -> Self {
self.metadata = metadata;
self
}
}
impl fmt::Display for IssueSection<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let location = self
.location
.map(|loc| ("location".to_string(), Box::new(loc) as _));
let metadata = self.metadata.iter().chain(location.as_ref());
let metadata = MetadataSection { metadata }.to_string();
let mut body = Body::new();
body.push_section("Error", ConsoleSection(self.msg))?;
if !self.metadata.is_empty() {
body.push_section("Metadata", metadata)?;
}
#[cfg(feature = "capture-spantrace")]
if let Some(st) = self.span_trace {
body.push_section(
"SpanTrace",
Collapsed(ConsoleSection(st.with_header("SpanTrace:\n"))),
)?;
}
if let Some(bt) = self.backtrace {
body.push_section(
"Backtrace",
Collapsed(ConsoleSection(
DisplayFromDebug(bt).with_header("Backtrace:\n"),
)),
)?;
}
let url_result = Url::parse_with_params(
self.url,
&[("title", "<autogenerated-issue>"), ("body", &body.body)],
);
let url: &dyn fmt::Display = match &url_result {
Ok(url_struct) => url_struct,
Err(_) => &self.url,
};
url.with_header("Consider reporting this error using this URL: ")
.fmt(f)
}
}
struct Body {
body: String,
}
impl Body {
fn new() -> Self {
Body {
body: String::new(),
}
}
fn push_section<T>(&mut self, header: &'static str, section: T) -> fmt::Result
where
T: fmt::Display,
{
use std::fmt::Write;
let separator = if self.body.is_empty() { "" } else { "\n\n" };
let header = header
.with_header("## ")
.with_header(separator)
.with_footer("\n");
write!(&mut self.body, "{}", section.with_header(header))
}
}
struct MetadataSection<T> {
metadata: T,
}
impl<'a, T> MetadataSection<T>
where
T: IntoIterator<Item = &'a (String, Display<'a>)>,
{
// This is implemented as a free functions so it can consume the `metadata`
// iterator, rather than being forced to leave it unmodified if its behind a
// `&self` shared reference via the Display trait
#[allow(clippy::inherent_to_string, clippy::wrong_self_convention)]
fn to_string(self) -> String {
use std::fmt::Write;
let mut out = String::new();
let f = &mut out;
writeln!(f, "|key|value|").expect("writing to a string doesn't panic");
writeln!(f, "|--|--|").expect("writing to a string doesn't panic");
for (key, value) in self.metadata {
writeln!(f, "|**{}**|{}|", key, value).expect("writing to a string doesn't panic");
}
out
}
}
struct ConsoleSection<T>(T);
impl<T> fmt::Display for ConsoleSection<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(&self.0).with_header("```\n").with_footer("\n```").fmt(f)
}
}
struct Collapsed<T>(T);
impl<T> fmt::Display for Collapsed<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(&self.0)
.with_header("\n<details>\n\n")
.with_footer("\n</details>")
.fmt(f)
}
}
struct DisplayFromDebug<T>(T);
impl<T> fmt::Display for DisplayFromDebug<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

View File

@ -0,0 +1,320 @@
//! Provides an extension trait for attaching `Section` to error reports.
use crate::{
config::Theme,
eyre::{Report, Result},
Section,
};
use indenter::indented;
use owo_colors::OwoColorize;
use std::fmt::Write;
use std::fmt::{self, Display};
impl Section for Report {
type Return = Report;
fn note<D>(mut self, note: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Note(Box::new(note), handler.theme));
}
self
}
fn with_note<D, F>(mut self, note: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Note(Box::new(note()), handler.theme));
}
self
}
fn warning<D>(mut self, warning: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Warning(Box::new(warning), handler.theme));
}
self
}
fn with_warning<D, F>(mut self, warning: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Warning(Box::new(warning()), handler.theme));
}
self
}
fn suggestion<D>(mut self, suggestion: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Suggestion(Box::new(suggestion), handler.theme));
}
self
}
fn with_suggestion<D, F>(mut self, suggestion: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler
.sections
.push(HelpInfo::Suggestion(Box::new(suggestion()), handler.theme));
}
self
}
fn with_section<D, F>(mut self, section: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
let section = Box::new(section());
handler.sections.push(HelpInfo::Custom(section));
}
self
}
fn section<D>(mut self, section: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
let section = Box::new(section);
handler.sections.push(HelpInfo::Custom(section));
}
self
}
fn error<E2>(mut self, error: E2) -> Self::Return
where
E2: std::error::Error + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
let error = error.into();
handler.sections.push(HelpInfo::Error(error, handler.theme));
}
self
}
fn with_error<E2, F>(mut self, error: F) -> Self::Return
where
F: FnOnce() -> E2,
E2: std::error::Error + Send + Sync + 'static,
{
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
let error = error().into();
handler.sections.push(HelpInfo::Error(error, handler.theme));
}
self
}
fn suppress_backtrace(mut self, suppress: bool) -> Self::Return {
if let Some(handler) = self.handler_mut().downcast_mut::<crate::Handler>() {
handler.suppress_backtrace = suppress;
}
self
}
}
impl<T, E> Section for Result<T, E>
where
E: Into<Report>,
{
type Return = Result<T, Report>;
fn note<D>(self, note: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.note(note))
}
fn with_note<D, F>(self, note: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|error| error.into())
.map_err(|report| report.note(note()))
}
fn warning<D>(self, warning: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.warning(warning))
}
fn with_warning<D, F>(self, warning: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|error| error.into())
.map_err(|report| report.warning(warning()))
}
fn suggestion<D>(self, suggestion: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.suggestion(suggestion))
}
fn with_suggestion<D, F>(self, suggestion: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|error| error.into())
.map_err(|report| report.suggestion(suggestion()))
}
fn with_section<D, F>(self, section: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|error| error.into())
.map_err(|report| report.section(section()))
}
fn section<D>(self, section: D) -> Self::Return
where
D: Display + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.section(section))
}
fn error<E2>(self, error: E2) -> Self::Return
where
E2: std::error::Error + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.error(error))
}
fn with_error<E2, F>(self, error: F) -> Self::Return
where
F: FnOnce() -> E2,
E2: std::error::Error + Send + Sync + 'static,
{
self.map_err(|error| error.into())
.map_err(|report| report.error(error()))
}
fn suppress_backtrace(self, suppress: bool) -> Self::Return {
self.map_err(|error| error.into())
.map_err(|report| report.suppress_backtrace(suppress))
}
}
pub(crate) enum HelpInfo {
Error(Box<dyn std::error::Error + Send + Sync + 'static>, Theme),
Custom(Box<dyn Display + Send + Sync + 'static>),
Note(Box<dyn Display + Send + Sync + 'static>, Theme),
Warning(Box<dyn Display + Send + Sync + 'static>, Theme),
Suggestion(Box<dyn Display + Send + Sync + 'static>, Theme),
}
impl Display for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HelpInfo::Note(note, theme) => {
write!(f, "{}: {}", "Note".style(theme.help_info_note), note)
}
HelpInfo::Warning(warning, theme) => write!(
f,
"{}: {}",
"Warning".style(theme.help_info_warning),
warning
),
HelpInfo::Suggestion(suggestion, theme) => write!(
f,
"{}: {}",
"Suggestion".style(theme.help_info_suggestion),
suggestion
),
HelpInfo::Custom(section) => write!(f, "{}", section),
HelpInfo::Error(error, theme) => {
// a lot here
let errors = std::iter::successors(
Some(error.as_ref() as &(dyn std::error::Error + 'static)),
|e| e.source(),
);
write!(f, "Error:")?;
for (n, error) in errors.enumerate() {
writeln!(f)?;
write!(indented(f).ind(n), "{}", error.style(theme.help_info_error))?;
}
Ok(())
}
}
}
}
impl fmt::Debug for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HelpInfo::Note(note, ..) => f
.debug_tuple("Note")
.field(&format_args!("{}", note))
.finish(),
HelpInfo::Warning(warning, ..) => f
.debug_tuple("Warning")
.field(&format_args!("{}", warning))
.finish(),
HelpInfo::Suggestion(suggestion, ..) => f
.debug_tuple("Suggestion")
.field(&format_args!("{}", suggestion))
.finish(),
HelpInfo::Custom(custom, ..) => f
.debug_tuple("CustomSection")
.field(&format_args!("{}", custom))
.finish(),
HelpInfo::Error(error, ..) => f.debug_tuple("Error").field(error).finish(),
}
}
}

View File

@ -0,0 +1,333 @@
//! Helpers for adding custom sections to error reports
use crate::writers::WriterExt;
use std::fmt::{self, Display};
#[cfg(feature = "issue-url")]
pub(crate) mod github;
pub(crate) mod help;
/// An indented section with a header for an error report
///
/// # Details
///
/// This helper provides two functions to help with constructing nicely formatted
/// error reports. First, it handles indentation of every line of the body for
/// you, and makes sure it is consistent with the rest of color-eyre's output.
/// Second, it omits outputting the header if the body itself is empty,
/// preventing unnecessary pollution of the report for sections with dynamic
/// content.
///
/// # Examples
///
/// ```rust
/// use color_eyre::{eyre::eyre, SectionExt, Section, eyre::Report};
/// use std::process::Command;
/// use tracing::instrument;
///
/// trait Output {
/// fn output2(&mut self) -> Result<String, Report>;
/// }
///
/// impl Output for Command {
/// #[instrument]
/// fn output2(&mut self) -> Result<String, Report> {
/// let output = self.output()?;
///
/// let stdout = String::from_utf8_lossy(&output.stdout);
///
/// if !output.status.success() {
/// let stderr = String::from_utf8_lossy(&output.stderr);
/// Err(eyre!("cmd exited with non-zero status code"))
/// .with_section(move || stdout.trim().to_string().header("Stdout:"))
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))
/// } else {
/// Ok(stdout.into())
/// }
/// }
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct IndentedSection<H, B> {
header: H,
body: B,
}
impl<H, B> fmt::Display for IndentedSection<H, B>
where
H: Display + Send + Sync + 'static,
B: Display + Send + Sync + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
let mut headered = f.header(&self.header);
let headered = headered.ready();
let mut headered = headered.header("\n");
let mut headered = headered.ready();
let mut indented = indenter::indented(&mut headered)
.with_format(indenter::Format::Uniform { indentation: " " });
write!(&mut indented, "{}", self.body)?;
Ok(())
}
}
/// Extension trait for constructing sections with commonly used formats
pub trait SectionExt: Sized {
/// Add a header to a `Section` and indent the body
///
/// # Details
///
/// Bodies are always indented to the same level as error messages and spans.
/// The header is not printed if the display impl of the body produces no
/// output.
///
/// # Examples
///
/// ```rust,no_run
/// use color_eyre::{eyre::eyre, Section, SectionExt, eyre::Report};
///
/// let all_in_header = "header\n body\n body";
/// let report = Err::<(), Report>(eyre!("an error occurred"))
/// .section(all_in_header)
/// .unwrap_err();
///
/// let just_header = "header";
/// let just_body = "body\nbody";
/// let report2 = Err::<(), Report>(eyre!("an error occurred"))
/// .section(just_body.header(just_header))
/// .unwrap_err();
///
/// assert_eq!(format!("{:?}", report), format!("{:?}", report2))
/// ```
fn header<C>(self, header: C) -> IndentedSection<C, Self>
where
C: Display + Send + Sync + 'static;
}
impl<T> SectionExt for T
where
T: Display + Send + Sync + 'static,
{
fn header<C>(self, header: C) -> IndentedSection<C, Self>
where
C: Display + Send + Sync + 'static,
{
IndentedSection { body: self, header }
}
}
/// A helper trait for attaching informational sections to error reports to be
/// displayed after the chain of errors
///
/// # Details
///
/// `color_eyre` provides two types of help text that can be attached to error reports: custom
/// sections and pre-configured sections. Custom sections are added via the `section` and
/// `with_section` methods, and give maximum control over formatting.
///
/// The pre-configured sections are provided via `suggestion`, `warning`, and `note`. These
/// sections are displayed after all other sections with no extra newlines between subsequent Help
/// sections. They consist only of a header portion and are prepended with a colored string
/// indicating the kind of section, e.g. `Note: This might have failed due to ..."
pub trait Section: crate::private::Sealed {
/// The return type of each method after adding context
type Return;
/// Add a section to an error report, to be displayed after the chain of errors.
///
/// # Details
///
/// Sections are displayed in the order they are added to the error report. They are displayed
/// immediately after the `Error:` section and before the `SpanTrace` and `Backtrace` sections.
/// They consist of a header and an optional body. The body of the section is indented by
/// default.
///
/// # Examples
///
/// ```rust,should_panic
/// use color_eyre::{eyre::eyre, eyre::Report, Section};
///
/// Err(eyre!("command failed"))
/// .section("Please report bugs to https://real.url/bugs")?;
/// # Ok::<_, Report>(())
/// ```
fn section<D>(self, section: D) -> Self::Return
where
D: Display + Send + Sync + 'static;
/// Add a Section to an error report, to be displayed after the chain of errors. The closure to
/// create the Section is lazily evaluated only in the case of an error.
///
/// # Examples
///
/// ```rust
/// use color_eyre::{eyre::eyre, eyre::Report, Section, SectionExt};
///
/// # #[cfg(not(miri))]
/// # {
/// let output = std::process::Command::new("ls")
/// .output()?;
///
/// let output = if !output.status.success() {
/// let stderr = String::from_utf8_lossy(&output.stderr);
/// Err(eyre!("cmd exited with non-zero status code"))
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))?
/// } else {
/// String::from_utf8_lossy(&output.stdout)
/// };
///
/// println!("{}", output);
/// # }
/// # Ok::<_, Report>(())
/// ```
fn with_section<D, F>(self, section: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
/// Add an error section to an error report, to be displayed after the primary error message
/// section.
///
/// # Examples
///
/// ```rust,should_panic
/// use color_eyre::{eyre::eyre, eyre::Report, Section};
/// use thiserror::Error;
///
/// #[derive(Debug, Error)]
/// #[error("{0}")]
/// struct StrError(&'static str);
///
/// Err(eyre!("command failed"))
/// .error(StrError("got one error"))
/// .error(StrError("got a second error"))?;
/// # Ok::<_, Report>(())
/// ```
fn error<E>(self, error: E) -> Self::Return
where
E: std::error::Error + Send + Sync + 'static;
/// Add an error section to an error report, to be displayed after the primary error message
/// section. The closure to create the Section is lazily evaluated only in the case of an error.
///
/// # Examples
///
/// ```rust,should_panic
/// use color_eyre::{eyre::eyre, eyre::Report, Section};
/// use thiserror::Error;
///
/// #[derive(Debug, Error)]
/// #[error("{0}")]
/// struct StringError(String);
///
/// Err(eyre!("command failed"))
/// .with_error(|| StringError("got one error".into()))
/// .with_error(|| StringError("got a second error".into()))?;
/// # Ok::<_, Report>(())
/// ```
fn with_error<E, F>(self, error: F) -> Self::Return
where
F: FnOnce() -> E,
E: std::error::Error + Send + Sync + 'static;
/// Add a Note to an error report, to be displayed after the chain of errors.
///
/// # Examples
///
/// ```rust
/// # use std::{error::Error, fmt::{self, Display}};
/// # use color_eyre::eyre::Result;
/// # #[derive(Debug)]
/// # struct FakeErr;
/// # impl Display for FakeErr {
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// # write!(f, "FakeErr")
/// # }
/// # }
/// # impl std::error::Error for FakeErr {}
/// # fn main() -> Result<()> {
/// # fn fallible_fn() -> Result<(), FakeErr> {
/// # Ok(())
/// # }
/// use color_eyre::Section as _;
///
/// fallible_fn().note("This might have failed due to ...")?;
/// # Ok(())
/// # }
/// ```
fn note<D>(self, note: D) -> Self::Return
where
D: Display + Send + Sync + 'static;
/// Add a Note to an error report, to be displayed after the chain of errors. The closure to
/// create the Note is lazily evaluated only in the case of an error.
///
/// # Examples
///
/// ```rust
/// # use std::{error::Error, fmt::{self, Display}};
/// # use color_eyre::eyre::Result;
/// # #[derive(Debug)]
/// # struct FakeErr;
/// # impl Display for FakeErr {
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// # write!(f, "FakeErr")
/// # }
/// # }
/// # impl std::error::Error for FakeErr {}
/// # fn main() -> Result<()> {
/// # fn fallible_fn() -> Result<(), FakeErr> {
/// # Ok(())
/// # }
/// use color_eyre::Section as _;
///
/// fallible_fn().with_note(|| {
/// format!("This might have failed due to ... It has failed {} times", 100)
/// })?;
/// # Ok(())
/// # }
/// ```
fn with_note<D, F>(self, f: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
/// Add a Warning to an error report, to be displayed after the chain of errors.
fn warning<D>(self, warning: D) -> Self::Return
where
D: Display + Send + Sync + 'static;
/// Add a Warning to an error report, to be displayed after the chain of errors. The closure to
/// create the Warning is lazily evaluated only in the case of an error.
fn with_warning<D, F>(self, f: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
/// Add a Suggestion to an error report, to be displayed after the chain of errors.
fn suggestion<D>(self, suggestion: D) -> Self::Return
where
D: Display + Send + Sync + 'static;
/// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure
/// to create the Suggestion is lazily evaluated only in the case of an error.
fn with_suggestion<D, F>(self, f: F) -> Self::Return
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
/// Whether to suppress printing of collected backtrace (if any).
///
/// Useful for reporting "unexceptional" errors for which a backtrace
/// isn't really necessary.
fn suppress_backtrace(self, suppress: bool) -> Self::Return;
}
/// Trait for printing a panic error message for the given PanicInfo
pub trait PanicMessage: Send + Sync + 'static {
/// Display trait equivalent for implementing the display logic
fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result;
}

261
color-eyre/src/writers.rs Normal file
View File

@ -0,0 +1,261 @@
use crate::config::{lib_verbosity, panic_verbosity, Verbosity};
use fmt::Write;
use std::fmt::{self, Display};
#[cfg(feature = "capture-spantrace")]
use tracing_error::{SpanTrace, SpanTraceStatus};
#[allow(explicit_outlives_requirements)]
pub(crate) struct HeaderWriter<'a, H, W>
where
H: ?Sized,
{
inner: W,
header: &'a H,
started: bool,
}
pub(crate) trait WriterExt: Sized {
fn header<H: ?Sized>(self, header: &H) -> HeaderWriter<'_, H, Self>;
}
impl<W> WriterExt for W {
fn header<H: ?Sized>(self, header: &H) -> HeaderWriter<'_, H, Self> {
HeaderWriter {
inner: self,
header,
started: false,
}
}
}
pub(crate) trait DisplayExt: Sized + Display {
fn with_header<H: Display>(self, header: H) -> Header<Self, H>;
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>;
}
impl<T> DisplayExt for T
where
T: Display,
{
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F> {
Footer { body: self, footer }
}
fn with_header<H: Display>(self, header: H) -> Header<Self, H> {
Header {
body: self,
h: header,
}
}
}
pub(crate) struct ReadyHeaderWriter<'a, 'b, H: ?Sized, W>(&'b mut HeaderWriter<'a, H, W>);
impl<'a, H: ?Sized, W> HeaderWriter<'a, H, W> {
pub(crate) fn ready(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> {
self.started = false;
ReadyHeaderWriter(self)
}
pub(crate) fn in_progress(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> {
self.started = true;
ReadyHeaderWriter(self)
}
}
impl<'a, H: ?Sized, W> fmt::Write for ReadyHeaderWriter<'a, '_, H, W>
where
H: Display,
W: fmt::Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
if !self.0.started && !s.is_empty() {
self.0.inner.write_fmt(format_args!("{}", self.0.header))?;
self.0.started = true;
}
self.0.inner.write_str(s)
}
}
pub(crate) struct FooterWriter<W> {
inner: W,
had_output: bool,
}
impl<W> fmt::Write for FooterWriter<W>
where
W: fmt::Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
if !self.had_output && !s.is_empty() {
self.had_output = true;
}
self.inner.write_str(s)
}
}
#[allow(explicit_outlives_requirements)]
pub(crate) struct Footer<B, H>
where
B: Display,
H: Display,
{
body: B,
footer: H,
}
impl<B, H> fmt::Display for Footer<B, H>
where
B: Display,
H: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut inner_f = FooterWriter {
inner: &mut *f,
had_output: false,
};
write!(&mut inner_f, "{}", self.body)?;
if inner_f.had_output {
self.footer.fmt(f)?;
}
Ok(())
}
}
#[allow(explicit_outlives_requirements)]
pub(crate) struct Header<B, H>
where
B: Display,
H: Display,
{
body: B,
h: H,
}
impl<B, H> fmt::Display for Header<B, H>
where
B: Display,
H: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f.header(&self.h).ready(), "{}", self.body)?;
Ok(())
}
}
#[cfg(feature = "capture-spantrace")]
pub(crate) struct FormattedSpanTrace<'a>(pub(crate) &'a SpanTrace);
#[cfg(feature = "capture-spantrace")]
impl fmt::Display for FormattedSpanTrace<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use indenter::indented;
use indenter::Format;
if self.0.status() == SpanTraceStatus::CAPTURED {
write!(
indented(f).with_format(Format::Uniform { indentation: " " }),
"{}",
color_spantrace::colorize(self.0)
)?;
}
Ok(())
}
}
pub(crate) struct EnvSection<'a> {
pub(crate) bt_captured: &'a bool,
#[cfg(feature = "capture-spantrace")]
pub(crate) span_trace: Option<&'a SpanTrace>,
}
impl fmt::Display for EnvSection<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v = if std::thread::panicking() {
panic_verbosity()
} else {
lib_verbosity()
};
write!(f, "{}", BacktraceOmited(!self.bt_captured))?;
let mut separated = HeaderWriter {
inner: &mut *f,
header: &"\n",
started: false,
};
write!(&mut separated.ready(), "{}", SourceSnippets(v))?;
#[cfg(feature = "capture-spantrace")]
write!(
&mut separated.ready(),
"{}",
SpanTraceOmited(self.span_trace)
)?;
Ok(())
}
}
#[cfg(feature = "capture-spantrace")]
struct SpanTraceOmited<'a>(Option<&'a SpanTrace>);
#[cfg(feature = "capture-spantrace")]
impl fmt::Display for SpanTraceOmited<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(span_trace) = self.0 {
if span_trace.status() == SpanTraceStatus::UNSUPPORTED {
writeln!(f, "Warning: SpanTrace capture is Unsupported.")?;
write!(
f,
"Ensure that you've setup a tracing-error ErrorLayer and the semver versions are compatible"
)?;
}
}
Ok(())
}
}
struct BacktraceOmited(bool);
impl fmt::Display for BacktraceOmited {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Print some info on how to increase verbosity.
if self.0 {
write!(
f,
"Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it."
)?;
} else {
// This text only makes sense if frames are displayed.
write!(
f,
"Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering."
)?;
}
Ok(())
}
}
struct SourceSnippets(Verbosity);
impl fmt::Display for SourceSnippets {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 <= Verbosity::Medium {
write!(
f,
"Run with RUST_BACKTRACE=full to include source snippets."
)?;
}
Ok(())
}
}

View File

@ -0,0 +1,15 @@
use color_eyre::eyre;
use eyre::eyre;
#[test]
fn disabled() {
color_eyre::config::HookBuilder::default()
.display_env_section(false)
.install()
.unwrap();
let report = eyre!("error occured");
let report = format!("{:?}", report);
assert!(!report.contains("RUST_BACKTRACE"));
}

View File

@ -0,0 +1,15 @@
use color_eyre::eyre;
use eyre::eyre;
#[test]
fn enabled() {
color_eyre::config::HookBuilder::default()
.display_env_section(true)
.install()
.unwrap();
let report = eyre!("error occured");
let report = format!("{:?}", report);
assert!(report.contains("RUST_BACKTRACE"));
}

View File

@ -0,0 +1,58 @@
0: test
Location:
tests/theme.rs:17
Error:
0: error
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0: theme::get_error with msg="test"
at tests/theme.rs:11
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 5 frames hidden ⋮ 
6: theme::get_error::create_report::hdb41452bef3fc05d
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17
7: theme::get_error::{{closure}}::h739c7fe800e2d03f
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
8: core::option::Option<T>::ok_or_else::hd8e670bbca63e94a
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954
9: theme::get_error::h2f751f4927c6fecb
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
10: theme::test_error_backwards_compatibility::hfc4be9f22c32535c
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43
11: theme::test_error_backwards_compatibility::{{closure}}::hb001a9a908f0f5a4
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41
12: core::ops::function::FnOnce::call_once::he26938a69d361bf6
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
13: core::ops::function::FnOnce::call_once::h83cc023b85256d97
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585
15: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h6b77566b8f386abb
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691
16: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271
17: std::panicking::try::do_call::he67b1e56b423a618
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403
18: std::panicking::try::ha9224adcdd41a723
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367
19: std::panic::catch_unwind::h9111b58ae0b27828
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133
20: test::run_test_in_process::h15b6b7d5919893aa
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608
21: test::run_test::{{closure}}::h8ef02d13d4506b7f
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572
22: test::run_test::{{closure}}::hcd7b423365d0ff7e
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600
 ⋮ 13 frames hidden ⋮ 
Note: note
Warning: warning
Suggestion: suggestion
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,53 @@
0: test
Location:
tests/theme.rs:17
Error:
0: error
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 5 frames hidden ⋮ 
6: theme::get_error::create_report::hf800a973f2100b44
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17
7: theme::get_error::{{closure}}::ha65156cf9648d3e0
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
8: core::option::Option<T>::ok_or_else::h08df66cff4c7bff2
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954
9: theme::get_error::h7c1fce8fa3550ff9
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
10: theme::test_error_backwards_compatibility::h732311d7da5d7160
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43
11: theme::test_error_backwards_compatibility::{{closure}}::h144cea82038adfc7
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41
12: core::ops::function::FnOnce::call_once::h8d0ee3b0b70ed418
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
13: core::ops::function::FnOnce::call_once::h83cc023b85256d97
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585
15: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h6b77566b8f386abb
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691
16: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271
17: std::panicking::try::do_call::he67b1e56b423a618
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403
18: std::panicking::try::ha9224adcdd41a723
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367
19: std::panic::catch_unwind::h9111b58ae0b27828
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133
20: test::run_test_in_process::h15b6b7d5919893aa
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608
21: test::run_test::{{closure}}::h8ef02d13d4506b7f
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572
22: test::run_test::{{closure}}::hcd7b423365d0ff7e
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600
 ⋮ 13 frames hidden ⋮ 
Note: note
Warning: warning
Suggestion: suggestion
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,46 @@
0: test
Error:
0: error
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 5 frames hidden ⋮ 
6: theme::get_error::create_report::h43540daddae98383
at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:17
7: theme::get_error::{{closure}}::h40bbef2f4cd93fab
at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:26
8: core::option::Option<T>::ok_or_else::h8aa47839ff49cfbe
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs:1087
9: theme::get_error::h78b5b4d52bfbbad0
at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:26
10: theme::test_error_backwards_compatibility::h9de398ce80defffa
at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:45
11: theme::test_error_backwards_compatibility::{{closure}}::hbe7b8ad2562c4dc4
at /home/username/dev/rust/eyre/color-eyre/tests/theme.rs:43
12: core::ops::function::FnOnce::call_once::hfc715417a1b707c5
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248
13: core::ops::function::FnOnce::call_once::h9ee1367930602049
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248
14: test::__rust_begin_short_backtrace::h35061c5e0f5ad5d6
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs:572
15: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h98fe3dd14bfe63ea
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/alloc/src/boxed.rs:1940
16: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h3ab012fb764e8d57
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panic/unwind_safe.rs:271
17: std::panicking::try::do_call::h810a5ea64fd04126
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:492
18: std::panicking::try::h0b213f9a8c1fe629
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:456
19: std::panic::catch_unwind::h00f746771ade371f
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:137
20: test::run_test_in_process::h5645647f0d0a3da3
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs:595
 ⋮ 15 frames hidden ⋮ 
Note: note
Warning: warning
Suggestion: suggestion
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,55 @@
0: test
Error:
0: error
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0: theme::get_error with msg="test"
at tests/theme.rs:11
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 5 frames hidden ⋮ 
6: theme::get_error::create_report::h4bc625c000e4636e
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:17
7: theme::get_error::{{closure}}::h3dee499015f52230
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
8: core::option::Option<T>::ok_or_else::h32a80642d5f9cd65
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs:954
9: theme::get_error::hb3756d9f0d65527f
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:25
10: theme::test_error_backwards_compatibility::h69192dd92f3a8a2e
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:43
11: theme::test_error_backwards_compatibility::{{closure}}::hd9459c2e516ade18
at /home/jlusby/git/yaahc/color-eyre/tests/theme.rs:41
12: core::ops::function::FnOnce::call_once::h540507413fe72275
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
13: core::ops::function::FnOnce::call_once::h83cc023b85256d97
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
14: test::__rust_begin_short_backtrace::h7330e4e8b0549e26
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:585
15: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h6b77566b8f386abb
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs:1691
16: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h2ad5de64df41b71c
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs:271
17: std::panicking::try::do_call::he67b1e56b423a618
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:403
18: std::panicking::try::ha9224adcdd41a723
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:367
19: std::panic::catch_unwind::h9111b58ae0b27828
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:133
20: test::run_test_in_process::h15b6b7d5919893aa
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs:608
21: test::run_test::{{closure}}::h8ef02d13d4506b7f
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:572
22: test::run_test::{{closure}}::hcd7b423365d0ff7e
at /rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs:600
 ⋮ 13 frames hidden ⋮ 
Note: note
Warning: warning
Suggestion: suggestion
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,23 @@
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/examples/theme_test_helper`
The application panicked (crashed).
Message: <non string panic payload>
Location: examples/theme_test_helper.rs:37
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0: theme_test_helper::get_error with msg="test"
at examples/theme_test_helper.rs:34
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 6 frames hidden ⋮ 
7: std::panic::panic_any::hd76a7f826307234c
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs:57
8: theme_test_helper::main::h767d3fd6c45048c8
at /home/jlusby/git/yaahc/color-eyre/examples/theme_test_helper.rs:37
9: core::ops::function::FnOnce::call_once::hc5a1cd4127189dad
at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs:227
 ⋮ 15 frames hidden ⋮ 
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,18 @@
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `/home/username/dev/rust/eyre/target/debug/examples/theme_test_helper`
The application panicked (crashed).
Message: <non string panic payload>
Location: color-eyre/examples/theme_test_helper.rs:38
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ⋮ 6 frames hidden ⋮ 
7: std::panic::panic_any::h4a05c03c4d0c389c
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:61
8: theme_test_helper::main::hfc653b28cad3659d
at /home/username/dev/rust/eyre/color-eyre/examples/theme_test_helper.rs:38
9: core::ops::function::FnOnce::call_once::hb0110cdf4417a5ed
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248
 ⋮ 16 frames hidden ⋮ 
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.

View File

@ -0,0 +1,7 @@
use color_eyre::install;
#[test]
fn double_install_should_not_panic() {
install().unwrap();
assert!(install().is_err());
}

View File

@ -0,0 +1,16 @@
#[cfg(feature = "track-caller")]
#[test]
fn disabled() {
use color_eyre::eyre;
use eyre::eyre;
color_eyre::config::HookBuilder::default()
.display_location_section(false)
.install()
.unwrap();
let report = eyre!("error occured");
let report = format!("{:?}", report);
assert!(!report.contains("Location:"));
}

297
color-eyre/tests/theme.rs Normal file
View File

@ -0,0 +1,297 @@
// Note: It's recommended, not to change anything above or below (see big comment below)
use color_eyre::{eyre::Report, Section};
#[rustfmt::skip]
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct TestError(&'static str);
#[rustfmt::skip]
#[tracing::instrument]
fn get_error(msg: &'static str) -> Report {
#[rustfmt::skip]
#[inline(never)]
fn create_report(msg: &'static str) -> Report {
Report::msg(msg)
.note("note")
.warning("warning")
.suggestion("suggestion")
.error(TestError("error"))
}
// Using `Option` to trigger `is_dependency_code`.
// See https://github.com/eyre-rs/color-eyre/blob/4ddaeb2126ed8b14e4e6aa03d7eef49eb8561cf0/src/config.rs#L56
None::<Option<()>>.ok_or_else(|| create_report(msg)).unwrap_err()
}
#[cfg(all(not(feature = "track-caller"), not(feature = "capture-spantrace"),))]
static ERROR_FILE_NAME: &str = "theme_error_control_minimal.txt";
#[cfg(all(feature = "track-caller", not(feature = "capture-spantrace"),))]
static ERROR_FILE_NAME: &str = "theme_error_control_location.txt";
#[cfg(all(not(feature = "track-caller"), feature = "capture-spantrace",))]
static ERROR_FILE_NAME: &str = "theme_error_control_spantrace.txt";
#[cfg(all(feature = "capture-spantrace", feature = "track-caller",))]
static ERROR_FILE_NAME: &str = "theme_error_control.txt";
#[test]
#[cfg(not(miri))]
fn test_error_backwards_compatibility() {
setup();
let error = get_error("test");
/*
Note: If you change anything above this comment, it could make the stored test data invalid (because the structure of the generated error might change). In most cases, small changes shouldn't be a problem, but keep this in mind if you change something and suddenly this test case fails.
The empty lines at the beginning are needed because `color_eyre` sometimes seems to not be able to find the correct line of source and uses the first line of the module (plus the next four lines).
If a change of the code above leads to incompatibility, you therefore have to backport this (changed) file to the version of `color_eyre` that you want to test against and execute it to generate new control test data.
To do this, do the following:
1) Change this file, and if the test now fails do:
2) Checkout the `color_eyre` version from Git that you want to test against
3) Add this test file to '/tests'
4) If `error_file_path` or `panic_file_path` exist (see below), delete these files
5) If you now run this test, it will fail and generate test data files in the current working directory
6) copy these files to `error_file_path` and `panic_file_path` in the current version of `color_eyre` (see the instructions that are printed out in step 5)
Now this test shouldn't fail anymore in the current version.
Alternatively, you also could just regenerate the test data of the current repo (as described above, but without backporting), and use this test data from now on (this makes sense, if you only changed the above code, and nothing else that could lead to the test failing).
# How the tests in this file work:
1) generate a error (for example, with the code above)
2) convert this error to a string
3) load stored error data to compare to (stored in `error_file_path` and `panic_file_path`)
4) if `error_file_path` and/or `panic_file_path` doesn't exist, generate corresponding files in the current working directory and request the user to fix the issue (see below)
5) extract ANSI escaping sequences (of controls and current errors)
6) compare if the current error and the control contains the same ANSI escape sequences
7) If not, fail and show the full strings of the control and the current error
Below you'll find instructions about how to debug failures of the tests in this file
*/
let target = format!("{:?}", error);
test_backwards_compatibility(target, ERROR_FILE_NAME)
}
#[cfg(not(feature = "capture-spantrace"))]
static PANIC_FILE_NAME: &str = "theme_panic_control_no_spantrace.txt";
#[cfg(feature = "capture-spantrace")]
static PANIC_FILE_NAME: &str = "theme_panic_control.txt";
// The following tests the installed panic handler
#[test]
#[allow(unused_mut)]
#[allow(clippy::vec_init_then_push)]
#[cfg(not(miri))]
fn test_panic_backwards_compatibility() {
let mut features: Vec<&str> = vec![];
#[cfg(feature = "capture-spantrace")]
features.push("capture-spantrace");
#[cfg(feature = "issue-url")]
features.push("issue-url");
#[cfg(feature = "track-caller")]
features.push("track-caller");
let features = features.join(",");
let features = if !features.is_empty() {
vec!["--features", &features]
} else {
vec![]
};
let output = std::process::Command::new("cargo")
.args(["run", "--example", "theme_test_helper"])
.arg("--no-default-features")
.args(&features)
.output()
.expect("failed to execute process");
let target = String::from_utf8(output.stderr).expect("failed to convert output to `String`");
println!("{}", target);
test_backwards_compatibility(target, PANIC_FILE_NAME)
}
/// Helper for `test_error` and `test_panic`
fn test_backwards_compatibility(target: String, file_name: &str) {
use ansi_parser::{AnsiParser, AnsiSequence, Output};
use owo_colors::OwoColorize;
use std::{fs, path::Path};
let file_path = ["tests/data/", file_name].concat();
// If `file_path` is missing, save corresponding file to current working directory, and panic with the request to move the file to `file_path`, and to commit it to Git. Being explicit (instead of saving directly to `file_path`) to make sure `file_path` is committed to Git.
if !Path::new(&file_path).is_file() {
std::fs::write(file_name, &target)
.expect("\n\nError saving missing `control target` to a file");
panic!("Required test data missing! Fix this, by moving '{}' to '{}', and commit it to Git.\n\nNote: '{0}' was just generated in the current working directory.\n\n", file_name, file_path);
}
// `unwrap` should never fail with files generated by this function
let control = String::from_utf8(fs::read(file_path).unwrap()).unwrap();
fn split_ansi_output(input: &str) -> (Vec<Output>, Vec<AnsiSequence>) {
let all: Vec<_> = input.ansi_parse().collect();
let ansi: Vec<_> = input
.ansi_parse()
.filter_map(|x| {
if let Output::Escape(ansi) = x {
Some(ansi)
} else {
None
}
})
.collect();
(all, ansi)
}
fn normalize_backtrace(input: &str) -> String {
input
.lines()
.take_while(|v| !v.contains("core::panic"))
.collect::<Vec<_>>()
.join("\n")
}
let control = normalize_backtrace(&control);
let target = normalize_backtrace(&target);
let (_control_tokens, control_ansi) = split_ansi_output(&control);
let (_target_tokens, target_ansi) = split_ansi_output(&target);
fn section(title: &str, content: impl AsRef<str>) -> String {
format!(
"{}\n{}",
format!("-------- {title} --------").red(),
content.as_ref()
)
}
// pretty_assertions::assert_eq!(target, control);
let msg = [
// comment out / un-comment what you need or don't need for debugging (see below for more instructions):
format!("{}", "\x1b[0m\n\nANSI escape sequences are not identical to control!".red()),
// ^ `\x1b[0m` clears previous ANSI escape sequences
section("CONTROL STRING", &control),
// section("CONTROL DEBUG STRING", format!("{control:?}")),
// section("CONTROL ANSI PARSER OUTPUT", format!("{_control_tokens:?}")),
// section("CONTROL ANSI PARSER ANSI", format!("{control_ansi:?}")),
section("CURRENT STRING", &target),
// section("CURRENT DEBUG STRING", format!("{target:?}")),
// section("CURRENT ANSI PARSER OUTPUT", format!("{_target_tokens:?}")),
// section("CURRENT ANSI PARSER ANSI", format!("{target_ansi:?}")),
format!("{}", "See the src of this test for more information about the test and ways to include/exclude debugging information.\n\n".red()),
].join("\n\n");
pretty_assertions::assert_eq!(target_ansi, control_ansi, "{}", &msg);
/*
# Tips for debugging test failures
It's a bit a pain to find the reason for test failures. To make it as easy as possible, I recommend the following workflow:
## Compare the actual errors
1) Run the test in two terminals with "CONTROL STRING" and "CURRENT STRING" active
2) In on terminal have the output of "CONTROL STRING" visible, in the out that of "CURRENT STRING"
3) Make sure, both errors are at the same location of their terminal
4) Now switch between the two terminal rapidly and often. This way it's easy to see changes.
Note that we only compare ANSI escape sequences so if the text changes, that is not a problem.
A problem would it be, if there is a new section of content (which might contain new ANSI escape sequences). This could happen, for example, if the current code produces warnings, etc. (especially, with the panic handler test).
## Compare `ansi_parser` tokens
If you fixed all potential problems above, and the test still failes, compare the actual ANSI escape sequences:
1) Activate "CURRENT ANSI PARSER OUTPUT" and "CURRENT ANSI PARSER OUTPUT" above
2) Copy this output to a text editor and replace all "), " with ")," + a newline (this way every token is on its own line)
3) Compare this new output with a diff tool (https://meldmerge.org/ is a nice option with a GUI)
With this approach, you should see what has changed. Just remember that we only compare the ANSI escape sequences, text is skipped. With "CURRENT ANSI PARSER OUTPUT" and "CURRENT ANSI PARSER OUTPUT", however, text tokens are shown as well (to make it easier to figure out the source of ANSI escape sequences.)
*/
}
fn setup() {
std::env::set_var("RUST_LIB_BACKTRACE", "1");
#[cfg(feature = "capture-spantrace")]
{
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(tracing_error::ErrorLayer::default())
.init();
}
color_eyre::install().expect("Failed to install `color_eyre`");
/*
# Easy way to test styles
1) uncomment the last line
2) activate the following code
3) change the styles
4) run this test via `cargo test test_error_backwards_compatibility --test styles`
5) your new styled error will be below the output "CURRENT STRING ="
6) if there is not such output, search for "CURRENT STRING =" above, and activate the line
7) if you are interested in running this test for actual testing this crate, don't forget to uncomment the code below, activate the above line
*/
/*
use owo_colors::style;
let styles = color_eyre::config::Styles::dark()
// ^ or, instead of `dark`, use `new` for blank styles or `light` if you what to derive from a light theme. Now configure your styles (see the docs for all options):
.line_number(style().blue())
.help_info_suggestion(style().red());
color_eyre::config::HookBuilder::new()
.styles(styles)
.install()
.expect("Failed to install `color_eyre`");
*/
}

23
color-eyre/tests/wasm.rs Normal file
View File

@ -0,0 +1,23 @@
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen_test::wasm_bindgen_test]
pub fn color_eyre_simple() {
use color_eyre::eyre::WrapErr;
use color_eyre::*;
install().expect("Failed to install color_eyre");
let err_str = format!(
"{:?}",
Err::<(), Report>(eyre::eyre!("Base Error"))
.note("A note")
.suggestion("A suggestion")
.wrap_err("A wrapped error")
.unwrap_err()
);
// Print it out so if people run with `-- --nocapture`, they
// can see the full message.
println!("Error String is:\n\n{}", err_str);
assert!(err_str.contains("A wrapped error"));
assert!(err_str.contains("A suggestion"));
assert!(err_str.contains("A note"));
assert!(err_str.contains("Base Error"));
}

View File

@ -1,12 +1,15 @@
color-spantrace
===============
# color-spantrace
[![Build Status][actions-badge]][actions-url]
[![Latest Version](https://img.shields.io/crates/v/color-spantrace.svg)](https://crates.io/crates/color-spantrace)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/color-spantrace)
[![Latest Version][version-badge]][version-url]
[![Rust Documentation][docs-badge]][docs-url]
[actions-badge]: https://github.com/eyre-rs/color-spantrace/workflows/Continuous%20integration/badge.svg
[actions-url]: https://github.com/eyre-rs/color-spantrace/actions?query=workflow%3A%22Continuous+integration%22
[actions-badge]: https://github.com/eyre-rs/eyre/workflows/Continuous%20integration/badge.svg
[actions-url]: https://github.com/eyre-rs/eyre/actions?query=workflow%3A%22Continuous+integration%22
[version-badge]: https://img.shields.io/crates/v/color-spantrace.svg
[version-url]: https://crates.io/crates/color-spantrace
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-url]: https://docs.rs/color-spantrace
A rust library for colorizing [`tracing_error::SpanTrace`] objects in the style
of [`color-backtrace`].

View File

@ -1,7 +1,5 @@
use std::panic::Location;
use eyre::WrapErr;
struct LocationHandler {
actual: Option<&'static str>,
expected: &'static str,
@ -46,6 +44,7 @@ fn test_wrap_err() {
Box::new(LocationHandler::new(expected_location))
}));
use eyre::WrapErr;
let err = read_path("totally_fake_path")
.wrap_err("oopsie")
.unwrap_err();
@ -75,6 +74,7 @@ fn test_wrap_err_with() {
Box::new(LocationHandler::new(expected_location))
}));
use eyre::WrapErr;
let err = read_path("totally_fake_path")
.wrap_err_with(|| "oopsie")
.unwrap_err();
@ -83,7 +83,6 @@ fn test_wrap_err_with() {
println!("{:?}", err);
}
#[cfg(feature = "anyhow")]
#[test]
fn test_option_ok_or_eyre() {
let _ = eyre::set_hook(Box::new(|_e| {
@ -108,6 +107,7 @@ fn test_context() {
Box::new(LocationHandler::new(expected_location))
}));
use eyre::WrapErr;
let err = read_path("totally_fake_path")
.context("oopsie")
.unwrap_err();
@ -126,6 +126,7 @@ fn test_with_context() {
Box::new(LocationHandler::new(expected_location))
}));
use eyre::WrapErr;
let err = read_path("totally_fake_path")
.with_context(|| "oopsie")
.unwrap_err();