Skip to content

Commit 5b87037

Browse files
committed
feat: added new macro trace_error and added anyhow features to have trace_anyhow_error
1 parent bf6c94e commit 5b87037

File tree

5 files changed

+193
-4
lines changed

5 files changed

+193
-4
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ required-features = ["json-logger"]
3838

3939
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
4040
[dependencies]
41+
anyhow = {version = "1.0.100", features = ["std"], optional = true}
4142
opentelemetry = {version = "0.31", optional = true}
4243
opentelemetry-semantic-conventions = {version = "0.31", optional = true}
4344
opentelemetry-otlp = {version = "0.31", features = ["http-proto", "reqwest-blocking-client"], default-features = false, optional = true}
@@ -66,4 +67,5 @@ opentelemetry-jaeger = {version = "0.22", features = ["integration_test"]}
6667
prima_bridge = "0.25"
6768
tokio = {version = "1.17", features = ["rt", "macros", "rt-multi-thread"]}
6869
tracing-actix-web = {version = "0.7.11", features = ["opentelemetry_0_27"]}
70+
tracing-capture = "0.1.0"
6971
uuid = {version = "1.10", features = ["v4"]}

src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
//! # }
1919
//! ```
2020
21-
mod config;
21+
#[cfg(feature = "traces")]
22+
#[macro_use]
23+
mod macros;
2224

25+
mod config;
2326
mod subscriber;
2427

2528
#[cfg(feature = "json-logger")]
@@ -65,9 +68,7 @@ macro_rules! report_error {
6568
($error:expr, $($args:tt)*) => {
6669
{
6770
let kind = ::std::any::type_name_of_val(&$error);
68-
let stack = error_report::Report::new(&$error);
69-
let backtrace = ::std::backtrace::Backtrace::force_capture();
70-
$crate::tracing::error!(error.kind = kind, error.stack = ?stack, error.trace = %backtrace, error.message = &$error as &dyn ::std::error::Error, $($args)+)
71+
$crate::tracing::error!(error.kind = kind, error = &$error as &dyn ::std::error::Error, $($args)+)
7172
}
7273
};
7374
}

