Skip to content
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

Add option to enable propagation of parent span tags #746

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
57 changes: 39 additions & 18 deletions sentry-tracing/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;

use super::layer::SentrySpanData;
use crate::TAGS_PREFIX;
use crate::{SpanPropagation, TAGS_PREFIX};

/// Converts a [`tracing_core::Level`] to a Sentry [`Level`]
fn convert_tracing_level(level: &tracing_core::Level) -> Level {
Expand Down Expand Up @@ -53,40 +53,34 @@ fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisi

fn extract_event_data_with_context<S>(
event: &tracing_core::Event,
ctx: Option<Context<S>>,
ctx: Option<(SpanPropagation, Context<S>)>,
) -> (Option<String>, FieldVisitor)
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let (message, mut visitor) = extract_event_data(event);

// Add the context fields of every parent span.
let current_span = ctx.as_ref().and_then(|ctx| {
let current_span = ctx.as_ref().and_then(|(propagation, ctx)| {
event
.parent()
.and_then(|id| ctx.span(id))
.or_else(|| ctx.lookup_current())
.and_then(|id| ctx.span(id).map(|span| (*propagation, span)))
.or_else(|| ctx.lookup_current().map(|span| (*propagation, span)))
});
if let Some(span) = current_span {
if let Some((propagation, span)) = current_span {
for span in span.scope() {
let name = span.name();
let ext = span.extensions();
if let Some(span_data) = ext.get::<SentrySpanData>() {
match &span_data.sentry_span {
TransactionOrSpan::Span(span) => {
for (key, value) in span.data().iter() {
if key != "message" {
let key = format!("{}:{}", name, key);
visitor.json_values.insert(key, value.clone());
}
visitor.propagate_span_attr(key, value, propagation, name);
}
}
TransactionOrSpan::Transaction(transaction) => {
for (key, value) in transaction.data().iter() {
if key != "message" {
let key = format!("{}:{}", name, key);
visitor.json_values.insert(key, value.clone());
}
visitor.propagate_span_attr(key, value, propagation, name);
}
}
}
Expand All @@ -105,6 +99,26 @@ pub(crate) struct FieldVisitor {
}

impl FieldVisitor {
fn propagate_span_attr(
&mut self,
key: &str,
value: &Value,
span_propagation: SpanPropagation,
span_name: &str,
) {
if key != "message" {
if span_propagation.is_tags_enabled() && key.starts_with(TAGS_PREFIX) {
//Propagate tags as it is, it will be extracted later on
if !self.json_values.contains_key(key) {
self.json_values.insert(key.to_owned(), value.clone());
}
} else if span_propagation.is_attrs_enabled() {
let key = format!("{}:{}", span_name, key);
self.json_values.insert(key, value.clone());
}
}
}

fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
self.json_values
.insert(field.name().to_owned(), value.into());
Expand Down Expand Up @@ -143,12 +157,19 @@ 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<(SpanPropagation, Context<'context, S>)>>,
) -> Breadcrumb
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let (message, visitor) = extract_event_data_with_context(event, ctx.into());
let ctx = match ctx.into() {
Some((propagation, ctx)) if propagation.is_attrs_enabled() => {
Some((SpanPropagation::Attributes, ctx))
}
//Breadcrumb has no tags, so propagate only attributes
_ => None,
};
let (message, visitor) = extract_event_data_with_context(event, ctx);
Breadcrumb {
category: Some(event.metadata().target().to_owned()),
ty: "log".into(),
Expand Down Expand Up @@ -219,7 +240,7 @@ fn contexts_from_event(
/// Creates an [`Event`] 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<(SpanPropagation, Context<'context, S>)>>,
) -> Event<'static>
where
S: Subscriber + for<'a> LookupSpan<'a>,
Expand All @@ -239,7 +260,7 @@ where
/// Creates an exception [`Event`] from a given [`tracing_core::Event`]
pub fn exception_from_event<'context, S>(
event: &tracing_core::Event,
ctx: impl Into<Option<Context<'context, S>>>,
ctx: impl Into<Option<(SpanPropagation, Context<'context, S>)>>,
) -> Event<'static>
where
S: Subscriber + for<'a> LookupSpan<'a>,
Expand Down
18 changes: 12 additions & 6 deletions sentry-tracing/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::registry::LookupSpan;

use crate::converters::*;
use crate::TAGS_PREFIX;
use crate::{SpanPropagation, TAGS_PREFIX};

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

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

with_span_attributes: bool,
span_propagation: Option<SpanPropagation>,
}

impl<S> SentryLayer<S> {
Expand Down Expand Up @@ -123,8 +123,14 @@ impl<S> SentryLayer<S> {
/// the [traces_sample_rate][sentry_core::ClientOptions::traces_sample_rate] to `1.0`
/// while configuring your sentry client.
#[must_use]
pub fn enable_span_attributes(mut self) -> Self {
self.with_span_attributes = true;
pub fn enable_span_attributes(self) -> Self {
self.enable_span_propagation(SpanPropagation::Attributes)
}

#[must_use]
/// Configures span propagation for events' creation
pub fn enable_span_propagation(mut self, propagation: SpanPropagation) -> Self {
self.span_propagation = Some(propagation);
self
}
}
Expand All @@ -140,7 +146,7 @@ where

span_filter: Box::new(default_span_filter),

with_span_attributes: false,
span_propagation: None,
}
}
}
Expand Down Expand Up @@ -210,7 +216,7 @@ where
let item = match &self.event_mapper {
Some(mapper) => mapper(event, ctx),
None => {
let span_ctx = self.with_span_attributes.then_some(ctx);
let span_ctx = self.span_propagation.map(|propagation| (propagation, ctx));
match (self.event_filter)(event.metadata()) {
EventFilter::Ignore => EventMapping::Ignore,
EventFilter::Breadcrumb => {
Expand Down
29 changes: 29 additions & 0 deletions sentry-tracing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,32 @@ pub use converters::*;
pub use layer::*;

const TAGS_PREFIX: &str = "tags.";

#[derive(Debug, Clone, Copy)]
/// Controls propagation of span data when creating event
///
/// Note that the root span is considered a [transaction][sentry_core::protocol::Transaction]
/// so its context will only be grabbed only if you set the transaction to be sampled.
/// The most straightforward way to do this is to set
/// the [traces_sample_rate][sentry_core::ClientOptions::traces_sample_rate] to `1.0`
/// while configuring your sentry client.
pub enum SpanPropagation {
/// Collects all attributes prefixed with span name
Attributes,
/// Accumulates tags from within attributes as event tags, without overriding existing tags
Tags,
/// Collects both tags and attributes
All,
}

impl SpanPropagation {
#[inline(always)]
pub(crate) const fn is_tags_enabled(&self) -> bool {
matches!(self, Self::Tags | Self::All)
}

#[inline(always)]
pub(crate) const fn is_attrs_enabled(&self) -> bool {
matches!(self, Self::Attributes | Self::All)
}
}
Loading