Skip to content

Commit de85f12

Browse files
committed
Add option to enable propagation of parent span tags
1 parent 6401cfc commit de85f12

File tree

6 files changed

+175
-35
lines changed

6 files changed

+175
-35
lines changed

sentry-core/src/performance.rs

+59
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,22 @@ pub struct Transaction {
526526
pub(crate) inner: TransactionArc,
527527
}
528528

529+
/// Iterable for a transaction's [tags attributes](protocol::Transaction::tags).
530+
pub struct TransactionTags<'a>(MutexGuard<'a, TransactionInner>);
531+
impl<'a> TransactionTags<'a> {
532+
#[inline]
533+
/// Returns iterator over available tags, if transaction is sampled
534+
pub fn iter(&'a self) -> Option<impl Iterator<Item = (&'a String, &'a String)> + 'a> {
535+
self.0.transaction.as_ref().map(|tx| tx.tags.iter())
536+
}
537+
538+
#[inline]
539+
/// Converts self into data pointer
540+
pub fn into_data(self) -> TransactionData<'a> {
541+
TransactionData(self.0)
542+
}
543+
}
544+
529545
/// Iterable for a transaction's [data attributes](protocol::TraceContext::data).
530546
pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
531547

@@ -647,6 +663,12 @@ impl Transaction {
647663
TransactionData(self.inner.lock().unwrap())
648664
}
649665

666+
/// Returns an iterating accessor to the transaction's
667+
/// [tags attributes](protocol::Transaction::tags).
668+
pub fn tags(&self) -> TransactionTags {
669+
TransactionTags(self.inner.lock().unwrap())
670+
}
671+
650672
/// Get the TransactionContext of the Transaction.
651673
///
652674
/// Note that this clones the underlying value.
@@ -756,6 +778,38 @@ impl Transaction {
756778
}
757779
}
758780

781+
/// A smart pointer to a span's [`tags` field](protocol::Span::tags).
782+
pub struct Tags<'a>(MutexGuard<'a, protocol::Span>);
783+
784+
impl<'a> Tags<'a> {
785+
/// Set some tag to be sent with this Span.
786+
pub fn set_tag(&mut self, key: String, value: String) {
787+
self.0.tags.insert(key, value);
788+
}
789+
790+
#[inline]
791+
/// Moves pointer to [`data` field](protocol::Span::data)
792+
pub fn into_data(self) -> Data<'a> {
793+
Data(self.0)
794+
}
795+
}
796+
797+
impl Deref for Tags<'_> {
798+
type Target = BTreeMap<String, String>;
799+
800+
#[inline]
801+
fn deref(&self) -> &Self::Target {
802+
&self.0.tags
803+
}
804+
}
805+
806+
impl DerefMut for Tags<'_> {
807+
#[inline]
808+
fn deref_mut(&mut self) -> &mut Self::Target {
809+
&mut self.0.tags
810+
}
811+
}
812+
759813
/// A smart pointer to a span's [`data` field](protocol::Span::data).
760814
pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
761815

@@ -826,6 +880,11 @@ impl Span {
826880
Data(self.span.lock().unwrap())
827881
}
828882

883+
/// Returns a smart pointer to the span's [`tags` field](protocol::Span::tags).
884+
pub fn tags(&self) -> Tags {
885+
Tags(self.span.lock().unwrap())
886+
}
887+
829888
/// Get the TransactionContext of the Span.
830889
///
831890
/// Note that this clones the underlying value.

sentry-tracing/src/converters.rs

+57-20
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use tracing_subscriber::layer::Context;
99
use tracing_subscriber::registry::LookupSpan;
1010

1111
use super::layer::SentrySpanData;
12-
use crate::TAGS_PREFIX;
12+
use crate::{SpanPropagation, TAGS_PREFIX};
1313

