Skip to content

Commit dfdb7b6

Browse files
authored
tracing: send spans' attributes along with the event ones (#629)
1 parent c53da38 commit dfdb7b6

File tree

3 files changed

+123
-13
lines changed

3 files changed

+123
-13
lines changed

sentry-core/src/performance.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,25 @@ pub struct Transaction {
399399
pub(crate) inner: TransactionArc,
400400
}
401401

402+
/// Iterable for a transaction's [`extra` field](protocol::Transaction::extra).
403+
pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
404+
405+
impl<'a> TransactionData<'a> {
406+
/// Iterate over the `extra` map
407+
/// of the [transaction][protocol::Transaction].
408+
///
409+
/// If the transaction not sampled for sending,
410+
/// the metadata will not be populated at all
411+
/// so the produced iterator is empty.
412+
pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
413+
if let Some(ref rx) = self.0.transaction {
414+
Box::new(rx.extra.iter())
415+
} else {
416+
Box::new(std::iter::empty())
417+
}
418+
}
419+
}
420+
402421
impl Transaction {
403422
#[cfg(feature = "client")]
404423
fn new(mut client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
@@ -464,6 +483,18 @@ impl Transaction {
464483
}
465484
}
466485

486+
/// Returns an iterating accessor to the transaction's [`extra` field](protocol::Transaction::extra).
487+
///
488+
/// # Concurrency
489+
/// In order to obtain any kind of reference to the `extra` field,
490+
/// a `Mutex` needs to be locked. The returned `TransactionData` holds on to this lock
491+
/// for as long as it lives. Therefore you must take care not to keep the returned
492+
/// `TransactionData` around too long or it will never relinquish the lock and you may run into
493+
/// a deadlock.
494+
pub fn data(&self) -> TransactionData {
495+
TransactionData(self.inner.lock().unwrap())
496+
}
497+
467498
/// Get the TransactionContext of the Transaction.
468499
///
469500
/// Note that this clones the underlying value.

sentry-tracing/src/converters.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use std::collections::BTreeMap;
22
use std::error::Error;
33

44
use sentry_core::protocol::{Event, Exception, Mechanism, Thread, Value};
5-
use sentry_core::{event_from_error, Breadcrumb, Level};
5+
use sentry_core::{event_from_error, Breadcrumb, Level, TransactionOrSpan};
66
use tracing_core::field::{Field, Visit};
77
use tracing_core::{span, Subscriber};
88
use tracing_subscriber::layer::Context;
99
use tracing_subscriber::registry::LookupSpan;
1010

11+
use super::layer::SentrySpanData;
12+
1113
/// Converts a [`tracing_core::Level`] to a Sentry [`Level`]
1214
fn convert_tracing_level(level: &tracing_core::Level) -> Level {
1315
match level {
@@ -29,6 +31,7 @@ fn level_to_exception_type(level: &tracing_core::Level) -> &'static str {
2931
}
3032

3133
/// Extracts the message and metadata from an event
34+
/// and also optionally from its spans chain.
3235
fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisitor) {
3336
// Find message of the event, if any
3437
let mut visitor = FieldVisitor::default();
@@ -44,6 +47,52 @@ fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisi
4447
(message, visitor)
4548
}
4649

50+
fn extract_event_data_with_context<S>(
51+
event: &tracing_core::Event,
52+
ctx: Option<Context<S>>,
53+
) -> (Option<String>, FieldVisitor)
54+
where
55+
S: Subscriber + for<'a> LookupSpan<'a>,
56+
{
57+
let (message, mut visitor) = extract_event_data(event);
58+
59+
// Add the context fields of every parent span.
60+
let current_span = ctx.as_ref().and_then(|ctx| {
61+
event
62+
.parent()
63+
.and_then(|id| ctx.span(id))
64+
.or_else(|| ctx.lookup_current())
65+
});
66+
if let Some(span) = current_span {
67+
for span in span.scope() {
68+
let name = span.name();
69+
let ext = span.extensions();
70+
if let Some(span_data) = ext.get::<SentrySpanData>() {
71+
match &span_data.sentry_span {
72+
TransactionOrSpan::Span(span) => {
73+
for (key, value) in span.data().iter() {
74+
if key != "message" {
75+
let key = format!("{}:{}", name, key);
76+
visitor.json_values.insert(key, value.clone());
77+
}
78+
}
79+
}
80+
TransactionOrSpan::Transaction(transaction) => {
81+
for (key, value) in transaction.data().iter() {
82+
if key != "message" {
83+
let key = format!("{}:{}", name, key);
84+
visitor.json_values.insert(key, value.clone());
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}
92+
93+
(message, visitor)
94+
}
95+
4796
/// Extracts the message and metadata from a span
4897
pub(crate) fn extract_span_data(
4998
attrs: &span::Attributes,
@@ -174,11 +223,14 @@ fn contexts_from_event(
174223
}
175224

176225
/// Creates an [`Event`] from a given [`tracing_core::Event`]
177-
pub fn event_from_event<S>(event: &tracing_core::Event, _ctx: Context<S>) -> Event<'static>
226+
pub fn event_from_event<'context, S>(
227+
event: &tracing_core::Event,
228+
ctx: impl Into<Option<Context<'context, S>>>,
229+
) -> Event<'static>
178230
where
179231
S: Subscriber + for<'a> LookupSpan<'a>,
180232
{
181-
let (message, mut visitor) = extract_event_data(event);
233+
let (message, mut visitor) = extract_event_data_with_context(event, ctx.into());
182234

183235
Event {
184236
logger: Some(event.metadata().target().to_owned()),
@@ -191,15 +243,18 @@ where
191243
}
192244

193245
/// Creates an exception [`Event`] from a given [`tracing_core::Event`]
194-
pub fn exception_from_event<S>(event: &tracing_core::Event, _ctx: Context<S>) -> Event<'static>
246+
pub fn exception_from_event<'context, S>(
247+
event: &tracing_core::Event,
248+
ctx: impl Into<Option<Context<'context, S>>>,
249+
) -> Event<'static>
195250
where
196251
S: Subscriber + for<'a> LookupSpan<'a>,
197252
{
198253
// Exception records in Sentry need a valid type, value and full stack trace to support
199254
// proper grouping and issue metadata generation. tracing_core::Record does not contain sufficient
200255
// information for this. However, it may contain a serialized error which we can parse to emit
201256
// an exception record.
202-
let (mut message, visitor) = extract_event_data(event);
257+
let (mut message, visitor) = extract_event_data_with_context(event, ctx.into());
203258
let FieldVisitor {
204259
mut exceptions,
205260
mut json_values,

sentry-tracing/src/layer.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ pub struct SentryLayer<S> {
6161
event_mapper: Option<EventMapper<S>>,
6262

6363
span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
64+
65+
with_span_attributes: bool,
6466
}
6567

6668
impl<S> SentryLayer<S> {
@@ -104,6 +106,19 @@ impl<S> SentryLayer<S> {
104106
self.span_filter = Box::new(filter);
105107
self
106108
}
109+
110+
/// Enable every parent span's attributes to be sent along with own event's attributes.
111+
///
112+
/// Note that the root span is considered a [transaction][sentry_core::protocol::Transaction]
113+
/// so its context will only be grabbed only if you set the transaction to be sampled.
114+
/// The most straightforward way to do this is to set
115+
/// the [traces_sample_rate][sentry_core::ClientOptions::traces_sample_rate] to `1.0`
116+
/// while configuring your sentry client.
117+
#[must_use]
118+
pub fn enable_span_attributes(mut self) -> Self {
119+
self.with_span_attributes = true;
120+
self
121+
}
107122
}
108123

109124
impl<S> Default for SentryLayer<S>
@@ -116,15 +131,17 @@ where
116131
event_mapper: None,
117132

118133
span_filter: Box::new(default_span_filter),
134+
135+
with_span_attributes: false,
119136
}
120137
}
121138
}
122139

123140
/// Data that is attached to the tracing Spans `extensions`, in order to
124141
/// `finish` the corresponding sentry span `on_close`, and re-set its parent as
125142
/// the *current* span.
126-
struct SentrySpanData {
127-
sentry_span: TransactionOrSpan,
143+
pub(super) struct SentrySpanData {
144+
pub(super) sentry_span: TransactionOrSpan,
128145
parent_sentry_span: Option<TransactionOrSpan>,
129146
}
130147

@@ -135,12 +152,19 @@ where
135152
fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
136153
let item = match &self.event_mapper {
137154
Some(mapper) => mapper(event, ctx),
138-
None => match (self.event_filter)(event.metadata()) {
139-
EventFilter::Ignore => EventMapping::Ignore,
140-
EventFilter::Breadcrumb => EventMapping::Breadcrumb(breadcrumb_from_event(event)),
141-
EventFilter::Event => EventMapping::Event(event_from_event(event, ctx)),
142-
EventFilter::Exception => EventMapping::Event(exception_from_event(event, ctx)),
143-
},
155+
None => {
156+
let span_ctx = self.with_span_attributes.then_some(ctx);
157+
match (self.event_filter)(event.metadata()) {
158+
EventFilter::Ignore => EventMapping::Ignore,
159+
EventFilter::Breadcrumb => {
160+
EventMapping::Breadcrumb(breadcrumb_from_event(event))
161+
}
162+
EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)),
163+
EventFilter::Exception => {
164+
EventMapping::Event(exception_from_event(event, span_ctx))
165+
}
166+
}
167+
}
144168
};
145169

146170
match item {

0 commit comments

Comments
 (0)