src/macros.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/// Emitt a tracing error event for an error with rich, structured context.
2+
/// It capturing the error using the experimental Rust [std::error::Report](https://doc.rust-lang.org/stable/std/error/struct.Report.html)
3+
/// and adding the type name as error.kind, the backtrace as error.trace and the error stack as error.message
4+
///
5+
/// # Examples
6+
///
7+
/// ```rust
8+
/// use prima_tracing::trace_error;
9+
/// # fn main() {
10+
///
11+
/// let error = "not a number".parse::<usize>().unwrap_err();
12+
/// trace_error!(error, "Parsing error!");
13+
/// trace_error!(error, "Parsing error: {}", uid="1234");
14+
/// # }
15+
/// ```
16+
#[macro_export]
17+
macro_rules! trace_error {
18+
($error:expr, $message:expr) => {
19+
{
20+
$crate::trace_error!($error, $message,)
21+
}
22+
};
23+
($error:expr, $message:expr, $($field:tt)*) => {
24+
{
25+
let kind = std::any::type_name_of_val(&$error);
26+
let error_message = format!("{:#}", $error);
27+
let stack = error_report::Report::new(&$error);
28+
let trace = std::backtrace::Backtrace::force_capture();
29+
30+
$crate::tracing::error!(
31+
{
32+
error.message = error_message,
33+
error.kind = kind,
34+
error.stack = ?stack,
35+
error.trace = %trace,
36+
$($field)*
37+
},
38+
"{} {}",
39+
$message,
40+
$error
41+
);
42+
}
43+
};
44+
($error:expr, $message:expr) => {
45+
{
46+
$crate::trace_error!($error, $message, {})
47+
}
48+
};
49+
}
50+
51+
/// Emitt a tracing error event for anyhow error with rich, structured context.
52+
/// It capturing the error using the experimental Rust [std::error::Report](https://doc.rust-lang.org/stable/std/error/struct.Report.html)
53+
/// and adding the type name as error.kind, the backtrace as error.trace and the error stack as error.message
54+
///
55+
/// # Examples
56+
///
57+
/// ```rust
58+
/// use anyhow::anyhow
59+
/// use prima_tracing::trace_error;
60+
/// # fn main() {
61+
///
62+
/// let error = anyhow!("an error");
63+
/// trace_anyhow_error!(error, "Throw error!");
64+
/// trace_anyhow_error!(error, "Throw error!", uid="1234");
65+
/// # }
66+
/// ```
67+
#[cfg(feature = "anyhow")]
68+
#[macro_export]
69+
macro_rules! trace_anyhow_error {
70+
($error:expr, $message:expr) => {
71+
{
72+
$crate::trace_anyhow_error!($error, $message,)
73+
}
74+
};
75+
($error:expr, $message:expr, $($field:tt)*) => {
76+
{
77+
let kind = std::any::type_name_of_val(&$error.root_cause());
78+
let error_message = format!("{:#}", $error);
79+
let std_err: &(dyn std::error::Error + 'static) = $error.as_ref();
80+
let stack = error_report::Report::new(std_err);
81+
82+
$crate::tracing::error!(
83+
{
84+
error.message = error_message,
85+
error.kind = kind,
86+
error.stack = ?stack,
87+
error.trace = %$error.backtrace(),
88+
$($field)*
89+
},
90+
"{} {:?}",
91+
$message,
92+
$error
93+
);
94+
}
95+
};
96+
($error:expr, $message:expr) => {
97+
{
98+
$crate::trace_anyhow_error!($error, $message,)
99+
}
100+
};
101+
}

tests/macros.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use tracing_capture::{CaptureLayer, CapturedEvent, SharedStorage};
2+
use tracing_subscriber::layer::SubscriberExt;
3+
4+
#[cfg(feature = "anyhow")]
5+
use {anyhow::anyhow, prima_tracing::trace_anyhow_error};
6+
7+
use prima_tracing::trace_error;
8+
9+
#[test]
10+
fn produce_trace_error() {
11+
let error = "not a number".parse::<usize>().unwrap_err();
12+
13+
with_test_tracing(
14+
|| {
15+
trace_error!(error, "Parsing error!", something = "something");
16+
},
17+
|events| {
18+
let event = events.first().unwrap();
19+
20+
assert_eq!(event["error.message"], "invalid digit found in string");
21+
assert_eq!(event["error.kind"], "core::num::error::ParseIntError");
22+
assert_eq!(
23+
event["error.stack"].as_debug_str(),
24+
Some("invalid digit found in string")
25+
);
26+
assert!(event["error.trace"].as_debug_str().is_some());
27+
assert!(event["error.trace"]
28+
.as_debug_str()
29+
.unwrap()
30+
.contains("macros::produce_trace_error"));
31+
assert_eq!(event["something"], "something");
32+
assert_eq!(
33+
event["message"].as_debug_str(),
34+
Some("Parsing error! invalid digit found in string")
35+
);
36+
},
37+
)
38+
}
39+
40+
#[cfg(feature = "anyhow")]
41+
#[test]
42+
fn produce_trace_anyhow_error() {
43+
let error = anyhow!("an error");
44+
45+
with_test_tracing(
46+
|| {
47+
trace_anyhow_error!(error, "Throw error!");
48+
},
49+
|events| {
50+
let event = events.first().unwrap();
51+
52+
assert_eq!(event["error.message"], "an error");
53+
assert_eq!(event["error.kind"], "&dyn core::error::Error");
54+
assert_eq!(event["error.stack"].as_debug_str(), Some("an error"));
55+
assert_eq!(
56+
event["error.trace"].as_debug_str(),
57+
Some(format!("{}", error.backtrace()).as_str())
58+
);
59+
assert_eq!(
60+
event["message"].as_debug_str(),
61+
Some(format!("Throw error! {error:?}").as_str())
62+
);
63+
},
64+
)
65+
}
66+
67+
fn with_test_tracing<F, T>(tracing: F, tests: T)
68+
where
69+
F: FnOnce(),
70+
T: FnOnce(Vec<CapturedEvent>),
71+
{
72+
let subscriber = tracing_subscriber::fmt().pretty().finish();
73+
let storage: SharedStorage = SharedStorage::default();
74+
let subscriber = subscriber.with(CaptureLayer::new(&storage));
75+
76+
tracing::subscriber::with_default(subscriber, || {
77+
tracing::info_span!("test-span").in_scope(tracing);
78+
});
79+
80+
let storage = storage.lock();
81+
let span = storage.all_spans().next().unwrap();
82+
83+
tests(span.events().collect());
84+
}

tests/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
mod e2e;
2+
mod macros;

0 commit comments

Comments
 (0)