From 477a5f815317554ecbbba376711ebec78c7b617b Mon Sep 17 00:00:00 2001 From: lcian Date: Wed, 18 Jun 2025 14:11:05 +0200 Subject: [PATCH 1/7] feat(tracing): support combined `EventFilter`s and `EventMapping`s --- Cargo.lock | 27 ++++----- sentry-tracing/Cargo.toml | 1 + sentry-tracing/src/converters.rs | 8 +-- sentry-tracing/src/layer.rs | 97 ++++++++++++++++++++++---------- 4 files changed, 86 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32221058..feb8b944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-sink", @@ -30,7 +30,7 @@ dependencies = [ "actix-service", "actix-utils", "base64", - "bitflags 2.9.0", + "bitflags 2.9.1", "brotli", "bytes", "bytestring", @@ -448,7 +448,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -470,9 +470,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -2152,7 +2152,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -2309,7 +2309,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -2407,7 +2407,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -2852,7 +2852,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -3028,7 +3028,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3041,7 +3041,7 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -3134,7 +3134,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", @@ -3334,6 +3334,7 @@ dependencies = [ name = "sentry-tracing" version = "0.40.0" dependencies = [ + "bitflags 2.9.1", "log", "sentry", "sentry-backtrace", @@ -4514,7 +4515,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] diff --git a/sentry-tracing/Cargo.toml b/sentry-tracing/Cargo.toml index b4ac5621..b7d3993e 100644 --- a/sentry-tracing/Cargo.toml +++ b/sentry-tracing/Cargo.toml @@ -29,6 +29,7 @@ tracing-subscriber = { version = "0.3.1", default-features = false, features = [ "std", ] } sentry-backtrace = { version = "0.40.0", path = "../sentry-backtrace", optional = true } +bitflags = "2.9.1" [dev-dependencies] log = "0.4" diff --git a/sentry-tracing/src/converters.rs b/sentry-tracing/src/converters.rs index 65735f54..d09f5325 100644 --- a/sentry-tracing/src/converters.rs +++ b/sentry-tracing/src/converters.rs @@ -77,7 +77,7 @@ fn extract_event_data( /// Extracts the message and metadata from an event, including the data in the current span. fn extract_event_data_with_context( event: &tracing_core::Event, - ctx: Option>, + ctx: Option<&Context>, store_errors_in_values: bool, ) -> (Option, FieldVisitor) where @@ -182,7 +182,7 @@ impl Visit for FieldVisitor { /// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`]. pub fn breadcrumb_from_event<'context, S>( event: &tracing_core::Event, - ctx: impl Into>>, + ctx: impl Into>>, ) -> Breadcrumb where S: Subscriber + for<'a> LookupSpan<'a>, @@ -261,7 +261,7 @@ fn contexts_from_event( /// Creates an [`Event`] (possibly carrying exceptions) from a given [`tracing_core::Event`]. pub fn event_from_event<'context, S>( event: &tracing_core::Event, - ctx: impl Into>>, + ctx: impl Into>>, ) -> Event<'static> where S: Subscriber + for<'a> LookupSpan<'a>, @@ -329,7 +329,7 @@ where #[cfg(feature = "logs")] pub fn log_from_event<'context, S>( event: &tracing_core::Event, - ctx: impl Into>>, + ctx: impl Into>>, ) -> Log where S: Subscriber + for<'a> LookupSpan<'a>, diff --git a/sentry-tracing/src/layer.rs b/sentry-tracing/src/layer.rs index f9cbf3aa..2787de5c 100644 --- a/sentry-tracing/src/layer.rs +++ b/sentry-tracing/src/layer.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::collections::BTreeMap; use std::sync::Arc; +use bitflags::bitflags; use sentry_core::protocol::Value; use sentry_core::{Breadcrumb, TransactionOrSpan}; use tracing_core::field::Visit; @@ -13,21 +14,22 @@ use tracing_subscriber::registry::LookupSpan; use crate::converters::*; use crate::TAGS_PREFIX; -/// The action that Sentry should perform for a given [`Event`] -#[derive(Debug, Clone, Copy)] -pub enum EventFilter { - /// Ignore the [`Event`] - Ignore, - /// Create a [`Breadcrumb`] from this [`Event`] - Breadcrumb, - /// Create a [`sentry_core::protocol::Event`] from this [`Event`] - Event, - /// Create a [`sentry_core::protocol::Log`] from this [`Event`] - #[cfg(feature = "logs")] - Log, +bitflags! { + /// The action that Sentry should perform for a given [`Event`] + #[derive(Debug, Clone, Copy)] + pub struct EventFilter: u32 { + /// Ignore the [`Event`] + const Ignore = 0b000; + /// Create a [`Breadcrumb`] from this [`Event`] + const Breadcrumb = 0b001; + /// Create a [`sentry_core::protocol::Event`] from this [`Event`] + const Event = 0b010; + /// Create a [`sentry_core::protocol::Log`] from this [`Event`] + const Log = 0b100; + } } -/// The type of data Sentry should ingest for a [`Event`] +/// The type of data Sentry should ingest for an [`Event`]. #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum EventMapping { @@ -40,6 +42,27 @@ pub enum EventMapping { /// Captures the [`sentry_core::protocol::Log`] to Sentry. #[cfg(feature = "logs")] Log(sentry_core::protocol::Log), + /// Captures multiple items to Sentry. + Combined(CombinedEventMapping), +} + +/// A list of event mappings. +#[derive(Debug)] +pub struct CombinedEventMapping(Vec); + +impl From for CombinedEventMapping { + fn from(value: EventMapping) -> Self { + match value { + EventMapping::Combined(combined) => combined, + _ => CombinedEventMapping(vec![value]), + } + } +} + +impl From> for CombinedEventMapping { + fn from(value: Vec) -> Self { + Self(value) + } } /// The default event filter. @@ -211,30 +234,44 @@ where S: Subscriber + for<'a> LookupSpan<'a>, { fn on_event(&self, event: &Event, ctx: Context<'_, S>) { - let item = match &self.event_mapper { + let items = match &self.event_mapper { Some(mapper) => mapper(event, ctx), None => { let span_ctx = self.with_span_attributes.then_some(ctx); - match (self.event_filter)(event.metadata()) { - EventFilter::Ignore => EventMapping::Ignore, - EventFilter::Breadcrumb => { - EventMapping::Breadcrumb(breadcrumb_from_event(event, span_ctx)) - } - EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)), - #[cfg(feature = "logs")] - EventFilter::Log => EventMapping::Log(log_from_event(event, span_ctx)), + let filter = (self.event_filter)(event.metadata()); + let mut items = vec![]; + if filter.contains(EventFilter::Breadcrumb) { + items.push(EventMapping::Breadcrumb(breadcrumb_from_event( + event, + span_ctx.as_ref(), + ))); } + if filter.contains(EventFilter::Event) { + items.push(EventMapping::Event(event_from_event( + event, + span_ctx.as_ref(), + ))); + } + #[cfg(feature = "logs")] + if filter.contains(EventFilter::Log) { + items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref()))); + } + EventMapping::Combined(CombinedEventMapping(items)) } }; - - match item { - EventMapping::Event(event) => { - sentry_core::capture_event(event); + let items = CombinedEventMapping::from(items); + + for item in items.0 { + match item { + EventMapping::Ignore => (), + EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb), + EventMapping::Event(event) => { + sentry_core::capture_event(event); + } + #[cfg(feature = "logs")] + EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)), + _ => (), } - EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb), - #[cfg(feature = "logs")] - EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)), - _ => (), } } From 49f727317ecf7ba031c508db87f95df006ecf806 Mon Sep 17 00:00:00 2001 From: lcian Date: Wed, 18 Jun 2025 14:39:24 +0200 Subject: [PATCH 2/7] tests --- sentry/tests/test_tracing.rs | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/sentry/tests/test_tracing.rs b/sentry/tests/test_tracing.rs index 6ec51317..f71e5b23 100644 --- a/sentry/tests/test_tracing.rs +++ b/sentry/tests/test_tracing.rs @@ -277,3 +277,84 @@ fn test_tracing_logs() { _ => panic!("expected item container"), } } + +#[test] +fn test_combined_event_filters() { + let sentry_layer = sentry_tracing::layer().event_filter(|md| match *md.level() { + tracing::Level::ERROR => { + sentry_tracing::EventFilter::Breadcrumb | sentry_tracing::EventFilter::Event + } + tracing::Level::WARN => sentry_tracing::EventFilter::Event, + _ => sentry_tracing::EventFilter::Ignore, + }); + + let _dispatcher = tracing_subscriber::registry() + .with(sentry_layer) + .set_default(); + + let events = sentry::test::with_captured_events(|| { + tracing::error!("Both a breadcrumb and an event"); + tracing::warn!("An event"); + }); + + assert_eq!(events.len(), 2); + + assert_eq!( + events[0].message, + Some("Both a breadcrumb and an event".to_owned()) + ); + + assert_eq!(events[1].message, Some("An event".to_owned())); + assert_eq!(events[1].breadcrumbs.len(), 1); + assert_eq!( + events[1].breadcrumbs[0].message, + Some("Both a breadcrumb and an event".into()) + ); +} + +#[test] +fn test_combined_event_mapper() { + let sentry_layer = + sentry_tracing::layer().event_mapper(|event, ctx| match *event.metadata().level() { + tracing::Level::ERROR => { + let breadcrumb = sentry_tracing::breadcrumb_from_event(event, Some(&ctx)); + let sentry_event = sentry_tracing::event_from_event(event, Some(&ctx)); + + sentry_tracing::EventMapping::Combined( + vec![ + sentry_tracing::EventMapping::Breadcrumb(breadcrumb), + sentry_tracing::EventMapping::Event(sentry_event), + ] + .into(), + ) + } + tracing::Level::WARN => { + let sentry_event = sentry_tracing::event_from_event(event, Some(&ctx)); + sentry_tracing::EventMapping::Event(sentry_event) + } + _ => sentry_tracing::EventMapping::Ignore, + }); + + let _dispatcher = tracing_subscriber::registry() + .with(sentry_layer) + .set_default(); + + let events = sentry::test::with_captured_events(|| { + tracing::error!("Both a breadcrumb and an event"); + tracing::warn!("An event"); + }); + + assert_eq!(events.len(), 2); + + assert_eq!( + events[0].message, + Some("Both a breadcrumb and an event".to_owned()) + ); + + assert_eq!(events[1].message, Some("An event".to_owned())); + assert_eq!(events[1].breadcrumbs.len(), 1); + assert_eq!( + events[1].breadcrumbs[0].message, + Some("Both a breadcrumb and an event".into()) + ); +} From 5497d156eb6926d3be8d9314b8707f5565013b78 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 20 Jun 2025 10:39:54 +0200 Subject: [PATCH 3/7] update --- sentry-tracing/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-tracing/Cargo.toml b/sentry-tracing/Cargo.toml index b7d3993e..00cc7845 100644 --- a/sentry-tracing/Cargo.toml +++ b/sentry-tracing/Cargo.toml @@ -29,7 +29,7 @@ tracing-subscriber = { version = "0.3.1", default-features = false, features = [ "std", ] } sentry-backtrace = { version = "0.40.0", path = "../sentry-backtrace", optional = true } -bitflags = "2.9.1" +bitflags = "2" [dev-dependencies] log = "0.4" From d2a19705b2c84a185d7f89ad3747e9b10e22f71d Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 20 Jun 2025 10:47:05 +0200 Subject: [PATCH 4/7] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac98b5cc..40fde267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Breaking changes + +- feat(tracing): support combined EventFilters and EventMappings (#847) by @lcian + - `EventFilter` has been changed to a `bitflags` struct. + - 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`. + - It's also possible to use `EventMapping::Combined` to map a `tracing` event to multiple items in Sentry. + - `ctx` in the signatures of `event_from_event`, `breadcrumb_from_event` and `log_from_event` has been changed to take `impl Into>>` to avoid cloning the `Context` when mapping to multiple items. + ### Fixes - fix(logs): stringify u64 attributes greater than `i64::MAX` (#846) by @lcian From 9e8bc1b6a9e73f6abada69116212c069b50dfb5c Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 20 Jun 2025 14:52:18 +0200 Subject: [PATCH 5/7] update --- sentry-tracing/Cargo.toml | 2 +- sentry-tracing/src/layer.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-tracing/Cargo.toml b/sentry-tracing/Cargo.toml index 00cc7845..73f94cbc 100644 --- a/sentry-tracing/Cargo.toml +++ b/sentry-tracing/Cargo.toml @@ -29,7 +29,7 @@ tracing-subscriber = { version = "0.3.1", default-features = false, features = [ "std", ] } sentry-backtrace = { version = "0.40.0", path = "../sentry-backtrace", optional = true } -bitflags = "2" +bitflags = "2.0.0" [dev-dependencies] log = "0.4" diff --git a/sentry-tracing/src/layer.rs b/sentry-tracing/src/layer.rs index 2787de5c..931fc253 100644 --- a/sentry-tracing/src/layer.rs +++ b/sentry-tracing/src/layer.rs @@ -43,6 +43,7 @@ pub enum EventMapping { #[cfg(feature = "logs")] Log(sentry_core::protocol::Log), /// Captures multiple items to Sentry. + /// Nesting multiple `EventMapping::Combined` inside each other will cause the inner mappings to be ignored. Combined(CombinedEventMapping), } @@ -270,7 +271,7 @@ where } #[cfg(feature = "logs")] EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)), - _ => (), + EventMapping::Combined(_) => sentry_debug!("[SentryLayer] found nested CombinedEventMapping, ignoring"), } } } From 085f3f784e20ee5d564856af87a8146cde20a20d Mon Sep 17 00:00:00 2001 From: lcian Date: Sun, 22 Jun 2025 21:42:02 +0200 Subject: [PATCH 6/7] improve --- sentry-tracing/src/layer.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry-tracing/src/layer.rs b/sentry-tracing/src/layer.rs index 931fc253..582427e7 100644 --- a/sentry-tracing/src/layer.rs +++ b/sentry-tracing/src/layer.rs @@ -271,7 +271,11 @@ where } #[cfg(feature = "logs")] EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)), - EventMapping::Combined(_) => sentry_debug!("[SentryLayer] found nested CombinedEventMapping, ignoring"), + EventMapping::Combined(_) => { + sentry_core::sentry_debug!( + "[SentryLayer] found nested CombinedEventMapping, ignoring" + ) + } } } } From 07dd2c75a76c67373a3edb3f38cb904c0504cc49 Mon Sep 17 00:00:00 2001 From: lcian Date: Sun, 22 Jun 2025 21:46:42 +0200 Subject: [PATCH 7/7] improve --- sentry-tracing/src/lib.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/sentry-tracing/src/lib.rs b/sentry-tracing/src/lib.rs index 97115ba0..01483b8a 100644 --- a/sentry-tracing/src/lib.rs +++ b/sentry-tracing/src/lib.rs @@ -86,7 +86,7 @@ //! //! Tracing events can be captured as traditional structured logs in Sentry. //! This is gated by the `logs` feature flag and requires setting up a custom Event filter/mapper -//! to capture logs. +//! to capture logs. You also need to pass `enable_logs: true` in your `sentry::init` call. //! //! ``` //! // assuming `tracing::Level::INFO => EventFilter::Log` in your `event_filter` @@ -141,6 +141,23 @@ //! tracing::error!(error = &custom_error as &dyn Error, "my operation failed"); //! ``` //! +//! # Sending multiple items to Sentry +//! +//! To map a `tracing` event to multiple items in Sentry, you can combine multiple event filters +//! using the bitwise or operator: +//! +//! ``` +//! let sentry_layer = sentry::integrations::tracing::layer() +//! .event_filter(|md| match *md.level() { +//! tracing::Level::ERROR => EventFilter::Event | EventFilter::Log, +//! tracing::Level::TRACE => EventFilter::Ignore, +//! _ => EventFilter::Log, +//! }) +//! .span_filter(|md| matches!(*md.level(), tracing::Level::ERROR | tracing::Level::WARN)); +//! ``` +//! +//! If you're using a custom event mapper instead of an event filter, use `EventMapping::Combined`. +//! //! # Tracing Spans //! //! The integration automatically tracks `tracing` spans as spans in Sentry. A convenient way to do