Skip to content

Commit c42ed3f

Browse files
authored
refactor(tracing): refactor internal code and improve docs (#839)
1 parent 2b1455b commit c42ed3f

File tree

6 files changed

+107
-59
lines changed

6 files changed

+107
-59
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
- fix(logs): send environment in `sentry.environment` default attribute (#837) by @lcian
88

9+
### Behavioral changes
10+
11+
- refactor(tracing): refactor internal code and improve docs (#839) by @lcian
12+
- Errors carried by breadcrumbs will now be stored in the breadcrumb `data` under their original field name.
13+
- Before, they were all stored under a single key called `errors`.
14+
915
### Dependencies
1016

1117
- chore(deps): upgrade `ureq` to 3.x (#835) by @algesten

sentry-tracing/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ sentry-backtrace = { version = "0.39.0", path = "../sentry-backtrace", optional
3131

3232
[dev-dependencies]
3333
log = "0.4"
34-
sentry = { path = "../sentry", default-features = false, features = ["test"] }
34+
sentry = { path = "../sentry", default-features = false, features = ["test", "tracing"] }
3535
serde_json = "1"
3636
tracing = "0.1"
3737
tracing-subscriber = { version = "0.3.1", features = ["fmt", "registry"] }

sentry-tracing/src/converters.rs

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ use tracing_subscriber::registry::LookupSpan;
1111
use super::layer::SentrySpanData;
1212
use crate::TAGS_PREFIX;
1313

14-
/// Converts a [`tracing_core::Level`] to a Sentry [`Level`]
15-
fn convert_tracing_level(level: &tracing_core::Level) -> Level {
16-
match level {
17-
&tracing_core::Level::TRACE | &tracing_core::Level::DEBUG => Level::Debug,
18-
&tracing_core::Level::INFO => Level::Info,
19-
&tracing_core::Level::WARN => Level::Warning,
20-
&tracing_core::Level::ERROR => Level::Error,
14+
/// Converts a [`tracing_core::Level`] to a Sentry [`Level`].
15+
fn level_to_sentry_level(level: &tracing_core::Level) -> Level {
16+
match *level {
17+
tracing_core::Level::TRACE | tracing_core::Level::DEBUG => Level::Debug,
18+
tracing_core::Level::INFO => Level::Info,
19+
tracing_core::Level::WARN => Level::Warning,
20+
tracing_core::Level::ERROR => Level::Error,
2121
}
2222
}
2323

24+
/// Converts a [`tracing_core::Level`] to the corresponding Sentry [`Exception::ty`] entry.
2425
#[allow(unused)]
2526
fn level_to_exception_type(level: &tracing_core::Level) -> &'static str {
2627
match *level {
@@ -32,11 +33,16 @@ fn level_to_exception_type(level: &tracing_core::Level) -> &'static str {
3233
}
3334
}
3435

35-
/// Extracts the message and metadata from an event
36-
/// and also optionally from its spans chain.
37-
fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisitor) {
36+
/// Extracts the message and metadata from an event.
37+
fn extract_event_data(
38+
event: &tracing_core::Event,
39+
store_errors_in_values: bool,
40+
) -> (Option<String>, FieldVisitor) {
3841
// Find message of the event, if any
39-
let mut visitor = FieldVisitor::default();
42+
let mut visitor = FieldVisitor {
43+
store_errors_in_values,
44+
..Default::default()
45+
};
4046
event.record(&mut visitor);
4147
let message = visitor
4248
.json_values
@@ -52,14 +58,16 @@ fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisi
5258
(message, visitor)
5359
}
5460

61+
/// Extracts the message and metadata from an event, including the data in the current span.
5562
fn extract_event_data_with_context<S>(
5663
event: &tracing_core::Event,
5764
ctx: Option<Context<S>>,
65+
store_errors_in_values: bool,
5866
) -> (Option<String>, FieldVisitor)
5967
where
6068
S: Subscriber + for<'a> LookupSpan<'a>,
6169
{
62-
let (message, mut visitor) = extract_event_data(event);
70+
let (message, mut visitor) = extract_event_data(event, store_errors_in_values);
6371

6472
// Add the context fields of every parent span.
6573
let current_span = ctx.as_ref().and_then(|ctx| {
@@ -72,6 +80,7 @@ where
7280
for span in span.scope() {
7381
let name = span.name();
7482
let ext = span.extensions();
83+
7584
if let Some(span_data) = ext.get::<SentrySpanData>() {
7685
match &span_data.sentry_span {
7786
TransactionOrSpan::Span(span) => {
@@ -98,11 +107,14 @@ where
98107
(message, visitor)
99108
}
100109

101-
/// Records all fields of [`tracing_core::Event`] for easy access
110+
/// Records the fields of a [`tracing_core::Event`].
102111
#[derive(Default)]
103112
pub(crate) struct FieldVisitor {
104-
pub json_values: BTreeMap<String, Value>,
105-
pub exceptions: Vec<Exception>,
113+
pub(crate) json_values: BTreeMap<String, Value>,
114+
pub(crate) exceptions: Vec<Exception>,
115+
/// If `true`, stringify and store errors in `self.json_values` under the original field name
116+
/// else (default), convert to `Exception`s and store in `self.exceptions`.
117+
store_errors_in_values: bool,
106118
}
107119

108120
impl FieldVisitor {
@@ -129,10 +141,20 @@ impl Visit for FieldVisitor {
129141
self.record(field, value);
130142
}
131143

132-
fn record_error(&mut self, _field: &Field, value: &(dyn Error + 'static)) {
144+
fn record_error(&mut self, field: &Field, value: &(dyn Error + 'static)) {
133145
let event = event_from_error(value);
134-
for exception in event.exception {
135-
self.exceptions.push(exception);
146+
if self.store_errors_in_values {
147+
let error_chain = event
148+
.exception
149+
.iter()
150+
.rev()
151+
.filter_map(|x| x.value.as_ref().map(|v| format!("{}: {}", x.ty, *v)))
152+
.collect::<Vec<String>>();
153+
self.record(field, error_chain);
154+
} else {
155+
for exception in event.exception {
156+
self.exceptions.push(exception);
157+
}
136158
}
137159
}
138160

@@ -141,41 +163,28 @@ impl Visit for FieldVisitor {
141163
}
142164
}
143165

144-
/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`]
166+
/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`].
145167
pub fn breadcrumb_from_event<'context, S>(
146168
event: &tracing_core::Event,
147169
ctx: impl Into<Option<Context<'context, S>>>,
148170
) -> Breadcrumb
149171
where
150172
S: Subscriber + for<'a> LookupSpan<'a>,
151173
{
152-
let (message, visitor) = extract_event_data_with_context(event, ctx.into());
153-
154-
let FieldVisitor {
155-
exceptions,
156-
mut json_values,
157-
} = visitor;
158-
159-
let errors = exceptions
160-
.iter()
161-
.rev()
162-
.filter_map(|x| x.value.as_ref().map(|v| format!("{}: {}", x.ty, *v)))
163-
.collect::<Vec<String>>();
164-
if !errors.is_empty() {
165-
json_values.insert("errors".to_owned(), errors.into());
166-
}
174+
let (message, visitor) = extract_event_data_with_context(event, ctx.into(), true);
167175

168176
Breadcrumb {
169177
category: Some(event.metadata().target().to_owned()),
170178
ty: "log".into(),
171-
level: convert_tracing_level(event.metadata().level()),
179+
level: level_to_sentry_level(event.metadata().level()),
172180
message,
173-
data: json_values,
181+
data: visitor.json_values,
174182
..Default::default()
175183
}
176184
}
177185

178-
fn tags_from_event(fields: &mut BTreeMap<String, Value>) -> BTreeMap<String, String> {
186+
/// Convert `tracing` fields to the corresponding Sentry tags, removing them from `fields`.
187+
fn extract_and_remove_tags(fields: &mut BTreeMap<String, Value>) -> BTreeMap<String, String> {
179188
let mut tags = BTreeMap::new();
180189

181190
fields.retain(|key, value| {
@@ -200,6 +209,7 @@ fn tags_from_event(fields: &mut BTreeMap<String, Value>) -> BTreeMap<String, Str
200209
tags
201210
}
202211

212+
/// Create Sentry Contexts out of the `tracing` event and fields.
203213
fn contexts_from_event(
204214
event: &tracing_core::Event,
205215
fields: BTreeMap<String, Value>,
@@ -232,7 +242,7 @@ fn contexts_from_event(
232242
context
233243
}
234244

235-
/// Creates an [`Event`] (possibly carrying an exception) from a given [`tracing_core::Event`]
245+
/// Creates an [`Event`] (possibly carrying exceptions) from a given [`tracing_core::Event`].
236246
pub fn event_from_event<'context, S>(
237247
event: &tracing_core::Event,
238248
ctx: impl Into<Option<Context<'context, S>>>,
@@ -245,10 +255,11 @@ where
245255
// information for this. However, it may contain a serialized error which we can parse to emit
246256
// an exception record.
247257
#[allow(unused_mut)]
248-
let (mut message, visitor) = extract_event_data_with_context(event, ctx.into());
258+
let (mut message, visitor) = extract_event_data_with_context(event, ctx.into(), false);
249259
let FieldVisitor {
250260
mut exceptions,
251261
mut json_values,
262+
store_errors_in_values: _,
252263
} = visitor;
253264

254265
// If there are a message, an exception, and we are capturing stack traces, then add the message
@@ -289,10 +300,10 @@ where
289300

290301
Event {
291302
logger: Some(event.metadata().target().to_owned()),
292-
level: convert_tracing_level(event.metadata().level()),
303+
level: level_to_sentry_level(event.metadata().level()),
293304
message,
294305
exception: exceptions.into(),
295-
tags: tags_from_event(&mut json_values),
306+
tags: extract_and_remove_tags(&mut json_values),
296307
contexts: contexts_from_event(event, json_values),
297308
..Default::default()
298309
}

sentry-tracing/src/layer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tracing_subscriber::registry::LookupSpan;
1313
use crate::converters::*;
1414
use crate::TAGS_PREFIX;
1515

16-
/// The action that Sentry should perform for a [`Metadata`]
16+
/// The action that Sentry should perform for a given [`Event`]
1717
#[derive(Debug, Clone, Copy)]
1818
pub enum EventFilter {
1919
/// Ignore the [`Event`]

sentry-tracing/src/lib.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
//! Support for automatic breadcrumb, event, and trace capturing from `tracing` events.
2-
//!
3-
//! The `tracing` crate is supported in three ways. First, events can be captured as breadcrumbs for
4-
//! later. Secondly, error events can be captured as events to Sentry. Finally, spans can be
5-
//! recorded as structured transaction events. By default, events above `Info` are recorded as
6-
//! breadcrumbs, events above `Error` are captured as error events, and spans above `Info` are
7-
//! recorded as transactions.
1+
//! Support for automatic breadcrumb, event, and trace capturing from `tracing` events and spans.
2+
//!
3+
//! The `tracing` crate is supported in three ways:
4+
//! - `tracing` events can be captured as Sentry events. These are grouped and show up in the Sentry
5+
//! [issues](https://docs.sentry.io/product/issues/) page, representing high severity issues to be
6+
//! acted upon.
7+
//! - `tracing` events can be captured as [breadcrumbs](https://docs.sentry.io/product/issues/issue-details/breadcrumbs/).
8+
//! Breadcrumbs create a trail of what happened prior to an event, and are therefore sent only when
9+
//! an event is captured, either manually through e.g. `sentry::capture_message` or through integrations
10+
//! (e.g. the panic integration is enabled (default) and a panic happens).
11+
//! - `tracing` spans can be captured as Sentry spans. These can be used to provide more contextual
12+
//! information for errors, diagnose [performance
13+
//! issues](https://docs.sentry.io/product/insights/overview/), and capture additional attributes to
14+
//! aggregate and compute [metrics](https://docs.sentry.io/product/explore/trace-explorer/).
15+
//!
16+
//! By default, events above `Info` are recorded as breadcrumbs, events above `Error` are captured
17+
//! as error events, and spans above `Info` are recorded as spans.
818
//!
919
//! # Configuration
1020
//!
@@ -23,29 +33,37 @@
2333
//! // Register the Sentry tracing layer to capture breadcrumbs, events, and spans:
2434
//! tracing_subscriber::registry()
2535
//! .with(tracing_subscriber::fmt::layer())
26-
//! .with(sentry_tracing::layer())
36+
//! .with(sentry::integrations::tracing::layer())
2737
//! .init();
2838
//! ```
2939
//!
30-
//! It is also possible to set an explicit filter, to customize which log events are captured by
31-
//! Sentry:
40+
//! You can customize the behavior of the layer by providing an explicit event filter, to customize which events
41+
//! are captured by Sentry and the data type they are mapped to.
42+
//! Similarly, you can provide a span filter to customize which spans are captured by Sentry.
3243
//!
3344
//! ```
34-
//! use sentry_tracing::EventFilter;
45+
//! use sentry::integrations::tracing::EventFilter;
3546
//! use tracing_subscriber::prelude::*;
3647
//!
37-
//! let sentry_layer = sentry_tracing::layer().event_filter(|md| match md.level() {
38-
//! &tracing::Level::ERROR => EventFilter::Event,
39-
//! _ => EventFilter::Ignore,
40-
//! });
48+
//! let sentry_layer = sentry::integrations::tracing::layer()
49+
//! .event_filter(|md| match *md.level() {
50+
//! tracing::Level::ERROR => EventFilter::Event,
51+
//! _ => EventFilter::Ignore,
52+
//! })
53+
//! .span_filter(|md| matches!(*md.level(), tracing::Level::ERROR | tracing::Level::WARN));
4154
//!
4255
//! tracing_subscriber::registry()
4356
//! .with(tracing_subscriber::fmt::layer())
4457
//! .with(sentry_layer)
4558
//! .init();
4659
//! ```
4760
//!
48-
//! # Logging Messages
61+
//! In addition, a custom event mapper can be provided, to fully customize if and how `tracing` events are converted to Sentry data.
62+
//!
63+
//! Note that if both an event mapper and event filter are set, the mapper takes precedence, thus the
64+
//! filter has no effect.
65+
//!
66+
//! # Capturing breadcrumbs
4967
//!
5068
//! Tracing events automatically create breadcrumbs that are attached to the current scope in
5169
//! Sentry. They show up on errors and transactions captured within this scope as shown in the

sentry/tests/test_tracing.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn test_tracing() {
2828

2929
let err = "NaN".parse::<usize>().unwrap_err();
3030
let err: &dyn std::error::Error = &err;
31+
tracing::warn!(something = err, "Breadcrumb with error");
3132
tracing::error!(err, tagname = "tagvalue");
3233
let _ = fn_errors();
3334
});
@@ -78,6 +79,7 @@ fn test_tracing() {
7879
);
7980

8081
let event = events.next().unwrap();
82+
assert_eq!(event.breadcrumbs.len(), 3);
8183
assert!(!event.exception.is_empty());
8284
assert_eq!(event.exception[0].ty, "ParseIntError");
8385
assert_eq!(
@@ -100,6 +102,17 @@ fn test_tracing() {
100102
_ => panic!("Wrong context type"),
101103
}
102104

105+
assert_eq!(event.breadcrumbs[2].level, sentry::Level::Warning);
106+
assert_eq!(
107+
event.breadcrumbs[2].message,
108+
Some("Breadcrumb with error".into())
109+
);
110+
assert!(event.breadcrumbs[2].data.contains_key("something"));
111+
assert_eq!(
112+
event.breadcrumbs[2].data.get("something").unwrap(),
113+
&Value::from(vec!("ParseIntError: invalid digit found in string"))
114+
);
115+
103116
let event = events.next().unwrap();
104117
assert_eq!(event.message, Some("I'm broken!".to_string()));
105118
}

0 commit comments

Comments
 (0)