Skip to content

feat(tracing): support combined EventFilters and EventMappings #847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<&'context Context<'context, S>>>` to avoid cloning the `Context` when mapping to multiple items.

### Fixes

- fix(logs): stringify u64 attributes greater than `i64::MAX` (#846) by @lcian
Expand Down
27 changes: 14 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sentry-tracing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.0.0"

[dev-dependencies]
log = "0.4"
Expand Down
8 changes: 4 additions & 4 deletions sentry-tracing/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S>(
event: &tracing_core::Event,
ctx: Option<Context<S>>,
ctx: Option<&Context<S>>,
store_errors_in_values: bool,
) -> (Option<String>, FieldVisitor)
where
Expand Down Expand Up @@ -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<Option<Context<'context, S>>>,
ctx: impl Into<Option<&'context Context<'context, S>>>,
) -> Breadcrumb
where
S: Subscriber + for<'a> LookupSpan<'a>,
Expand Down Expand Up @@ -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<Option<Context<'context, S>>>,
ctx: impl Into<Option<&'context Context<'context, S>>>,
) -> Event<'static>
where
S: Subscriber + for<'a> LookupSpan<'a>,
Expand Down Expand Up @@ -329,7 +329,7 @@ where
#[cfg(feature = "logs")]
pub fn log_from_event<'context, S>(
event: &tracing_core::Event,
ctx: impl Into<Option<Context<'context, S>>>,
ctx: impl Into<Option<&'context Context<'context, S>>>,
) -> Log
where
S: Subscriber + for<'a> LookupSpan<'a>,
Expand Down
102 changes: 72 additions & 30 deletions sentry-tracing/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -40,6 +42,28 @@ pub enum EventMapping {
/// Captures the [`sentry_core::protocol::Log`] to Sentry.
#[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),
}

/// A list of event mappings.
#[derive(Debug)]
pub struct CombinedEventMapping(Vec<EventMapping>);

impl From<EventMapping> for CombinedEventMapping {
fn from(value: EventMapping) -> Self {
match value {
EventMapping::Combined(combined) => combined,
_ => CombinedEventMapping(vec![value]),
}
}
}

impl From<Vec<EventMapping>> for CombinedEventMapping {
fn from(value: Vec<EventMapping>) -> Self {
Self(value)
}
}

/// The default event filter.
Expand Down Expand Up @@ -211,30 +235,48 @@ 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::Combined(_) => {
sentry_core::sentry_debug!(
"[SentryLayer] found nested CombinedEventMapping, ignoring"
)
}
}
EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
#[cfg(feature = "logs")]
EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
_ => (),
}
}

Expand Down
19 changes: 18 additions & 1 deletion sentry-tracing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down
Loading