Skip to content

Commit 20a5d48

Browse files
authored
feat(tracing): support combined EventFilters and EventMappings (#847)
1 parent 333b14e commit 20a5d48

File tree

7 files changed

+198
-48
lines changed

7 files changed

+198
-48
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## Unreleased
44

5+
### Breaking changes
6+
7+
- feat(tracing): support combined EventFilters and EventMappings (#847) by @lcian
8+
- `EventFilter` has been changed to a `bitflags` struct.
9+
- It's now possible to map a `tracing` event to multiple items in Sentry by combining multiple event filters in the `event_filter`, e.g. `tracing::Level::ERROR => EventFilter::Event | EventFilter::Log`.
10+
- It's also possible to use `EventMapping::Combined` to map a `tracing` event to multiple items in Sentry.
11+
- `ctx` in the signatures of `event_from_event`, `breadcrumb_from_event` and `log_from_event` has been changed to take `impl Into<Option<&'context Context<'context, S>>>` to avoid cloning the `Context` when mapping to multiple items.
12+
513
### Fixes
614

715
- fix(logs): stringify u64 attributes greater than `i64::MAX` (#846) by @lcian

Cargo.lock

Lines changed: 14 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sentry-tracing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ tracing-subscriber = { version = "0.3.1", default-features = false, features = [
2929
"std",
3030
] }
3131
sentry-backtrace = { version = "0.40.0", path = "../sentry-backtrace", optional = true }
32+
bitflags = "2.0.0"
3233

3334
[dev-dependencies]
3435
log = "0.4"

sentry-tracing/src/converters.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn extract_event_data(
7777
/// Extracts the message and metadata from an event, including the data in the current span.
7878
fn extract_event_data_with_context<S>(
7979
event: &tracing_core::Event,
80-
ctx: Option<Context<S>>,
80+
ctx: Option<&Context<S>>,
8181
store_errors_in_values: bool,
8282
) -> (Option<String>, FieldVisitor)
8383
where
@@ -182,7 +182,7 @@ impl Visit for FieldVisitor {
182182
/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`].
183183
pub fn breadcrumb_from_event<'context, S>(
184184
event: &tracing_core::Event,
185-
ctx: impl Into<Option<Context<'context, S>>>,
185+
ctx: impl Into<Option<&'context Context<'context, S>>>,
186186
) -> Breadcrumb
187187
where
188188
S: Subscriber + for<'a> LookupSpan<'a>,
@@ -261,7 +261,7 @@ fn contexts_from_event(
261261
/// Creates an [`Event`] (possibly carrying exceptions) from a given [`tracing_core::Event`].
262262
pub fn event_from_event<'context, S>(
263263
event: &tracing_core::Event,
264-
ctx: impl Into<Option<Context<'context, S>>>,
264+
ctx: impl Into<Option<&'context Context<'context, S>>>,
265265
) -> Event<'static>
266266
where
267267
S: Subscriber + for<'a> LookupSpan<'a>,
@@ -329,7 +329,7 @@ where
329329
#[cfg(feature = "logs")]
330330
pub fn log_from_event<'context, S>(
331331
event: &tracing_core::Event,
332-
ctx: impl Into<Option<Context<'context, S>>>,
332+
ctx: impl Into<Option<&'context Context<'context, S>>>,
333333
) -> Log
334334
where
335335
S: Subscriber + for<'a> LookupSpan<'a>,

sentry-tracing/src/layer.rs

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::cell::RefCell;
33
use std::collections::BTreeMap;
44
use std::sync::Arc;
55

6+
use bitflags::bitflags;
67
use sentry_core::protocol::Value;
78
use sentry_core::{Breadcrumb, TransactionOrSpan};
89
use tracing_core::field::Visit;
@@ -13,21 +14,22 @@ use tracing_subscriber::registry::LookupSpan;
1314
use crate::converters::*;
1415
use crate::TAGS_PREFIX;
1516

16-
/// The action that Sentry should perform for a given [`Event`]
17-
#[derive(Debug, Clone, Copy)]
18-
pub enum EventFilter {
19-
/// Ignore the [`Event`]
20-
Ignore,
21-
/// Create a [`Breadcrumb`] from this [`Event`]
22-
Breadcrumb,
23-
/// Create a [`sentry_core::protocol::Event`] from this [`Event`]
24-
Event,
25-
/// Create a [`sentry_core::protocol::Log`] from this [`Event`]
26-
#[cfg(feature = "logs")]
27-
Log,
17+
bitflags! {
18+
/// The action that Sentry should perform for a given [`Event`]
19+
#[derive(Debug, Clone, Copy)]
20+
pub struct EventFilter: u32 {
21+
/// Ignore the [`Event`]
22+
const Ignore = 0b000;
23+
/// Create a [`Breadcrumb`] from this [`Event`]
24+
const Breadcrumb = 0b001;
25+
/// Create a [`sentry_core::protocol::Event`] from this [`Event`]
26+
const Event = 0b010;
27+
/// Create a [`sentry_core::protocol::Log`] from this [`Event`]
28+
const Log = 0b100;
29+
}
2830
}
2931

30-
/// The type of data Sentry should ingest for a [`Event`]
32+
/// The type of data Sentry should ingest for an [`Event`].
3133
#[derive(Debug)]
3234
#[allow(clippy::large_enum_variant)]
3335
pub enum EventMapping {
@@ -40,6 +42,28 @@ pub enum EventMapping {
4042
/// Captures the [`sentry_core::protocol::Log`] to Sentry.
4143
#[cfg(feature = "logs")]
4244
Log(sentry_core::protocol::Log),
45+
/// Captures multiple items to Sentry.
46+
/// Nesting multiple `EventMapping::Combined` inside each other will cause the inner mappings to be ignored.
47+
Combined(CombinedEventMapping),
48+
}
49+
50+
/// A list of event mappings.
51+
#[derive(Debug)]
52+
pub struct CombinedEventMapping(Vec<EventMapping>);
53+
54+
impl From<EventMapping> for CombinedEventMapping {
55+
fn from(value: EventMapping) -> Self {
56+
match value {
57+
EventMapping::Combined(combined) => combined,
58+
_ => CombinedEventMapping(vec![value]),
59+
}
60+
}
61+
}
62+
63+
impl From<Vec<EventMapping>> for CombinedEventMapping {
64+
fn from(value: Vec<EventMapping>) -> Self {
65+
Self(value)
66+
}
4367
}
4468

4569
/// The default event filter.
@@ -211,30 +235,48 @@ where
211235
S: Subscriber + for<'a> LookupSpan<'a>,
212236
{
213237
fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
214-
let item = match &self.event_mapper {
238+
let items = match &self.event_mapper {
215239
Some(mapper) => mapper(event, ctx),
216240
None => {
217241
let span_ctx = self.with_span_attributes.then_some(ctx);
218-
match (self.event_filter)(event.metadata()) {
219-
EventFilter::Ignore => EventMapping::Ignore,
220-
EventFilter::Breadcrumb => {
221-
EventMapping::Breadcrumb(breadcrumb_from_event(event, span_ctx))
222-
}
223-
EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)),
224-
#[cfg(feature = "logs")]
225-
EventFilter::Log => EventMapping::Log(log_from_event(event, span_ctx)),
242+
let filter = (self.event_filter)(event.metadata());
243+
let mut items = vec![];
244+
if filter.contains(EventFilter::Breadcrumb) {
245+
items.push(EventMapping::Breadcrumb(breadcrumb_from_event(
246+
event,
247+
span_ctx.as_ref(),
248+
)));
249+
}
250+
if filter.contains(EventFilter::Event) {
251+
items.push(EventMapping::Event(event_from_event(
252+
event,
253+
span_ctx.as_ref(),
254+
)));
226255
}
256+
#[cfg(feature = "logs")]
257+
if filter.contains(EventFilter::Log) {
258+
items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref())));
259+
}
260+
EventMapping::Combined(CombinedEventMapping(items))
227261
}
228262
};
229-
230-
match item {
231-
EventMapping::Event(event) => {
232-
sentry_core::capture_event(event);
263+
let items = CombinedEventMapping::from(items);
264+
265+
for item in items.0 {
266+
match item {
267+
EventMapping::Ignore => (),
268+
EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
269+
EventMapping::Event(event) => {
270+
sentry_core::capture_event(event);
271+
}
272+
#[cfg(feature = "logs")]
273+
EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
274+
EventMapping::Combined(_) => {
275+
sentry_core::sentry_debug!(
276+
"[SentryLayer] found nested CombinedEventMapping, ignoring"
277+
)
278+
}
233279
}
234-
EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
235-
#[cfg(feature = "logs")]
236-
EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
237-
_ => (),
238280
}
239281
}
240282

sentry-tracing/src/lib.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
//!
8787
//! Tracing events can be captured as traditional structured logs in Sentry.
8888
//! This is gated by the `logs` feature flag and requires setting up a custom Event filter/mapper
89-
//! to capture logs.
89+
//! to capture logs. You also need to pass `enable_logs: true` in your `sentry::init` call.
9090
//!
9191
//! ```
9292
//! // assuming `tracing::Level::INFO => EventFilter::Log` in your `event_filter`
@@ -141,6 +141,23 @@
141141
//! tracing::error!(error = &custom_error as &dyn Error, "my operation failed");
142142
//! ```
143143
//!
144+
//! # Sending multiple items to Sentry
145+
//!
146+
//! To map a `tracing` event to multiple items in Sentry, you can combine multiple event filters
147+
//! using the bitwise or operator:
148+
//!
149+
//! ```
150+
//! let sentry_layer = sentry::integrations::tracing::layer()
151+
//! .event_filter(|md| match *md.level() {
152+
//! tracing::Level::ERROR => EventFilter::Event | EventFilter::Log,
153+
//! tracing::Level::TRACE => EventFilter::Ignore,
154+
//! _ => EventFilter::Log,
155+
//! })
156+
//! .span_filter(|md| matches!(*md.level(), tracing::Level::ERROR | tracing::Level::WARN));
157+
//! ```
158+
//!
159+
//! If you're using a custom event mapper instead of an event filter, use `EventMapping::Combined`.
160+
//!
144161
//! # Tracing Spans
145162
//!
146163
//! The integration automatically tracks `tracing` spans as spans in Sentry. A convenient way to do

0 commit comments

Comments
 (0)