mirror of
https://github.com/eyre-rs/eyre.git
synced 2025-09-27 04:50:50 +00:00
Merge branch 'master' into fix-ambiguous-methods
This commit is contained in:
commit
2b32acdd03
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
15
README.md
15
README.md
@ -1,13 +1,16 @@
|
||||
eyre
|
||||
====
|
||||
# eyre
|
||||
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[](https://crates.io/crates/eyre)
|
||||
[](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
135
color-eyre/.github/workflows/ci.yml
vendored
Normal 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
88
color-eyre/CHANGELOG.md
Normal 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
83
color-eyre/Cargo.toml
Normal 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
1
color-eyre/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-APACHE
|
1
color-eyre/LICENSE-MIT
Symbolic link
1
color-eyre/LICENSE-MIT
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-MIT
|
231
color-eyre/README.md
Normal file
231
color-eyre/README.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
Running `RUST_LIB_BACKTRACE=1 cargo run --example usage` tells `color-eyre` to use the short
|
||||
format, which additionally capture a [`backtrace::Backtrace`]:
|
||||
|
||||

|
||||
|
||||
<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.
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
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>
|
61
color-eyre/examples/custom_filter.rs
Normal file
61
color-eyre/examples/custom_filter.rs
Normal 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")
|
||||
}
|
68
color-eyre/examples/custom_section.rs
Normal file
68
color-eyre/examples/custom_section.rs
Normal 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")
|
||||
}
|
56
color-eyre/examples/debug_perf.rs
Normal file
56
color-eyre/examples/debug_perf.rs
Normal 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();
|
||||
}
|
74
color-eyre/examples/github_issue.rs
Normal file
74
color-eyre/examples/github_issue.rs
Normal 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")
|
||||
}
|
55
color-eyre/examples/multiple_errors.rs
Normal file
55
color-eyre/examples/multiple_errors.rs
Normal 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,
|
||||
}
|
50
color-eyre/examples/panic_compose.rs
Normal file
50
color-eyre/examples/panic_compose.rs
Normal 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")
|
||||
}
|
46
color-eyre/examples/panic_hook.rs
Normal file
46
color-eyre/examples/panic_hook.rs
Normal 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")
|
||||
}
|
62
color-eyre/examples/theme.rs
Normal file
62
color-eyre/examples/theme.rs
Normal 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`");
|
||||
}
|
62
color-eyre/examples/theme_test_helper.rs
Normal file
62
color-eyre/examples/theme_test_helper.rs
Normal 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`");
|
||||
}
|
43
color-eyre/examples/usage.rs
Normal file
43
color-eyre/examples/usage.rs
Normal 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")
|
||||
}
|
BIN
color-eyre/pictures/custom_section.png
Normal file
BIN
color-eyre/pictures/custom_section.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
BIN
color-eyre/pictures/full.png
Normal file
BIN
color-eyre/pictures/full.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 253 KiB |
BIN
color-eyre/pictures/minimal.png
Normal file
BIN
color-eyre/pictures/minimal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
BIN
color-eyre/pictures/short.png
Normal file
BIN
color-eyre/pictures/short.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 149 KiB |
13
color-eyre/scripts/default.nix
Normal file
13
color-eyre/scripts/default.nix
Normal 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 ];
|
||||
}
|
126
color-eyre/scripts/fix_html_examples.py
Executable file
126
color-eyre/scripts/fix_html_examples.py
Executable 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
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
25
color-eyre/src/fmt.rs
Normal 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
188
color-eyre/src/handler.rs
Normal 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
460
color-eyre/src/lib.rs
Normal 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="cat" "fake_file"</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="fake_file"</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="fake_file"<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="fake_file"</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="fake_file"<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="fake_file"</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="fake_file"<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="fake_file"</span>
|
||||
//! at <span style="color: #75507B">examples/usage.rs</span>:<span style="color: #75507B">32</span>
|
||||
//! 30 │ }
|
||||
//! 31 │
|
||||
//! <b> 32 > #[instrument]</b>
|
||||
//! 33 │ fn read_file(path: &str) -> Result<(), Report> {
|
||||
//! 34 │ info!("Reading file");
|
||||
//! 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 > #[instrument]</b>
|
||||
//! 39 │ fn read_config() -> Result<(), Report> {
|
||||
//! 40 │ read_file("fake_file")
|
||||
//!
|
||||
//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 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: &str) -> Result<(), Report> {
|
||||
//! 34 │ info!("Reading file");
|
||||
//! <span style="color: #D3D7CF"><b> 35 > 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() -> Result<(), Report> {
|
||||
//! <span style="color: #D3D7CF"><b> 40 > read_file("fake_file")</b></span>
|
||||
//! 41 │ .wrap_err("Unable to read config")
|
||||
//! 42 │ .suggestion("try using a file that exists next time")
|
||||
//! 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 > 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="cat" "fake_file"</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="fake_file"</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()
|
||||
}
|
5
color-eyre/src/private.rs
Normal file
5
color-eyre/src/private.rs
Normal 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 {}
|
190
color-eyre/src/section/github.rs
Normal file
190
color-eyre/src/section/github.rs
Normal 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)
|
||||
}
|
||||
}
|
320
color-eyre/src/section/help.rs
Normal file
320
color-eyre/src/section/help.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
333
color-eyre/src/section/mod.rs
Normal file
333
color-eyre/src/section/mod.rs
Normal 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
261
color-eyre/src/writers.rs
Normal 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(())
|
||||
}
|
||||
}
|
15
color-eyre/tests/bt_disabled.rs
Normal file
15
color-eyre/tests/bt_disabled.rs
Normal 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"));
|
||||
}
|
15
color-eyre/tests/bt_enabled.rs
Normal file
15
color-eyre/tests/bt_enabled.rs
Normal 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"));
|
||||
}
|
58
color-eyre/tests/data/theme_error_control.txt
Normal file
58
color-eyre/tests/data/theme_error_control.txt
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
0: [91mtest[0m
|
||||
|
||||
Location:
|
||||
[35mtests/theme.rs[0m:[35m17[0m
|
||||
|
||||
Error:
|
||||
0: [91merror[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
0: [91mtheme[0m[91m::[0m[91mget_error[0m with [96m[3mmsg[0m[2m=[0m"test"[0m
|
||||
at [35mtests/theme.rs[0m:[35m11[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 5 frames hidden ⋮ [0m
|
||||
6: [91mtheme::get_error::create_report[0m[90m::hdb41452bef3fc05d[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m17[0m
|
||||
7: [91mtheme::get_error::{{closure}}[0m[90m::h739c7fe800e2d03f[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
8: [32mcore::option::Option<T>::ok_or_else[0m[90m::hd8e670bbca63e94a[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs[0m:[35m954[0m
|
||||
9: [91mtheme::get_error[0m[90m::h2f751f4927c6fecb[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
10: [91mtheme::test_error_backwards_compatibility[0m[90m::hfc4be9f22c32535c[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m43[0m
|
||||
11: [91mtheme::test_error_backwards_compatibility::{{closure}}[0m[90m::hb001a9a908f0f5a4[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m41[0m
|
||||
12: [32mcore::ops::function::FnOnce::call_once[0m[90m::he26938a69d361bf6[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
13: [32mcore::ops::function::FnOnce::call_once[0m[90m::h83cc023b85256d97[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
14: [32mtest::__rust_begin_short_backtrace[0m[90m::h7330e4e8b0549e26[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m585[0m
|
||||
15: [32m<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once[0m[90m::h6b77566b8f386abb[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs[0m:[35m1691[0m
|
||||
16: [32m<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once[0m[90m::h2ad5de64df41b71c[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs[0m:[35m271[0m
|
||||
17: [32mstd::panicking::try::do_call[0m[90m::he67b1e56b423a618[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m403[0m
|
||||
18: [32mstd::panicking::try[0m[90m::ha9224adcdd41a723[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m367[0m
|
||||
19: [32mstd::panic::catch_unwind[0m[90m::h9111b58ae0b27828[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs[0m:[35m133[0m
|
||||
20: [32mtest::run_test_in_process[0m[90m::h15b6b7d5919893aa[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m608[0m
|
||||
21: [32mtest::run_test::{{closure}}[0m[90m::h8ef02d13d4506b7f[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m572[0m
|
||||
22: [32mtest::run_test::{{closure}}[0m[90m::hcd7b423365d0ff7e[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m600[0m
|
||||
[96m ⋮ 13 frames hidden ⋮ [0m
|
||||
|
||||
[96mNote[0m: note
|
||||
[93mWarning[0m: warning
|
||||
[96mSuggestion[0m: suggestion
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
53
color-eyre/tests/data/theme_error_control_location.txt
Normal file
53
color-eyre/tests/data/theme_error_control_location.txt
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
0: [91mtest[0m
|
||||
|
||||
Location:
|
||||
[35mtests/theme.rs[0m:[35m17[0m
|
||||
|
||||
Error:
|
||||
0: [91merror[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 5 frames hidden ⋮ [0m
|
||||
6: [91mtheme::get_error::create_report[0m[90m::hf800a973f2100b44[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m17[0m
|
||||
7: [91mtheme::get_error::{{closure}}[0m[90m::ha65156cf9648d3e0[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
8: [32mcore::option::Option<T>::ok_or_else[0m[90m::h08df66cff4c7bff2[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs[0m:[35m954[0m
|
||||
9: [91mtheme::get_error[0m[90m::h7c1fce8fa3550ff9[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
10: [91mtheme::test_error_backwards_compatibility[0m[90m::h732311d7da5d7160[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m43[0m
|
||||
11: [91mtheme::test_error_backwards_compatibility::{{closure}}[0m[90m::h144cea82038adfc7[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m41[0m
|
||||
12: [32mcore::ops::function::FnOnce::call_once[0m[90m::h8d0ee3b0b70ed418[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
13: [32mcore::ops::function::FnOnce::call_once[0m[90m::h83cc023b85256d97[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
14: [32mtest::__rust_begin_short_backtrace[0m[90m::h7330e4e8b0549e26[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m585[0m
|
||||
15: [32m<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once[0m[90m::h6b77566b8f386abb[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs[0m:[35m1691[0m
|
||||
16: [32m<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once[0m[90m::h2ad5de64df41b71c[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs[0m:[35m271[0m
|
||||
17: [32mstd::panicking::try::do_call[0m[90m::he67b1e56b423a618[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m403[0m
|
||||
18: [32mstd::panicking::try[0m[90m::ha9224adcdd41a723[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m367[0m
|
||||
19: [32mstd::panic::catch_unwind[0m[90m::h9111b58ae0b27828[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs[0m:[35m133[0m
|
||||
20: [32mtest::run_test_in_process[0m[90m::h15b6b7d5919893aa[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m608[0m
|
||||
21: [32mtest::run_test::{{closure}}[0m[90m::h8ef02d13d4506b7f[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m572[0m
|
||||
22: [32mtest::run_test::{{closure}}[0m[90m::hcd7b423365d0ff7e[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m600[0m
|
||||
[96m ⋮ 13 frames hidden ⋮ [0m
|
||||
|
||||
[96mNote[0m: note
|
||||
[93mWarning[0m: warning
|
||||
[96mSuggestion[0m: suggestion
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
46
color-eyre/tests/data/theme_error_control_minimal.txt
Normal file
46
color-eyre/tests/data/theme_error_control_minimal.txt
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
0: [91mtest[0m
|
||||
|
||||
Error:
|
||||
0: [91merror[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 5 frames hidden ⋮ [0m
|
||||
6: [91mtheme::get_error::create_report[0m[90m::h43540daddae98383[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/tests/theme.rs[0m:[35m17[0m
|
||||
7: [91mtheme::get_error::{{closure}}[0m[90m::h40bbef2f4cd93fab[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/tests/theme.rs[0m:[35m26[0m
|
||||
8: [32mcore::option::Option<T>::ok_or_else[0m[90m::h8aa47839ff49cfbe[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs[0m:[35m1087[0m
|
||||
9: [91mtheme::get_error[0m[90m::h78b5b4d52bfbbad0[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/tests/theme.rs[0m:[35m26[0m
|
||||
10: [91mtheme::test_error_backwards_compatibility[0m[90m::h9de398ce80defffa[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/tests/theme.rs[0m:[35m45[0m
|
||||
11: [91mtheme::test_error_backwards_compatibility::{{closure}}[0m[90m::hbe7b8ad2562c4dc4[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/tests/theme.rs[0m:[35m43[0m
|
||||
12: [32mcore::ops::function::FnOnce::call_once[0m[90m::hfc715417a1b707c5[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs[0m:[35m248[0m
|
||||
13: [32mcore::ops::function::FnOnce::call_once[0m[90m::h9ee1367930602049[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs[0m:[35m248[0m
|
||||
14: [32mtest::__rust_begin_short_backtrace[0m[90m::h35061c5e0f5ad5d6[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs[0m:[35m572[0m
|
||||
15: [32m<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once[0m[90m::h98fe3dd14bfe63ea[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/alloc/src/boxed.rs[0m:[35m1940[0m
|
||||
16: [32m<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once[0m[90m::h3ab012fb764e8d57[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panic/unwind_safe.rs[0m:[35m271[0m
|
||||
17: [32mstd::panicking::try::do_call[0m[90m::h810a5ea64fd04126[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs[0m:[35m492[0m
|
||||
18: [32mstd::panicking::try[0m[90m::h0b213f9a8c1fe629[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs[0m:[35m456[0m
|
||||
19: [32mstd::panic::catch_unwind[0m[90m::h00f746771ade371f[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs[0m:[35m137[0m
|
||||
20: [32mtest::run_test_in_process[0m[90m::h5645647f0d0a3da3[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/test/src/lib.rs[0m:[35m595[0m
|
||||
[96m ⋮ 15 frames hidden ⋮ [0m
|
||||
|
||||
[96mNote[0m: note
|
||||
[93mWarning[0m: warning
|
||||
[96mSuggestion[0m: suggestion
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
55
color-eyre/tests/data/theme_error_control_spantrace.txt
Normal file
55
color-eyre/tests/data/theme_error_control_spantrace.txt
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
0: [91mtest[0m
|
||||
|
||||
Error:
|
||||
0: [91merror[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
0: [91mtheme[0m[91m::[0m[91mget_error[0m with [96m[3mmsg[0m[2m=[0m"test"[0m
|
||||
at [35mtests/theme.rs[0m:[35m11[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 5 frames hidden ⋮ [0m
|
||||
6: [91mtheme::get_error::create_report[0m[90m::h4bc625c000e4636e[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m17[0m
|
||||
7: [91mtheme::get_error::{{closure}}[0m[90m::h3dee499015f52230[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
8: [32mcore::option::Option<T>::ok_or_else[0m[90m::h32a80642d5f9cd65[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/option.rs[0m:[35m954[0m
|
||||
9: [91mtheme::get_error[0m[90m::hb3756d9f0d65527f[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m25[0m
|
||||
10: [91mtheme::test_error_backwards_compatibility[0m[90m::h69192dd92f3a8a2e[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m43[0m
|
||||
11: [91mtheme::test_error_backwards_compatibility::{{closure}}[0m[90m::hd9459c2e516ade18[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/tests/theme.rs[0m:[35m41[0m
|
||||
12: [32mcore::ops::function::FnOnce::call_once[0m[90m::h540507413fe72275[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
13: [32mcore::ops::function::FnOnce::call_once[0m[90m::h83cc023b85256d97[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
14: [32mtest::__rust_begin_short_backtrace[0m[90m::h7330e4e8b0549e26[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m585[0m
|
||||
15: [32m<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once[0m[90m::h6b77566b8f386abb[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/alloc/src/boxed.rs[0m:[35m1691[0m
|
||||
16: [32m<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once[0m[90m::h2ad5de64df41b71c[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panic/unwind_safe.rs[0m:[35m271[0m
|
||||
17: [32mstd::panicking::try::do_call[0m[90m::he67b1e56b423a618[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m403[0m
|
||||
18: [32mstd::panicking::try[0m[90m::ha9224adcdd41a723[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs[0m:[35m367[0m
|
||||
19: [32mstd::panic::catch_unwind[0m[90m::h9111b58ae0b27828[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs[0m:[35m133[0m
|
||||
20: [32mtest::run_test_in_process[0m[90m::h15b6b7d5919893aa[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/test/src/lib.rs[0m:[35m608[0m
|
||||
21: [32mtest::run_test::{{closure}}[0m[90m::h8ef02d13d4506b7f[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m572[0m
|
||||
22: [32mtest::run_test::{{closure}}[0m[90m::hcd7b423365d0ff7e[0m
|
||||
at [35m/rustc/7b4d9e155fec06583c763f176fc432dc779f1fc6/library/test/src/lib.rs[0m:[35m600[0m
|
||||
[96m ⋮ 13 frames hidden ⋮ [0m
|
||||
|
||||
[96mNote[0m: note
|
||||
[93mWarning[0m: warning
|
||||
[96mSuggestion[0m: suggestion
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
23
color-eyre/tests/data/theme_panic_control.txt
Normal file
23
color-eyre/tests/data/theme_panic_control.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
|
||||
Running `target/debug/examples/theme_test_helper`
|
||||
[31mThe application panicked (crashed).[0m
|
||||
Message: [36m<non string panic payload>[0m
|
||||
Location: [35mexamples/theme_test_helper.rs[0m:[35m37[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
0: [91mtheme_test_helper[0m[91m::[0m[91mget_error[0m with [96m[3mmsg[0m[2m=[0m"test"[0m
|
||||
at [35mexamples/theme_test_helper.rs[0m:[35m34[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 6 frames hidden ⋮ [0m
|
||||
7: [32mstd::panic::panic_any[0m[90m::hd76a7f826307234c[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panic.rs[0m:[35m57[0m
|
||||
8: [91mtheme_test_helper::main[0m[90m::h767d3fd6c45048c8[0m
|
||||
at [35m/home/jlusby/git/yaahc/color-eyre/examples/theme_test_helper.rs[0m:[35m37[0m
|
||||
9: [32mcore::ops::function::FnOnce::call_once[0m[90m::hc5a1cd4127189dad[0m
|
||||
at [35m/rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/ops/function.rs[0m:[35m227[0m
|
||||
[96m ⋮ 15 frames hidden ⋮ [0m
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
18
color-eyre/tests/data/theme_panic_control_no_spantrace.txt
Normal file
18
color-eyre/tests/data/theme_panic_control_no_spantrace.txt
Normal 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`
|
||||
[31mThe application panicked (crashed).[0m
|
||||
Message: [36m<non string panic payload>[0m
|
||||
Location: [35mcolor-eyre/examples/theme_test_helper.rs[0m:[35m38[0m
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[96m ⋮ 6 frames hidden ⋮ [0m
|
||||
7: [32mstd::panic::panic_any[0m[90m::h4a05c03c4d0c389c[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs[0m:[35m61[0m
|
||||
8: [91mtheme_test_helper::main[0m[90m::hfc653b28cad3659d[0m
|
||||
at [35m/home/username/dev/rust/eyre/color-eyre/examples/theme_test_helper.rs[0m:[35m38[0m
|
||||
9: [32mcore::ops::function::FnOnce::call_once[0m[90m::hb0110cdf4417a5ed[0m
|
||||
at [35m/rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs[0m:[35m248[0m
|
||||
[96m ⋮ 16 frames hidden ⋮ [0m
|
||||
|
||||
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
|
||||
Run with RUST_BACKTRACE=full to include source snippets.
|
7
color-eyre/tests/install.rs
Normal file
7
color-eyre/tests/install.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use color_eyre::install;
|
||||
|
||||
#[test]
|
||||
fn double_install_should_not_panic() {
|
||||
install().unwrap();
|
||||
assert!(install().is_err());
|
||||
}
|
16
color-eyre/tests/location_disabled.rs
Normal file
16
color-eyre/tests/location_disabled.rs
Normal 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
297
color-eyre/tests/theme.rs
Normal 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
23
color-eyre/tests/wasm.rs
Normal 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"));
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
color-spantrace
|
||||
===============
|
||||
# color-spantrace
|
||||
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[](https://crates.io/crates/color-spantrace)
|
||||
[](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`].
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user