1414
/// Converts a [`tracing_core::Level`] to a Sentry [`Level`]
1515
fn convert_tracing_level(level: &tracing_core::Level) -> Level {
@@ -54,39 +54,55 @@ fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisi
5454

5555
fn extract_event_data_with_context<S>(
5656
event: &tracing_core::Event,
57-
ctx: Option<Context<S>>,
57+
ctx: Context<S>,
58+
propagation: Option<SpanPropagation>,
5859
) -> (Option<String>, FieldVisitor)
5960
where
6061
S: Subscriber + for<'a> LookupSpan<'a>,
6162
{
6263
let (message, mut visitor) = extract_event_data(event);
6364

64-
// Add the context fields of every parent span.
65-
let current_span = ctx.as_ref().and_then(|ctx| {
65+
// Add the context fields of every parent span, if propagation is enabled
66+
let propagation_span = propagation.and_then(|propagation| {
6667
event
6768
.parent()
68-
.and_then(|id| ctx.span(id))
69-
.or_else(|| ctx.lookup_current())
69+
.and_then(|id| ctx.span(id).map(|span| (propagation, span)))
70+
.or_else(|| ctx.lookup_current().map(|span| (propagation, span)))
7071
});
71-
if let Some(span) = current_span {
72+
if let Some((propagation, span)) = propagation_span {
7273
for span in span.scope() {
7374
let name = span.name();
7475
let ext = span.extensions();
7576
if let Some(span_data) = ext.get::<SentrySpanData>() {
7677
match &span_data.sentry_span {
7778
TransactionOrSpan::Span(span) => {
78-
for (key, value) in span.data().iter() {
79-
if key != "message" {
80-
let key = format!("{}:{}", name, key);
81-
visitor.json_values.insert(key, value.clone());
79+
let tags = span.tags();
80+
81+
if propagation.is_tags_enabled() {
82+
for (key, value) in tags.iter() {
83+
visitor.propagate_span_tag(key, value);
84+
}
85+
}
86+
87+
if propagation.is_attrs_enabled() {
88+
for (key, value) in tags.into_data().iter() {
89+
visitor.propagate_span_attr(key, value, name);
8290
}
8391
}
8492
}
8593
TransactionOrSpan::Transaction(transaction) => {
86-
for (key, value) in transaction.data().iter() {
87-
if key != "message" {
88-
let key = format!("{}:{}", name, key);
89-
visitor.json_values.insert(key, value.clone());
94+
let tags = transaction.tags();
95+
if propagation.is_tags_enabled() {
96+
if let Some(tags) = tags.iter() {
97+
for (key, value) in tags {
98+
visitor.propagate_span_tag(key, value);
99+
}
100+
}
101+
}
102+
103+
if propagation.is_attrs_enabled() {
104+
for (key, value) in tags.into_data().iter() {
105+
visitor.propagate_span_attr(key, value, name);
90106
}
91107
}
92108
}
@@ -106,6 +122,19 @@ pub(crate) struct FieldVisitor {
106122
}
107123

108124
impl FieldVisitor {
125+
fn propagate_span_tag(&mut self, key: &str, value: &str) {
126+
//Propagate tags as it is, it will be extracted later on
127+
let tag = format!("{TAGS_PREFIX}{key}");
128+
self.json_values.entry(tag).or_insert_with(|| value.into());
129+
}
130+
131+
fn propagate_span_attr(&mut self, key: &str, value: &Value, span_name: &str) {
132+
if key != "message" {
133+
let key = format!("{}:{}", span_name, key);
134+
self.json_values.insert(key, value.clone());
135+
}
136+
}
137+
109138
fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
110139
self.json_values
111140
.insert(field.name().to_owned(), value.into());
@@ -144,12 +173,19 @@ impl Visit for FieldVisitor {
144173
/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`]
145174
pub fn breadcrumb_from_event<'context, S>(
146175
event: &tracing_core::Event,
147-
ctx: impl Into<Option<Context<'context, S>>>,
176+
ctx: Context<'context, S>,
177+
mut propagation: Option<SpanPropagation>,
148178
) -> Breadcrumb
149179
where
150180
S: Subscriber + for<'a> LookupSpan<'a>,
151181
{
152-
let (message, visitor) = extract_event_data_with_context(event, ctx.into());
182+
if let Some(propagation) = propagation.as_mut() {
183+
if propagation.is_attrs_enabled() {
184+
//Breadcrumb has no tags, so propagate only attributes
185+
*propagation = SpanPropagation::Attributes;
186+
}
187+
}
188+
let (message, visitor) = extract_event_data_with_context(event, ctx, propagation);
153189

154190
let FieldVisitor {
155191
exceptions,
@@ -232,10 +268,11 @@ fn contexts_from_event(
232268
context
233269
}
234270

235-
/// Creates an [`Event`] (possibly carrying an exception) from a given [`tracing_core::Event`]
271+
/// Creates an [`Event`] from a given [`tracing_core::Event`]
236272
pub fn event_from_event<'context, S>(
237273
event: &tracing_core::Event,
238-
ctx: impl Into<Option<Context<'context, S>>>,
274+
ctx: Context<'context, S>,
275+
propagation: Option<SpanPropagation>,
239276
) -> Event<'static>
240277
where
241278
S: Subscriber + for<'a> LookupSpan<'a>,
@@ -245,7 +282,7 @@ where
245282
// information for this. However, it may contain a serialized error which we can parse to emit
246283
// an exception record.
247284
#[allow(unused_mut)]
248-
let (mut message, visitor) = extract_event_data_with_context(event, ctx.into());
285+
let (mut message, visitor) = extract_event_data_with_context(event, ctx, propagation);
249286
let FieldVisitor {
250287
mut exceptions,
251288
mut json_values,

sentry-tracing/src/layer.rs

+21-14
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tracing_subscriber::layer::{Context, Layer};
1111
use tracing_subscriber::registry::LookupSpan;
1212

1313
use crate::converters::*;
14-
use crate::TAGS_PREFIX;
14+
use crate::{SpanPropagation, TAGS_PREFIX};
1515

1616
/// The action that Sentry should perform for a [`Metadata`]
1717
#[derive(Debug, Clone, Copy)]
@@ -68,7 +68,7 @@ pub struct SentryLayer<S> {
6868

6969
span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
7070

71-
with_span_attributes: bool,
71+
span_propagation: Option<SpanPropagation>,
7272
}
7373

7474
impl<S> SentryLayer<S> {
@@ -121,8 +121,14 @@ impl<S> SentryLayer<S> {
121121
/// the [traces_sample_rate][sentry_core::ClientOptions::traces_sample_rate] to `1.0`
122122
/// while configuring your sentry client.
123123
#[must_use]
124-
pub fn enable_span_attributes(mut self) -> Self {
125-
self.with_span_attributes = true;
124+
pub fn enable_span_attributes(self) -> Self {
125+
self.enable_span_propagation(SpanPropagation::Attributes)
126+
}
127+
128+
#[must_use]
129+
/// Configures span propagation for events' creation
130+
pub fn enable_span_propagation(mut self, propagation: SpanPropagation) -> Self {
131+
self.span_propagation = Some(propagation);
126132
self
127133
}
128134
}
@@ -138,7 +144,7 @@ where
138144

139145
span_filter: Box::new(default_span_filter),
140146

141-
with_span_attributes: false,
147+
span_propagation: None,
142148
}
143149
}
144150
}
@@ -207,16 +213,17 @@ where
207213
fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
208214
let item = match &self.event_mapper {
209215
Some(mapper) => mapper(event, ctx),
210-
None => {
211-
let span_ctx = self.with_span_attributes.then_some(ctx);
212-
match (self.event_filter)(event.metadata()) {
213-
EventFilter::Ignore => EventMapping::Ignore,
214-
EventFilter::Breadcrumb => {
215-
EventMapping::Breadcrumb(breadcrumb_from_event(event, span_ctx))
216-
}
217-
EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)),
216+
None => match (self.event_filter)(event.metadata()) {
217+
EventFilter::Ignore => EventMapping::Ignore,
218+
EventFilter::Breadcrumb => EventMapping::Breadcrumb(breadcrumb_from_event(
219+
event,
220+
ctx,
221+
self.span_propagation,
222+
)),
223+
EventFilter::Event => {
224+
EventMapping::Event(event_from_event(event, ctx, self.span_propagation))
218225
}
219-
}
226+
},
220227
};
221228

222229
match item {

sentry-tracing/src/lib.rs

+29
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,32 @@ pub use converters::*;
147147
pub use layer::*;
148148

149149
const TAGS_PREFIX: &str = "tags.";
150+
151+
#[derive(Debug, Clone, Copy)]
152+
/// Controls propagation of span data when creating event
153+
///
154+
/// Note that the root span is considered a [transaction][sentry_core::protocol::Transaction]
155+
/// so its context will only be grabbed only if you set the transaction to be sampled.
156+
/// The most straightforward way to do this is to set
157+
/// the [traces_sample_rate][sentry_core::ClientOptions::traces_sample_rate] to `1.0`
158+
/// while configuring your sentry client.
159+
pub enum SpanPropagation {
160+
/// Collects all attributes prefixed with span name
161+
Attributes,
162+
/// Accumulates tags from within attributes as event tags, without overriding existing tags
163+
Tags,
164+
/// Collects both tags and attributes
165+
All,
166+
}
167+
168+
impl SpanPropagation {
169+
#[inline(always)]
170+
pub(crate) const fn is_tags_enabled(&self) -> bool {
171+
matches!(self, Self::Tags | Self::All)
172+
}
173+
174+
#[inline(always)]
175+
pub(crate) const fn is_attrs_enabled(&self) -> bool {
176+
matches!(self, Self::Attributes | Self::All)
177+
}
178+
}

sentry-tracing/tests/shared.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use sentry::{ClientOptions, Hub};
22
use sentry_core::test::TestTransport;
3+
use sentry_tracing::SpanPropagation;
34

45
use std::sync::Arc;
56

@@ -17,7 +18,7 @@ pub fn init_sentry(traces_sample_rate: f32) -> Arc<TestTransport> {
1718
Hub::current().bind_client(Some(Arc::new(options.into())));
1819

1920
let _ = tracing_subscriber::registry()
20-
.with(sentry_tracing::layer().enable_span_attributes())
21+
.with(sentry_tracing::layer().enable_span_propagation(SpanPropagation::All))
2122
.try_init();
2223

2324
transport

sentry-tracing/tests/smoke.rs

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ fn should_instrument_function_with_event() {
1919
unexpected => panic!("Expected event, but got {:#?}", unexpected),
2020
};
2121

22+
//Event must be get tag attached
23+
let event_tag = event
24+
.tags
25+
.get("tag")
26+
.expect("event should be associated with span's tag");
27+
assert_eq!(event_tag, "key");
28+
2229
//Validate transaction is created
2330
let trace = match event.contexts.get("trace").expect("to get 'trace' context") {
2431
sentry::protocol::Context::Trace(trace) => trace,

0 commit comments

Comments
 (0)