Simplify sections features (#22)

* Simplify sections features

* cleanup docs a bit

* update docs and switch context to handler
This commit is contained in:
Jane Lusby 2020-06-02 18:26:02 -07:00 committed by GitHub
parent fe284a81b1
commit 5b6507d455
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 324 additions and 496 deletions

View File

@ -4,7 +4,7 @@ version = "0.3.2"
authors = ["Jane Lusby <jlusby@yaah.dev>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "A custom context for the `eyre` crate for colorful error reports, suggestions, and `tracing-error` support."
description = "An error report handler for panics and eyre::Reports for colorful, consistent, and well formatted error reports for all kinds of errors."
repository = "https://github.com/yaahc/color-eyre"
documentation = "https://docs.rs/color-eyre"
readme = "README.md"
@ -16,7 +16,7 @@ default = ["capture-spantrace"]
capture-spantrace = ["tracing-error", "color-spantrace"]
[dependencies]
eyre = "0.4.2"
eyre = "0.4.3"
tracing-error = { version = "0.1.2", optional = true }
color-backtrace = "0.4.0"
backtrace = "0.3.48"

126
README.md
View File

@ -11,8 +11,8 @@
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-url]: https://docs.rs/color-eyre
A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
sections, [`tracing-error`] support, and backtraces on stable.
An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
formatted error reports for all kinds of errors.
## TLDR
@ -82,53 +82,7 @@ opt-level = 3
### 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 following example, taken from
[`examples/usage.rs`]:
```rust
use color_eyre::{Help, Report};
use eyre::WrapErr;
use tracing::{info, instrument};
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
Ok(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")
}
```
and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
---
@ -154,9 +108,10 @@ error originated from, assuming it can find them on the disk.
### Custom `Section`s for error reports via [`Help`] 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
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
@ -179,16 +134,8 @@ impl Output for Command {
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:"
.skip_if(|| stdout.is_empty())
.body(stdout.trim().to_string())
})
.with_section(move || {
"Stderr:"
.skip_if(|| stderr.is_empty())
.body(stderr.trim().to_string())
})
.with_section(move || stdout.trim().to_string().header("Stdout:"))
.with_section(move || stderr.trim().to_string().header("Stderr:"))
} else {
Ok(stdout.into())
}
@ -198,26 +145,25 @@ impl Output for Command {
---
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`. Each section
includes a short header and a body that contains the actual output. Additionally these sections
use `skip_if` to tell the report not to include them if there was no output, preventing empty
sections from polluting the end report.
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:
Running `cargo run --example custom_section` shows us how these sections are
included in the output:
![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png)
Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
being empty and is skipped in the final report. This gives us a short and concise error report
indicating exactly what was attempted and how it failed.
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
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
@ -228,41 +174,43 @@ For an example of how to aggregate errors check out [`examples/multiple_errors.r
### 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.
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`].
## Explanation
This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
and a pair of type aliases for setting this context type as the parameter of
[`eyre::Report`].
This crate works by defining a `Handler` type which implements
[`eyre::EyreHandler`] and a pair of type aliases for setting this handler
type as the parameter of [`eyre::Report`].
```rust
use color_eyre::Context;
use color_eyre::Handler;
pub type Report = eyre::Report<Context>;
pub type Report = eyre::Report<Handler>;
pub type Result<T, E = Report> = core::result::Result<T, E>;
```
Please refer to the [`Context`] type's docs for more details about its feature set.
Please refer to the [`Handler`] type's docs for more details about its feature set.
[`eyre`]: https://docs.rs/eyre
[`tracing-error`]: https://docs.rs/tracing-error
[`color-backtrace`]: https://docs.rs/color-backtrace
[`eyre::EyreContext`]: https://docs.rs/eyre/*/eyre/trait.EyreContext.html
[`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
[`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
[`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
[`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.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

View File

@ -17,16 +17,8 @@ impl Output for Command {
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:"
.skip_if(|| stdout.is_empty())
.body(stdout.trim().to_string())
})
.with_section(move || {
"Stderr:"
.skip_if(|| stderr.is_empty())
.body(stderr.trim().to_string())
})
.with_section(move || stdout.trim().to_string().header("Stdout:"))
.with_section(move || stderr.trim().to_string().header("Stderr:"))
} else {
Ok(stdout.into())
}
@ -61,7 +53,7 @@ fn install_tracing() {
#[instrument]
fn read_file(path: &str) -> Result<String, Report> {
Command::new("cat").arg("fake_file").output2()
Command::new("cat").arg(path).output2()
}
#[instrument]

View File

@ -26,7 +26,7 @@ fn time_report_inner() {
.suggestion("try using a file that exists next time")
.unwrap_err();
let _ = println!("Error: {:?}", report);
println!("Error: {:?}", report);
drop(report);
let end = std::time::Instant::now();

View File

@ -1,5 +1,5 @@
//! A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
//! sections, [`tracing-error`] support, and backtraces on stable.
//! An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
//! formatted error reports for all kinds of errors.
//!
//! ## TLDR
//!
@ -69,53 +69,7 @@
//! ### 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 following example, taken from
//! [`examples/usage.rs`]:
//!
//! ```rust,should_panic
//! use color_eyre::{Help, Report};
//! use eyre::WrapErr;
//! use tracing::{info, instrument};
//!
//! #[instrument]
//! fn main() -> Result<(), Report> {
//! #[cfg(feature = "capture-spantrace")]
//! install_tracing();
//!
//! Ok(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")
//! }
//! ```
//! and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
//!
//! ---
//!
@ -141,9 +95,10 @@
//!
//! ### Custom `Section`s for error reports via [`Help`] 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
//! 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
@ -166,16 +121,8 @@
//! 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:"
//! .skip_if(|| stdout.is_empty())
//! .body(stdout.trim().to_string())
//! })
//! .with_section(move || {
//! "Stderr:"
//! .skip_if(|| stderr.is_empty())
//! .body(stderr.trim().to_string())
//! })
//! .with_section(move || stdout.trim().to_string().header("Stdout:"))
//! .with_section(move || stderr.trim().to_string().header("Stderr:"))
//! } else {
//! Ok(stdout.into())
//! }
@ -185,26 +132,25 @@
//!
//! ---
//!
//! 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`. Each section
//! includes a short header and a body that contains the actual output. Additionally these sections
//! use `skip_if` to tell the report not to include them if there was no output, preventing empty
//! sections from polluting the end report.
//! 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:
//! Running `cargo run --example custom_section` shows us how these sections are
//! included in the output:
//!
//! ![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png)
//!
//! Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
//! being empty and is skipped in the final report. This gives us a short and concise error report
//! indicating exactly what was attempted and how it failed.
//! 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
//! 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
@ -215,41 +161,43 @@
//!
//! ### 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.
//! 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`].
//!
//! ## Explanation
//!
//! This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
//! and a pair of type aliases for setting this context type as the parameter of
//! [`eyre::Report`].
//! This crate works by defining a `Handler` type which implements
//! [`eyre::EyreHandler`] and a pair of type aliases for setting this handler
//! type as the parameter of [`eyre::Report`].
//!
//! ```rust
//! use color_eyre::Context;
//! use color_eyre::Handler;
//!
//! pub type Report = eyre::Report<Context>;
//! pub type Report = eyre::Report<Handler>;
//! pub type Result<T, E = Report> = core::result::Result<T, E>;
//! ```
//!
//! Please refer to the [`Context`] type's docs for more details about its feature set.
//! Please refer to the [`Handler`] type's docs for more details about its feature set.
//!
//! [`eyre`]: https://docs.rs/eyre
//! [`tracing-error`]: https://docs.rs/tracing-error
//! [`color-backtrace`]: https://docs.rs/color-backtrace
//! [`eyre::EyreContext`]: https://docs.rs/eyre/*/eyre/trait.EyreContext.html
//! [`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
//! [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
//! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
//! [`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.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
@ -279,13 +227,14 @@
unused_parens,
while_true
)]
use crate::writers::HeaderWriter;
use ansi_term::Color::*;
use backtrace::Backtrace;
pub use color_backtrace::BacktracePrinter;
use eyre::*;
use indenter::{indented, Format};
use once_cell::sync::OnceCell;
use section::Order;
use section::help::HelpInfo;
pub use section::{help::Help, Section, SectionExt};
#[cfg(feature = "capture-spantrace")]
use std::error::Error;
@ -296,12 +245,15 @@ use std::{
};
#[cfg(feature = "capture-spantrace")]
use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus};
#[doc(hidden)]
pub use Handler as Context;
pub mod section;
mod writers;
static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
/// A custom context type for [`eyre::Report`] which provides colorful error
/// A custom handler type for [`eyre::Report`] which provides colorful error
/// reports and [`tracing-error`] support.
///
/// This type is not intended to be used directly, prefer using it via the
@ -312,17 +264,19 @@ static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
/// [`color_eyre::Report`]: type.Report.html
/// [`color_eyre::Result`]: type.Result.html
#[derive(Debug)]
pub struct Context {
pub struct Handler {
backtrace: Option<Backtrace>,
#[cfg(feature = "capture-spantrace")]
span_trace: Option<SpanTrace>,
sections: Vec<Section>,
sections: Vec<HelpInfo>,
}
#[derive(Debug)]
struct InstallError;
#[cfg(feature = "capture-spantrace")]
struct FormattedSpanTrace<'a>(&'a SpanTrace);
impl Context {
impl Handler {
/// Return a reference to the captured `Backtrace` type
///
/// # Examples
@ -336,7 +290,7 @@ impl Context {
/// std::env::set_var("RUST_BACKTRACE", "1");
///
/// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_some());
/// assert!(report.handler().backtrace().is_some());
/// ```
///
/// Alternatively, if you don't want backtraces to be printed on panic, you can use
@ -349,7 +303,7 @@ impl Context {
/// std::env::set_var("RUST_LIB_BACKTRACE", "1");
///
/// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_some());
/// assert!(report.handler().backtrace().is_some());
/// ```
///
/// And if you don't want backtraces to be captured but you still want panics to print
@ -363,7 +317,7 @@ impl Context {
/// std::env::set_var("RUST_LIB_BACKTRACE", "0");
///
/// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_none());
/// assert!(report.handler().backtrace().is_none());
/// ```
///
pub fn backtrace(&self) -> Option<&Backtrace> {
@ -381,7 +335,7 @@ impl Context {
/// use eyre::eyre;
///
/// let report: Report = eyre!("an error occurred");
/// assert!(report.context().span_trace().is_some());
/// assert!(report.handler().span_trace().is_some());
/// ```
///
/// However, `SpanTrace` is not captured if one of the source errors already captured a
@ -409,7 +363,7 @@ impl Context {
/// let error: TracedError<SourceError> = error.in_current_span();
///
/// let report: Report = error.into();
/// assert!(report.context().span_trace().is_none());
/// assert!(report.handler().span_trace().is_none());
/// ```
///
/// [`tracing_error::TracedError`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.TracedError.html
@ -420,7 +374,7 @@ impl Context {
}
}
impl EyreContext for Context {
impl EyreHandler for Handler {
#[allow(unused_variables)]
fn default(error: &(dyn std::error::Error + 'static)) -> Self {
let backtrace = if backtrace_enabled() {
@ -463,26 +417,32 @@ impl EyreContext for Context {
let mut buf = String::new();
for (n, error) in errors {
writeln!(f)?;
buf.clear();
write!(&mut buf, "{}", error).unwrap();
writeln!(f)?;
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
}
let separated = &mut HeaderWriter {
inner: &mut *f,
header: &"\n\n",
started: false,
};
for section in self
.sections
.iter()
.filter(|s| matches!(s.order, Order::AfterErrMsgs))
.filter(|s| matches!(s, HelpInfo::Error(_)))
{
write!(f, "\n\n{:?}", section)?;
write!(separated.ready(), "{}", section)?;
}
for section in self
.sections
.iter()
.filter(|s| matches!(s.order, Order::BeforeSpanTrace))
.filter(|s| matches!(s, HelpInfo::Custom(_)))
{
write!(f, "\n\n{:?}", section)?;
write!(separated.ready(), "{}", section)?;
}
#[cfg(feature = "capture-spantrace")]
@ -493,33 +453,23 @@ impl EyreContext for Context {
.or_else(|| get_deepest_spantrace(error))
.expect("SpanTrace capture failed");
match span_trace.status() {
SpanTraceStatus::CAPTURED => {
write!(f, "\n\n")?;
write!(indented(f).with_format(Format::Uniform { indentation: " " }), "{}", color_spantrace::colorize(span_trace))?
},
SpanTraceStatus::UNSUPPORTED => write!(f, "\n\nWarning: SpanTrace capture is Unsupported.\nEnsure that you've setup an error layer and the versions match")?,
_ => (),
}
write!(&mut separated.ready(), "{}", FormattedSpanTrace(span_trace))?;
}
if let Some(backtrace) = self.backtrace.as_ref() {
write!(f, "\n\n")?;
let bt_str = CONFIG
.get_or_init(default_printer)
let bt_str = installed_printer()
.format_trace_to_string(&backtrace)
.unwrap();
write!(
indented(f).with_format(Format::Uniform { indentation: " " }),
indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
"{}",
bt_str
)?;
} else if self
.sections
.iter()
.any(|s| matches!(s.order, Order::AfterBackTrace))
.any(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_)))
{
writeln!(f)?;
}
@ -527,9 +477,26 @@ impl EyreContext for Context {
for section in self
.sections
.iter()
.filter(|s| matches!(s.order, Order::AfterBackTrace))
.filter(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_)))
{
write!(f, "\n{:?}", section)?;
write!(f, "\n{}", section)?;
}
Ok(())
}
}
#[cfg(feature = "capture-spantrace")]
impl fmt::Display for FormattedSpanTrace<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
match self.0.status() {
SpanTraceStatus::CAPTURED => {
write!(indented(f).with_format(Format::Uniform { indentation: " " }), "{}", color_spantrace::colorize(self.0))?;
},
SpanTraceStatus::UNSUPPORTED => write!(f, "Warning: SpanTrace capture is Unsupported.\nEnsure that you've setup an error layer and the versions match")?,
_ => (),
}
Ok(())
@ -573,7 +540,7 @@ fn get_deepest_spantrace<'a>(error: &'a (dyn Error + 'static)) -> Option<&'a Spa
.next()
}
/// Override the global BacktracePrinter used by `color_eyre::Context` when printing captured
/// Override the global BacktracePrinter used by `color_eyre::Handler` when printing captured
/// backtraces.
///
/// # Examples
@ -606,7 +573,16 @@ fn get_deepest_spantrace<'a>(error: &'a (dyn Error + 'static)) -> Option<&'a Spa
/// ```
pub fn install(printer: BacktracePrinter) -> Result<(), impl std::error::Error> {
let printer = add_eyre_filters(printer);
CONFIG.set(printer).map_err(|_| InstallError)
if CONFIG.set(printer).is_err() {
return Err(InstallError);
}
Ok(())
}
fn installed_printer() -> &'static color_backtrace::BacktracePrinter {
CONFIG.get_or_init(default_printer)
}
fn default_printer() -> BacktracePrinter {
@ -616,7 +592,7 @@ fn default_printer() -> BacktracePrinter {
fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
printer.add_frame_filter(Box::new(|frames| {
let filters = &[
"<color_eyre::Context as eyre::EyreContext>::default",
"<color_eyre::Handler as eyre::EyreHandler>::default",
"eyre::",
"color_eyre::",
];
@ -635,7 +611,7 @@ fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
}))
}
/// A type alias for `eyre::Report<color_eyre::Context>`
/// A type alias for `eyre::Report<color_eyre::Handler>`
///
/// # Example
///
@ -648,7 +624,7 @@ fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
/// # Ok(Config)
/// }
/// ```
pub type Report = eyre::Report<Context>;
pub type Report = eyre::Report<Handler>;
/// A type alias for `Result<T, color_eyre::Report>`
///

View File

@ -1,6 +1,8 @@
//! Provides an extension trait for attaching `Section`s to error reports.
use crate::{section, Report, Result, Section};
use crate::{Report, Result};
use ansi_term::Color::*;
use indenter::indented;
use std::fmt::Write;
use std::fmt::{self, Display};
/// A helper trait for attaching help text to errors to be displayed after the chain of errors
@ -34,9 +36,9 @@ pub trait Help<T>: private::Sealed {
/// .section("Please report bugs to https://real.url/bugs")?;
/// # Ok::<_, Report>(())
/// ```
fn section<C>(self, section: C) -> Result<T>
fn section<D>(self, section: D) -> Result<T>
where
C: Into<Section>;
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.
@ -53,11 +55,7 @@ pub trait Help<T>: private::Sealed {
/// 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:"
/// .skip_if(|| stderr.is_empty())
/// .body(stderr.trim().to_string())
/// })?
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))?
/// } else {
/// String::from_utf8_lossy(&output.stdout)
/// };
@ -65,10 +63,10 @@ pub trait Help<T>: private::Sealed {
/// println!("{}", output);
/// # Ok::<_, Report>(())
/// ```
fn with_section<C, F>(self, section: F) -> Result<T>
fn with_section<D, F>(self, section: F) -> Result<T>
where
C: Into<Section>,
F: FnOnce() -> C;
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.
@ -142,9 +140,9 @@ pub trait Help<T>: private::Sealed {
/// # Ok(())
/// # }
/// ```
fn note<C>(self, context: C) -> Result<T>
fn note<D>(self, note: D) -> Result<T>
where
C: Display + Send + Sync + 'static;
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.
@ -174,156 +172,142 @@ pub trait Help<T>: private::Sealed {
/// # Ok(())
/// # }
/// ```
fn with_note<C, F>(self, f: F) -> Result<T>
fn with_note<D, F>(self, f: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
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<C>(self, context: C) -> Result<T>
fn warning<D>(self, warning: D) -> Result<T>
where
C: Display + Send + Sync + 'static;
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<C, F>(self, f: F) -> Result<T>
fn with_warning<D, F>(self, f: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
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<C>(self, context: C) -> Result<T>
fn suggestion<D>(self, suggestion: D) -> Result<T>
where
C: Display + Send + Sync + 'static;
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<C, F>(self, f: F) -> Result<T>
fn with_suggestion<D, F>(self, f: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
}
impl<T, E> Help<T> for std::result::Result<T, E>
where
E: Into<Report>,
{
fn note<C>(self, context: C) -> Result<T>
fn note<D>(self, note: D) -> Result<T>
where
C: Display + Send + Sync + 'static,
D: Display + Send + Sync + 'static,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Note(Box::new(context)))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Note(Box::new(note)));
e
})
}
fn with_note<C, F>(self, context: F) -> Result<T>
fn with_note<D, F>(self, note: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Note(Box::new(context())))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Note(Box::new(note())));
e
})
}
fn warning<C>(self, context: C) -> Result<T>
fn warning<D>(self, warning: D) -> Result<T>
where
C: Display + Send + Sync + 'static,
D: Display + Send + Sync + 'static,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Warning(Box::new(context)))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Warning(Box::new(warning)));
e
})
}
fn with_warning<C, F>(self, context: F) -> Result<T>
fn with_warning<D, F>(self, warning: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Warning(Box::new(context())))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Warning(Box::new(warning())));
e
})
}
fn suggestion<C>(self, context: C) -> Result<T>
fn suggestion<D>(self, suggestion: D) -> Result<T>
where
C: Display + Send + Sync + 'static,
D: Display + Send + Sync + 'static,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Suggestion(Box::new(context)))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Suggestion(Box::new(suggestion)));
e
})
}
fn with_suggestion<C, F>(self, context: F) -> Result<T>
fn with_suggestion<D, F>(self, suggestion: F) -> Result<T>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|e| {
let mut e = e.into();
e.context_mut().sections.push(
Section::from(HelpInfo::Suggestion(Box::new(context())))
.order(section::Order::AfterBackTrace),
);
e.handler_mut()
.sections
.push(HelpInfo::Suggestion(Box::new(suggestion())));
e
})
}
fn with_section<C, F>(self, section: F) -> Result<T>
fn with_section<D, F>(self, section: F) -> Result<T>
where
C: Into<Section>,
F: FnOnce() -> C,
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.map_err(|e| {
let mut e = e.into();
let section = section().into();
if !matches!(section.order, section::Order::SkipEntirely) {
e.context_mut().sections.push(section);
}
let section = Box::new(section());
e.handler_mut().sections.push(HelpInfo::Custom(section));
e
})
}
fn section<C>(self, section: C) -> Result<T>
fn section<D>(self, section: D) -> Result<T>
where
C: Into<Section>,
D: Display + Send + Sync + 'static,
{
self.map_err(|e| {
let mut e = e.into();
let section = section.into();
if !matches!(section.order, section::Order::SkipEntirely) {
e.context_mut().sections.push(section);
}
let section = Box::new(section);
e.handler_mut().sections.push(HelpInfo::Custom(section));
e
})
}
@ -334,12 +318,9 @@ where
{
self.map_err(|e| {
let mut e = e.into();
let section = Section {
inner: section::SectionKind::Error(Box::new(error)),
order: section::Order::AfterErrMsgs,
};
let error = error.into();
e.context_mut().sections.push(section);
e.handler_mut().sections.push(HelpInfo::Error(error));
e
})
}
@ -351,18 +332,17 @@ where
{
self.map_err(|e| {
let mut e = e.into();
let section = Section {
inner: section::SectionKind::Error(Box::new(error())),
order: section::Order::AfterErrMsgs,
};
let error = error().into();
e.context_mut().sections.push(section);
e.handler_mut().sections.push(HelpInfo::Error(error));
e
})
}
}
pub(crate) enum HelpInfo {
Error(Box<dyn std::error::Error + Send + Sync + 'static>),
Custom(Box<dyn Display + Send + Sync + 'static>),
Note(Box<dyn Display + Send + Sync + 'static>),
Warning(Box<dyn Display + Send + Sync + 'static>),
Suggestion(Box<dyn Display + Send + Sync + 'static>),
@ -371,9 +351,28 @@ pub(crate) enum HelpInfo {
impl Display for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Note(context) => write!(f, "{}: {}", Cyan.paint("Note"), context),
Self::Warning(context) => write!(f, "{}: {}", Yellow.paint("Warning"), context),
Self::Suggestion(context) => write!(f, "{}: {}", Cyan.paint("Suggestion"), context),
HelpInfo::Note(n) => write!(f, "{}: {}", Cyan.paint("Note"), n),
HelpInfo::Warning(w) => write!(f, "{}: {}", Yellow.paint("Warning"), w),
HelpInfo::Suggestion(s) => write!(f, "{}: {}", Cyan.paint("Suggestion"), s),
HelpInfo::Custom(c) => write!(f, "{}", c),
HelpInfo::Error(e) => {
// a lot here
let errors = std::iter::successors(
Some(e.as_ref() as &(dyn std::error::Error + 'static)),
|e| e.source(),
);
write!(f, "Error:")?;
let mut buf = String::new();
for (n, e) in errors.enumerate() {
writeln!(f)?;
buf.clear();
write!(&mut buf, "{}", e).unwrap();
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
}
Ok(())
}
}
}
}
@ -381,18 +380,23 @@ impl Display for HelpInfo {
impl fmt::Debug for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Note(context) => f
HelpInfo::Note(note) => f
.debug_tuple("Note")
.field(&format_args!("{}", context))
.field(&format_args!("{}", note))
.finish(),
Self::Warning(context) => f
HelpInfo::Warning(warning) => f
.debug_tuple("Warning")
.field(&format_args!("{}", context))
.field(&format_args!("{}", warning))
.finish(),
Self::Suggestion(context) => f
HelpInfo::Suggestion(suggestion) => f
.debug_tuple("Suggestion")
.field(&format_args!("{}", context))
.field(&format_args!("{}", suggestion))
.finish(),
HelpInfo::Custom(custom) => f
.debug_tuple("CustomSection")
.field(&format_args!("{}", custom))
.finish(),
HelpInfo::Error(error) => f.debug_tuple("Error").field(error).finish(),
}
}
}

View File

@ -1,33 +1,18 @@
//! Helpers for adding custom sections to error reports
use ansi_term::Color::*;
use indenter::indented;
use std::fmt::{self, Display, Write};
pub mod help;
#[non_exhaustive]
#[derive(Debug)]
pub(crate) enum Order {
AfterErrMsgs,
BeforeSpanTrace,
AfterBackTrace,
SkipEntirely,
}
/// A custom section for an error report.
/// An indenteted section with a header for an error report
///
/// # Details
///
/// Sections consist of two parts, a header: and an optional body. The header can contain any
/// number of lines and has no indentation applied to it by default. The body can contain any
/// number of lines and is always written after the header with indentation inserted before
/// every line.
///
/// # Construction
///
/// Sections are meant to be constructed via `Into<Section>`, which is implemented for all types
/// that implement `Display`. The constructed `Section` then takes ownership of the `Display` type
/// and boxes it internally for use later when printing the report.
/// 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
///
@ -51,41 +36,27 @@ pub(crate) enum Order {
/// 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:"
/// .skip_if(|| stdout.is_empty())
/// .body(stdout.trim().to_string())
/// })
/// .with_section(move || {
/// "Stderr:"
/// .skip_if(|| stderr.is_empty())
/// .body(stderr.trim().to_string())
/// })
/// .with_section(move || stdout.trim().to_string().header("Stdout:"))
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))
/// } else {
/// Ok(stdout.into())
/// }
/// }
/// }
/// ```
pub struct Section {
pub(crate) inner: SectionKind,
pub(crate) order: Order,
#[allow(missing_debug_implementations)]
pub struct Section<H, B> {
header: H,
body: B,
}
pub(crate) enum SectionKind {
Header(Box<dyn Display + Send + Sync + 'static>),
WithBody(
Box<dyn Display + Send + Sync + 'static>,
Box<dyn Display + Send + Sync + 'static>,
),
Error(Box<dyn std::error::Error + Send + Sync + 'static>),
}
/// Extension trait for customizing the content of a `Section`
pub trait SectionExt {
/// Add a body to a `Section`
/// Extension trait for constructing sections with commonly used formats
pub trait SectionExt: Sized {
/// Add a header to a `Section` and indent the body
///
/// 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
///
@ -101,147 +72,52 @@ pub trait SectionExt {
/// let just_header = "header";
/// let just_body = "body\nbody";
/// let report2 = Err::<(), Report>(eyre!("an error occurred"))
/// .section(just_header.body(just_body))
/// .section(just_body.header(just_header))
/// .unwrap_err();
///
/// assert_eq!(format!("{:?}", report), format!("{:?}", report2))
/// ```
fn body<C>(self, body: C) -> Section
fn header<C>(self, header: C) -> Section<C, Self>
where
C: Display + Send + Sync + 'static;
/// Skip a section based on some condition. For example, skip a section if the body is empty.
///
/// The skipped section is not stored in the report and is instead immediately dropped.
///
/// # Examples
///
/// ```rust
/// use eyre::eyre;
/// use color_eyre::{SectionExt, Report, Help};
///
/// fn add_body(report: Report, body: String) -> Result<(), Report> {
/// Err(report)
/// .with_section(|| "ExtraInfo:".skip_if(|| body.is_empty()).body(body))
/// }
///
/// let report = eyre!("an error occurred");
/// let before = format!("{:?}", report);
/// let body = String::new();
/// let report = add_body(report, body).unwrap_err();
/// let after = format!("{:?}", report);
/// assert_eq!(before, after);
///
/// let report = eyre!("an error occurred");
/// let before = format!("{:?}", report);
/// let body = String::from("Some actual text here");
/// let report = add_body(report, body).unwrap_err();
/// let after = format!("{:?}", report);
/// assert_ne!(before, after);
/// ```
fn skip_if<F>(self, condition: F) -> Section
where
F: FnOnce() -> bool;
}
impl Section {
pub(crate) fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
}
impl<T> SectionExt for T
where
Section: From<T>,
T: Display + Send + Sync + 'static,
{
fn body<C>(self, body: C) -> Section
fn header<C>(self, header: C) -> Section<C, Self>
where
C: Display + Send + Sync + 'static,
{
let section = Section::from(self);
let header = match section.inner {
SectionKind::Header(header) => header,
SectionKind::WithBody(header, _body) => header,
SectionKind::Error(_) => unreachable!("bodies cannot be added to Error sections"),
};
let inner = SectionKind::WithBody(header, Box::new(body));
Section {
inner,
order: section.order,
}
}
fn skip_if<F>(self, condition: F) -> Section
where
F: FnOnce() -> bool,
{
let mut section = Section::from(self);
section.order = if condition() {
Order::SkipEntirely
} else {
section.order
};
section
Section { body: self, header }
}
}
impl<T> From<T> for Section
impl<H, B> fmt::Display for Section<H, B>
where
T: Display + Send + Sync + 'static,
H: Display + Send + Sync + 'static,
B: Display + Send + Sync + 'static,
{
fn from(header: T) -> Self {
let inner = SectionKind::Header(Box::new(header));
Self {
inner,
order: Order::BeforeSpanTrace,
}
}
}
impl fmt::Debug for Section {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
let mut headered = crate::writers::HeaderWriter {
inner: f,
header: &self.header,
started: false,
};
impl fmt::Display for SectionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SectionKind::Header(header) => write!(f, "{}", header)?,
SectionKind::WithBody(header, body) => {
write!(f, "{}", header)?;
writeln!(f)?;
write!(
indenter::indented(f)
.with_format(indenter::Format::Uniform { indentation: " " }),
"{}",
body
)?;
}
SectionKind::Error(error) => {
// a lot here
let errors = std::iter::successors(
Some(error.as_ref() as &(dyn std::error::Error + 'static)),
|e| e.source(),
);
let mut headered = crate::writers::HeaderWriter {
inner: headered.ready(),
header: &"\n",
started: false,
};
write!(f, "Error:")?;
let mut buf = String::new();
for (n, error) in errors.enumerate() {
writeln!(f)?;
buf.clear();
write!(&mut buf, "{}", error).unwrap();
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
}
}
}
let mut headered = headered.ready();
let mut indented = indenter::indented(&mut headered)
.with_format(indenter::Format::Uniform { indentation: " " });
write!(&mut indented, "{}", self.body)?;
Ok(())
}

32
src/writers.rs Normal file
View File

@ -0,0 +1,32 @@
use std::fmt::{self, Display};
pub(crate) struct HeaderWriter<'a, H, W> {
pub(crate) inner: W,
pub(crate) header: &'a H,
pub(crate) started: bool,
}
pub(crate) struct ReadyHeaderWriter<'a, 'b, H, W>(&'b mut HeaderWriter<'a, H, W>);
impl<'a, H, W> HeaderWriter<'a, H, W> {
pub(crate) fn ready(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> {
self.started = false;
ReadyHeaderWriter(self)
}
}
impl<H, W> fmt::Write for ReadyHeaderWriter<'_, '_, 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)
}
}