mirror of
https://github.com/eyre-rs/eyre.git
synced 2025-09-28 13:31:29 +00:00
Implement github issue generator (#62)
* Initial implementation of github issue generator * document the hookbuilder methods * fix doctest * make issue generation feature optional * update changelog * (cargo-release) version 0.5.4-rc.1 * add doc cfg attr to new methods * (cargo-release) version 0.5.4-rc.2 * supress issue sections when they'd be empty * (cargo-release) version 0.5.4-rc.3 * (cargo-release) version 0.5.4 * (cargo-release) start next development iteration 0.5.5-alpha.0
This commit is contained in:
parent
05809302b3
commit
9c738c3a39
@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased] - ReleaseDate
|
## [Unreleased] - ReleaseDate
|
||||||
|
|
||||||
|
## [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
|
## [0.5.3] - 2020-09-14
|
||||||
### Added
|
### Added
|
||||||
- add `panic_section` method to `HookBuilder` for overriding the printer for
|
- add `panic_section` method to `HookBuilder` for overriding the printer for
|
||||||
@ -25,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
better compatibility with the Display trait
|
better compatibility with the Display trait
|
||||||
|
|
||||||
<!-- next-url -->
|
<!-- next-url -->
|
||||||
[Unreleased]: https://github.com/yaahc/color-eyre/compare/v0.5.3...HEAD
|
[Unreleased]: https://github.com/yaahc/color-eyre/compare/v0.5.4...HEAD
|
||||||
|
[0.5.4]: https://github.com/yaahc/color-eyre/compare/v0.5.3...v0.5.4
|
||||||
[0.5.3]: https://github.com/yaahc/color-eyre/compare/v0.5.2...v0.5.3
|
[0.5.3]: https://github.com/yaahc/color-eyre/compare/v0.5.2...v0.5.3
|
||||||
[0.5.2]: https://github.com/yaahc/color-eyre/releases/tag/v0.5.2
|
[0.5.2]: https://github.com/yaahc/color-eyre/releases/tag/v0.5.2
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.5.4-alpha.0"
|
version = "0.5.5-alpha.0"
|
||||||
authors = ["Jane Lusby <jlusby@yaah.dev>"]
|
authors = ["Jane Lusby <jlusby@yaah.dev>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -14,6 +14,7 @@ keywords = []
|
|||||||
[features]
|
[features]
|
||||||
default = ["capture-spantrace"]
|
default = ["capture-spantrace"]
|
||||||
capture-spantrace = ["tracing-error", "color-spantrace"]
|
capture-spantrace = ["tracing-error", "color-spantrace"]
|
||||||
|
issue-url = ["url"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eyre = "0.6.0"
|
eyre = "0.6.0"
|
||||||
@ -23,6 +24,7 @@ indenter = "0.3.0"
|
|||||||
owo-colors = "1.0.3"
|
owo-colors = "1.0.3"
|
||||||
color-spantrace = { version = "0.1.4", optional = true }
|
color-spantrace = { version = "0.1.4", optional = true }
|
||||||
once_cell = "1.4.0"
|
once_cell = "1.4.0"
|
||||||
|
url = { version = "2.1.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tracing-subscriber = "0.2.5"
|
tracing-subscriber = "0.2.5"
|
||||||
|
70
examples/github_issue.rs
Normal file
70
examples/github_issue.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#![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("https://github.com/yaahc/jane-eyre/issues/new")
|
||||||
|
.add_issue_metadata("version", "0.1.0")
|
||||||
|
.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")
|
||||||
|
}
|
114
src/config.rs
114
src/config.rs
@ -250,6 +250,10 @@ pub struct HookBuilder {
|
|||||||
display_env_section: bool,
|
display_env_section: bool,
|
||||||
panic_section: Option<Box<dyn Display + Send + Sync + 'static>>,
|
panic_section: Option<Box<dyn Display + Send + Sync + 'static>>,
|
||||||
panic_message: Box<dyn PanicMessage>,
|
panic_message: Box<dyn PanicMessage>,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: Option<String>,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: Vec<(String, Box<dyn Display + Send + Sync + 'static>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HookBuilder {
|
impl HookBuilder {
|
||||||
@ -284,6 +288,10 @@ impl HookBuilder {
|
|||||||
display_env_section: true,
|
display_env_section: true,
|
||||||
panic_section: None,
|
panic_section: None,
|
||||||
panic_message: Box::new(DefaultPanicMessage),
|
panic_message: Box::new(DefaultPanicMessage),
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: None,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +368,57 @@ impl HookBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set an upstream github repo and enable issue reporting url generation
|
||||||
|
///
|
||||||
|
/// # Details
|
||||||
|
///
|
||||||
|
/// Once enabled, color-eyre will generate urls that will create customized
|
||||||
|
/// issues pre-populated with information about the associated error report.
|
||||||
|
///
|
||||||
|
/// Additional information can be added to the metadata table in the
|
||||||
|
/// generated urls by calling `add_issue_metadata` when configuring the
|
||||||
|
/// HookBuilder.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// color_eyre::config::HookBuilder::default()
|
||||||
|
/// .issue_url("https://github.com/yaahc/jane-eyre/issues/new")
|
||||||
|
/// .install()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
|
||||||
|
pub fn issue_url<S: ToString>(mut self, url: S) -> Self {
|
||||||
|
self.issue_url = Some(url.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new entry to the metadata table in generated github issue urls
|
||||||
|
///
|
||||||
|
/// **Note**: this metadata will be ignored if no `issue_url` is set.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// color_eyre::config::HookBuilder::default()
|
||||||
|
/// .issue_url("https://github.com/yaahc/jane-eyre/issues/new")
|
||||||
|
/// .add_issue_metadata("version", "0.1.0")
|
||||||
|
/// .install()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
|
||||||
|
pub fn add_issue_metadata<K, V>(mut self, key: K, value: V) -> Self
|
||||||
|
where
|
||||||
|
K: Display,
|
||||||
|
V: Display + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let pair = (key.to_string(), Box::new(value) as _);
|
||||||
|
self.issue_metadata.push(pair);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configures the default capture mode for `SpanTraces` in error reports and panics
|
/// Configures the default capture mode for `SpanTraces` in error reports and panics
|
||||||
pub fn capture_span_trace_by_default(mut self, cond: bool) -> Self {
|
pub fn capture_span_trace_by_default(mut self, cond: bool) -> Self {
|
||||||
self.capture_span_trace_by_default = cond;
|
self.capture_span_trace_by_default = cond;
|
||||||
@ -423,6 +482,8 @@ impl HookBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn into_hooks(self) -> (PanicHook, EyreHook) {
|
pub(crate) fn into_hooks(self) -> (PanicHook, EyreHook) {
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
let metadata = Arc::new(self.issue_metadata);
|
||||||
let panic_hook = PanicHook {
|
let panic_hook = PanicHook {
|
||||||
filters: self.filters.into_iter().map(Into::into).collect(),
|
filters: self.filters.into_iter().map(Into::into).collect(),
|
||||||
section: self.panic_section,
|
section: self.panic_section,
|
||||||
@ -430,12 +491,20 @@ impl HookBuilder {
|
|||||||
capture_span_trace_by_default: self.capture_span_trace_by_default,
|
capture_span_trace_by_default: self.capture_span_trace_by_default,
|
||||||
display_env_section: self.display_env_section,
|
display_env_section: self.display_env_section,
|
||||||
panic_message: self.panic_message,
|
panic_message: self.panic_message,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: self.issue_url.clone(),
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: metadata.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eyre_hook = EyreHook {
|
let eyre_hook = EyreHook {
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
capture_span_trace_by_default: self.capture_span_trace_by_default,
|
capture_span_trace_by_default: self.capture_span_trace_by_default,
|
||||||
display_env_section: self.display_env_section,
|
display_env_section: self.display_env_section,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: self.issue_url,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
(panic_hook, eyre_hook)
|
(panic_hook, eyre_hook)
|
||||||
@ -533,6 +602,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) ->
|
|||||||
hook.panic_message.display(printer.0, out)?;
|
hook.panic_message.display(printer.0, out)?;
|
||||||
|
|
||||||
let v = panic_verbosity();
|
let v = panic_verbosity();
|
||||||
|
let capture_bt = v != Verbosity::Minimal;
|
||||||
|
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
let span_trace = if hook.spantrace_capture_enabled() {
|
let span_trace = if hook.spantrace_capture_enabled() {
|
||||||
@ -541,6 +611,12 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) ->
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bt = if capture_bt {
|
||||||
|
Some(backtrace::Backtrace::new())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mut separated = out.header("\n\n");
|
let mut separated = out.header("\n\n");
|
||||||
|
|
||||||
if let Some(ref section) = hook.section {
|
if let Some(ref section) = hook.section {
|
||||||
@ -558,10 +634,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let capture_bt = v != Verbosity::Minimal;
|
if let Some(bt) = bt.as_ref() {
|
||||||
|
|
||||||
if capture_bt {
|
|
||||||
let bt = backtrace::Backtrace::new();
|
|
||||||
let fmted_bt = hook.format_backtrace(&bt);
|
let fmted_bt = hook.format_backtrace(&bt);
|
||||||
write!(
|
write!(
|
||||||
indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
|
indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
|
||||||
@ -580,6 +653,27 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) ->
|
|||||||
write!(&mut separated.ready(), "{}", env_section)?;
|
write!(&mut separated.ready(), "{}", env_section)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
if let Some(url) = &hook.issue_url {
|
||||||
|
let payload = printer
|
||||||
|
.0
|
||||||
|
.payload()
|
||||||
|
.downcast_ref::<String>()
|
||||||
|
.map(String::as_str)
|
||||||
|
.or_else(|| printer.0.payload().downcast_ref::<&str>().cloned())
|
||||||
|
.unwrap_or("<non string panic payload>");
|
||||||
|
|
||||||
|
let issue_section = crate::section::github::IssueSection::new(url, payload)
|
||||||
|
.with_backtrace(bt.as_ref())
|
||||||
|
.with_location(printer.0.location())
|
||||||
|
.with_metadata(&**hook.issue_metadata);
|
||||||
|
|
||||||
|
#[cfg(feature = "capture-spantrace")]
|
||||||
|
let issue_section = issue_section.with_span_trace(span_trace.as_ref());
|
||||||
|
|
||||||
|
write!(&mut separated.ready(), "{}", issue_section)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,6 +684,10 @@ pub(crate) struct PanicHook {
|
|||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
capture_span_trace_by_default: bool,
|
capture_span_trace_by_default: bool,
|
||||||
display_env_section: bool,
|
display_env_section: bool,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: Option<String>,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PanicHook {
|
impl PanicHook {
|
||||||
@ -615,6 +713,10 @@ pub(crate) struct EyreHook {
|
|||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
capture_span_trace_by_default: bool,
|
capture_span_trace_by_default: bool,
|
||||||
display_env_section: bool,
|
display_env_section: bool,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: Option<String>,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EyreHook {
|
impl EyreHook {
|
||||||
@ -641,6 +743,10 @@ impl EyreHook {
|
|||||||
span_trace,
|
span_trace,
|
||||||
sections: Vec::new(),
|
sections: Vec::new(),
|
||||||
display_env_section: self.display_env_section,
|
display_env_section: self.display_env_section,
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_url: self.issue_url.clone(),
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
issue_metadata: self.issue_metadata.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,12 @@ use std::fmt::Write;
|
|||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
use tracing_error::{ExtractSpanTrace, 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 {
|
impl Handler {
|
||||||
/// Return a reference to the captured `Backtrace` type
|
/// Return a reference to the captured `Backtrace` type
|
||||||
pub fn backtrace(&self) -> Option<&Backtrace> {
|
pub fn backtrace(&self) -> Option<&Backtrace> {
|
||||||
@ -25,8 +31,6 @@ impl Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {}
|
|
||||||
|
|
||||||
impl eyre::EyreHandler for Handler {
|
impl eyre::EyreHandler for Handler {
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
@ -38,14 +42,16 @@ impl eyre::EyreHandler for Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
let errors = eyre::Chain::new(error)
|
let errors = || {
|
||||||
.filter(|e| e.span_trace().is_none())
|
eyre::Chain::new(error)
|
||||||
.enumerate();
|
.filter(|e| e.span_trace().is_none())
|
||||||
|
.enumerate()
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "capture-spantrace"))]
|
#[cfg(not(feature = "capture-spantrace"))]
|
||||||
let errors = eyre::Chain::new(error).enumerate();
|
let errors = || eyre::Chain::new(error).enumerate();
|
||||||
|
|
||||||
for (n, error) in errors {
|
for (n, error) in errors() {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
write!(indented(f).ind(n), "{}", error.bright_red())?;
|
write!(indented(f).ind(n), "{}", error.bright_red())?;
|
||||||
}
|
}
|
||||||
@ -118,6 +124,24 @@ impl eyre::EyreHandler for Handler {
|
|||||||
write!(&mut separated.ready(), "{}", env_section)?;
|
write!(&mut separated.ready(), "{}", env_section)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
if let Some(url) = &self.issue_url {
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,7 @@
|
|||||||
//! [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.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/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
|
//! [`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.5.3")]
|
#![doc(html_root_url = "https://docs.rs/color-eyre/0.5.4")]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![warn(
|
#![warn(
|
||||||
missing_docs,
|
missing_docs,
|
||||||
@ -359,6 +359,7 @@
|
|||||||
while_true
|
while_true
|
||||||
)]
|
)]
|
||||||
#![allow(clippy::try_err)]
|
#![allow(clippy::try_err)]
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
pub use eyre;
|
pub use eyre;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -393,13 +394,17 @@ mod writers;
|
|||||||
/// [`tracing-error`]: https://docs.rs/tracing-error
|
/// [`tracing-error`]: https://docs.rs/tracing-error
|
||||||
/// [`color_eyre::Report`]: type.Report.html
|
/// [`color_eyre::Report`]: type.Report.html
|
||||||
/// [`color_eyre::Result`]: type.Result.html
|
/// [`color_eyre::Result`]: type.Result.html
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
backtrace: Option<Backtrace>,
|
backtrace: Option<Backtrace>,
|
||||||
#[cfg(feature = "capture-spantrace")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
span_trace: Option<SpanTrace>,
|
span_trace: Option<SpanTrace>,
|
||||||
sections: Vec<HelpInfo>,
|
sections: Vec<HelpInfo>,
|
||||||
display_env_section: bool,
|
display_env_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>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static CONFIG: OnceCell<config::PanicHook> = OnceCell::new();
|
static CONFIG: OnceCell<config::PanicHook> = OnceCell::new();
|
||||||
|
185
src/section/github.rs
Normal file
185
src/section/github.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use crate::writers::DisplayExt;
|
||||||
|
use backtrace::Backtrace;
|
||||||
|
use std::{fmt, panic::Location};
|
||||||
|
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>,
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
use crate::writers::WriterExt;
|
use crate::writers::WriterExt;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
#[cfg(feature = "issue-url")]
|
||||||
|
pub(crate) mod github;
|
||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
|
|
||||||
/// An indented section with a header for an error report
|
/// An indented section with a header for an error report
|
||||||
|
@ -28,6 +28,27 @@ impl<W> WriterExt for W {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>);
|
pub(crate) struct ReadyHeaderWriter<'a, 'b, H: ?Sized, W>(&'b mut HeaderWriter<'a, H, W>);
|
||||||
|
|
||||||
impl<'a, H: ?Sized, W> HeaderWriter<'a, H, W> {
|
impl<'a, H: ?Sized, W> HeaderWriter<'a, H, W> {
|
||||||
@ -59,6 +80,77 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")]
|
#[cfg(feature = "capture-spantrace")]
|
||||||
pub(crate) struct FormattedSpanTrace<'a>(pub(crate) &'a SpanTrace);
|
pub(crate) struct FormattedSpanTrace<'a>(pub(crate) &'a SpanTrace);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user