Skip to content
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

- Apply timestamp validations to transaction spans. ([#6005](https://github.com/getsentry/relay/pull/6005), [#6013](https://github.com/getsentry/relay/pull/6013))
- Obtain PII values for `SpanData` fields from `sentry-conventions`. ([#5997](https://github.com/getsentry/relay/pull/5997))
- Add `sentry.dsc.transaction` and `sentry.dsc.trace_id` to all spans. ([#6001](https://github.com/getsentry/relay/pull/6001), [#6004](https://github.com/getsentry/relay/pull/6004), [#6008](https://github.com/getsentry/relay/pull/6008))
- Correctly handle attributes with placeholders during normalization. ([#6012](https://github.com/getsentry/relay/pull/6012))
- Add `sentry.dsc.transaction`, `sentry.dsc.trace_id`, and `sentry.dsc.project_id` to all spans. ([#6001](https://github.com/getsentry/relay/pull/6001), [#6004](https://github.com/getsentry/relay/pull/6004), [#6008](https://github.com/getsentry/relay/pull/6008), [#6011](https://github.com/getsentry/relay/pull/6011))

**Internal**:

Expand Down
77 changes: 59 additions & 18 deletions relay-event-normalization/src/eap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use relay_conventions::attributes::*;
use relay_conventions::{AttributeInfo, ReplacementName, WriteBehavior};
use relay_event_schema::protocol::{AttributeType, Attributes, BrowserContext, Geo};
use relay_protocol::{Annotated, ErrorKind, Meta, Remark, RemarkType, Value};
use relay_sampling::DynamicSamplingContext;
use relay_spans::derive_op_for_v2_span;

use crate::span::TABLE_NAME_REGEX;
Expand All @@ -20,7 +19,7 @@ use crate::span::tag_extraction::{
domain_from_scrubbed_http, domain_from_server_address, span_op_to_category,
sql_action_from_query, sql_tables_from_query,
};
use crate::{ClientHints, FromUserAgentInfo as _, RawUserAgentInfo};
use crate::{ClientHints, EnrichedDsc, FromUserAgentInfo as _, RawUserAgentInfo};

mod ai;
mod mobile;
Expand Down Expand Up @@ -346,42 +345,46 @@ pub fn normalize_user_geo(
attributes.insert_if_missing(USER__GEO__REGION, || geo.region);
}

/// Normalizes the [DSC](DynamicSamplingContext) into [`Attributes`].
/// Normalizes the dynamic sampling context into [`Attributes`].
///
/// If `is_segment` is set to `false`, the function will only add select attributes that are
/// necessary on every span - both segment and non-segment - for dynamic sampling to work. More
/// attributes are added when `is_segment` is set to `true`.
pub fn normalize_dsc(
attributes: &mut Annotated<Attributes>,
is_segment: &Annotated<bool>,
dsc: Option<&DynamicSamplingContext>,
dsc: Option<EnrichedDsc>,
) {
let Some(dsc) = dsc else { return };
let Some(dsc) = dsc else {
return;
};

let attributes = attributes.get_or_insert_with(Default::default);

// Check if DSC attributes are already set, the trace id is always required and must always be set.
if attributes.contains_key(SENTRY__DSC__TRACE_ID) {
return;
}
attributes.insert(SENTRY__DSC__TRACE_ID, dsc.trace_id.to_string());
attributes.insert(SENTRY__DSC__TRACE_ID, dsc.dsc.trace_id.to_string());

if let Some(transaction) = &dsc.transaction {
if let Some(transaction) = &dsc.dsc.transaction {
attributes.insert(SENTRY__DSC__TRANSACTION, transaction.clone());
}

attributes.insert(SENTRY__DSC__PROJECT_ID, dsc.sampling_project_id.to_string());

if is_segment.value().is_some_and(|is_segment| *is_segment) {
attributes.insert(SENTRY__DSC__PUBLIC_KEY, dsc.public_key.to_string());
if let Some(release) = &dsc.release {
attributes.insert(SENTRY__DSC__PUBLIC_KEY, dsc.dsc.public_key.to_string());
if let Some(release) = &dsc.dsc.release {
attributes.insert(SENTRY__DSC__RELEASE, release.clone());
}
if let Some(environment) = &dsc.environment {
if let Some(environment) = &dsc.dsc.environment {
attributes.insert(SENTRY__DSC__ENVIRONMENT, environment.clone());
}
if let Some(sample_rate) = dsc.sample_rate {
if let Some(sample_rate) = dsc.dsc.sample_rate {
attributes.insert(SENTRY__DSC__SAMPLE_RATE, sample_rate);
}
if let Some(sampled) = dsc.sampled {
if let Some(sampled) = dsc.dsc.sampled {
attributes.insert(SENTRY__DSC__SAMPLED, sampled);
}
}
Expand Down Expand Up @@ -778,7 +781,9 @@ pub fn write_legacy_attributes(attributes: &mut Annotated<Attributes>) {

#[cfg(test)]
mod tests {
use relay_base_schema::project::ProjectId;
use relay_protocol::{Empty, SerializableAnnotated, assert_annotated_snapshot};
use relay_sampling::DynamicSamplingContext;

use super::*;

Expand Down Expand Up @@ -807,10 +812,22 @@ mod tests {
#[test]
fn test_normalize_dsc_child_span_no_transaction() {
let mut attributes = Annotated::empty();
let dsc = mock_dsc(None);
normalize_dsc(&mut attributes, &Annotated::new(false), Some(&dsc));
let dsc = &mock_dsc(None);
let sampling_project_id = ProjectId::new(42);
normalize_dsc(
&mut attributes,
&Annotated::new(false),
Some(EnrichedDsc {
dsc,
sampling_project_id,
}),
);
assert_annotated_snapshot!(attributes, @r#"
{
"sentry.dsc.project_id": {
"type": "string",
"value": "42"
},
"sentry.dsc.trace_id": {
"type": "string",
"value": "67e5504410b1426f9247bb680e5fe0c8"
Expand All @@ -822,10 +839,22 @@ mod tests {
#[test]
fn test_normalize_dsc_child_span() {
let mut attributes = Annotated::empty();
let dsc = mock_dsc(Some("/some/endpoint"));
normalize_dsc(&mut attributes, &Annotated::new(false), Some(&dsc));
let dsc = &mock_dsc(Some("/some/endpoint"));
let sampling_project_id = ProjectId::new(42);
normalize_dsc(
&mut attributes,
&Annotated::new(false),
Some(EnrichedDsc {
dsc,
sampling_project_id,
}),
);
assert_annotated_snapshot!(attributes, @r#"
{
"sentry.dsc.project_id": {
"type": "string",
"value": "42"
},
"sentry.dsc.trace_id": {
"type": "string",
"value": "67e5504410b1426f9247bb680e5fe0c8"
Expand All @@ -841,10 +870,22 @@ mod tests {
#[test]
fn test_normalize_dsc_segment() {
let mut attributes = Annotated::empty();
let dsc = mock_dsc(Some("/some/endpoint"));
normalize_dsc(&mut attributes, &Annotated::new(true), Some(&dsc));
let dsc = &mock_dsc(Some("/some/endpoint"));
let sampling_project_id = ProjectId::new(42);
normalize_dsc(
&mut attributes,
&Annotated::new(true),
Some(EnrichedDsc {
dsc,
sampling_project_id,
}),
);
assert_annotated_snapshot!(attributes, @r#"
{
"sentry.dsc.project_id": {
"type": "string",
"value": "42"
},
"sentry.dsc.public_key": {
"type": "string",
"value": "12345678901234567890123456789012"
Expand Down
15 changes: 7 additions & 8 deletions relay-event-normalization/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ use relay_protocol::{
Annotated, Empty, Error, ErrorKind, FiniteF64, FromValue, Getter, Meta, Object, Remark,
RemarkType, TryFromFloatError, Value,
};
use relay_sampling::DynamicSamplingContext;
use smallvec::SmallVec;
use uuid::Uuid;

Expand All @@ -42,8 +41,8 @@ use crate::span::ai::enrich_ai_event_data;
use crate::span::tag_extraction::{extract_segment_name_from_event, extract_span_tags_from_event};
use crate::utils::{self, MAX_DURATION_MOBILE_MS, get_event_user_tag};
use crate::{
BorrowedSpanOpDefaults, BreakdownsConfig, CombinedMeasurementsConfig, GeoIpLookup, MaxChars,
ModelMetadata, PerformanceScoreConfig, RawUserAgentInfo, SpanDescriptionRule,
BorrowedSpanOpDefaults, BreakdownsConfig, CombinedMeasurementsConfig, EnrichedDsc, GeoIpLookup,
MaxChars, ModelMetadata, PerformanceScoreConfig, RawUserAgentInfo, SpanDescriptionRule,
TransactionNameConfig, breakdowns, event_error, legacy, mechanism, remove_other, schema, span,
stacktrace, transactions, trimming, user_agent,
};
Expand Down Expand Up @@ -141,7 +140,7 @@ pub struct NormalizationConfig<'a> {
/// This is similar to `transaction_name_config`, but applies to span descriptions.
pub span_description_rules: Option<&'a Vec<SpanDescriptionRule>>,

/// Configuration for generating performance score measurements for web vitals
/// Configuration for generating performance score measurements for web vitals.
pub performance_score: Option<&'a PerformanceScoreConfig>,

/// Metadata for AI models including costs and context size.
Expand All @@ -165,7 +164,7 @@ pub struct NormalizationConfig<'a> {
/// It is persisted into the event payload for correlation.
pub replay_id: Option<Uuid>,

/// Controls list of hosts to be excluded from scrubbing
/// Controls list of hosts to be excluded from scrubbing.
pub span_allowed_hosts: &'a [String],

/// Rules to infer `span.op` from other span fields.
Expand All @@ -177,8 +176,8 @@ pub struct NormalizationConfig<'a> {
/// Should add a random trace ID to events that lack one.
pub derive_trace_id: bool,

/// Dynamic sampling context
pub dsc: Option<&'a DynamicSamplingContext>,
/// Dynamic sampling context and additional attributes used for dsc span normalization.
pub dsc: Option<EnrichedDsc<'a>>,
}

impl Default for NormalizationConfig<'_> {
Expand Down Expand Up @@ -346,8 +345,8 @@ fn normalize(event: &mut Event, meta: &mut Meta, config: &NormalizationConfig) {
normalize_contexts(&mut event.contexts, event_id, config);

if config.normalize_spans && event.ty.value() == Some(&EventType::Transaction) {
span::normalize_app_start_spans(event);
span::normalize_dsc_for_event_spans(event, config.dsc);
span::normalize_app_start_spans(event);
span::exclusive_time::compute_span_exclusive_time(event);
}

Expand Down
11 changes: 11 additions & 0 deletions relay-event-normalization/src/normalize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::{collections::HashMap, sync::LazyLock};

use regex::Regex;
use relay_base_schema::metrics::MetricUnit;
use relay_base_schema::project::ProjectId;
use relay_event_schema::protocol::VALID_PLATFORMS;
use relay_pattern::Pattern;
use relay_protocol::{FiniteF64, RuleCondition};
use relay_sampling::DynamicSamplingContext;
use serde::{Deserialize, Serialize};

pub mod breakdowns;
Expand Down Expand Up @@ -364,6 +366,15 @@ pub struct ModelMetadataEntry {
pub context_size: Option<u64>,
}

/// [`DynamicSamplingContext`] plus additional attributes used for dsc span normalization.
#[derive(Debug, Clone, Copy)]
pub struct EnrichedDsc<'a> {
Comment thread
elramen marked this conversation as resolved.
/// Dynamic sampling context containing the trace id and root transaction that started the trace.
pub dsc: &'a DynamicSamplingContext,
/// ID of the project where the trace originated.
pub sampling_project_id: ProjectId,
Comment thread
elramen marked this conversation as resolved.
}

#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
Expand Down
24 changes: 14 additions & 10 deletions relay-event-normalization/src/normalize/span/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! Span normalization logic.

use regex::Regex;
use relay_conventions::attributes::{SENTRY__DSC__TRACE_ID, SENTRY__DSC__TRANSACTION};
use relay_conventions::attributes::*;
use relay_event_schema::protocol::{Event, SpanData, TraceContext};
use relay_protocol::{Annotated, Value};
use relay_sampling::DynamicSamplingContext;
use std::sync::LazyLock;

use crate::EnrichedDsc;

pub mod ai;
pub mod country_subregion;
pub mod description;
Expand Down Expand Up @@ -61,7 +62,7 @@ pub fn normalize_app_start_spans(event: &mut Event) {
///
/// If `sentry.dsc.trace_id` is already present in a span's `data`, the function does nothing for
/// that span.
pub fn normalize_dsc_for_event_spans(event: &mut Event, dsc: Option<&DynamicSamplingContext>) {
pub fn normalize_dsc_for_event_spans(event: &mut Event, dsc: Option<EnrichedDsc>) {
if let Some(ctx) = event.context_mut::<TraceContext>() {
normalize_dsc_for_span_data(&mut ctx.data, dsc);
}
Expand All @@ -77,25 +78,28 @@ pub fn normalize_dsc_for_event_spans(event: &mut Event, dsc: Option<&DynamicSamp
/// Writes DSC attributes needed for dynamic sampling into `span_data`.
///
/// If `sentry.dsc.trace_id` is already present in `span_data`, the function does nothing.
pub fn normalize_dsc_for_span_data(
span_data: &mut Annotated<SpanData>,
dsc: Option<&DynamicSamplingContext>,
) {
let Some(dsc) = dsc else { return };
pub fn normalize_dsc_for_span_data(span_data: &mut Annotated<SpanData>, dsc: Option<EnrichedDsc>) {
let Some(dsc) = dsc else {
return;
};

let data = span_data.get_or_insert_with(SpanData::default);
if data.other.contains_key(SENTRY__DSC__TRACE_ID) {
return;
Comment thread
elramen marked this conversation as resolved.
}
data.other.insert(
SENTRY__DSC__TRACE_ID.to_owned(),
Annotated::new(Value::String(dsc.trace_id.to_string())),
Annotated::new(Value::String(dsc.dsc.trace_id.to_string())),
);

if let Some(transaction) = &dsc.transaction {
if let Some(transaction) = &dsc.dsc.transaction {
data.other.insert(
SENTRY__DSC__TRANSACTION.to_owned(),
Annotated::new(Value::String(transaction.clone())),
);
}
data.other.insert(
SENTRY__DSC__PROJECT_ID.to_owned(),
Annotated::new(Value::String(dsc.sampling_project_id.to_string())),
);
}
6 changes: 3 additions & 3 deletions relay-server/src/processing/legacy_spans/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::services::processor::ProcessingError;
use chrono::{DateTime, Utc};
use relay_event_normalization::EnrichedDsc;
use relay_event_normalization::span::{self, ai};
use relay_event_normalization::{
BorrowedSpanOpDefaults, ClientHints, CombinedMeasurementsConfig, FromUserAgentInfo,
Expand All @@ -14,7 +15,6 @@ use relay_event_schema::processor::{ProcessingState, process_value};
use relay_event_schema::protocol::{BrowserContext, EventId, IpAddr, Span, SpanData};
use relay_metrics::UnixTimestamp;
use relay_protocol::{Annotated, Empty, Value};
use relay_sampling::DynamicSamplingContext;

/// Config needed to normalize a standalone span.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -56,8 +56,8 @@ pub struct NormalizeSpanConfig<'a> {
/// An initialized GeoIP lookup.
pub geo_lookup: &'a GeoIpLookup,
pub span_op_defaults: BorrowedSpanOpDefaults<'a>,
/// Dynamic sampling context from the envelope headers.
pub dsc: Option<&'a DynamicSamplingContext>,
/// Dynamic sampling context plus additional attributes used for dsc span normalization.
pub dsc: Option<EnrichedDsc<'a>>,
}

fn set_segment_attributes(span: &mut Annotated<Span>) {
Expand Down
17 changes: 15 additions & 2 deletions relay-server/src/processing/legacy_spans/process.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use relay_event_normalization::{CombinedMeasurementsConfig, GeoIpLookup, MeasurementsConfig};
use relay_event_normalization::{
CombinedMeasurementsConfig, EnrichedDsc, GeoIpLookup, MeasurementsConfig,
};
use relay_event_schema::processor::{ProcessingAction, ProcessingState, process_value};
use relay_event_schema::protocol::Span;
use relay_metrics::MetricNamespace;
Expand Down Expand Up @@ -44,7 +46,18 @@ pub fn normalize(
) {
let aggregator_config = ctx.config.aggregator_config_for(MetricNamespace::Spans);
let model_data = ctx.global_config.ai_model_metadata();
let sampling_project_id = ctx
.sampling_project_info
.and_then(|p| p.project_id)
.or(ctx.project_info.project_id);
let dsc = spans.headers.dsc().cloned();
let dsc = dsc
.as_ref()
.zip(sampling_project_id)
.map(|(dsc, sampling_project_id)| EnrichedDsc {
dsc,
sampling_project_id,
});
let norm = normalize::NormalizeSpanConfig {
received_at: spans.received_at(),
timestamp_range: aggregator_config.timestamp_range(),
Expand All @@ -66,7 +79,7 @@ pub fn normalize(
client_ip: spans.headers.meta().client_addr().map(Into::into),
geo_lookup,
span_op_defaults: ctx.global_config.span_op_defaults.borrow(),
dsc: dsc.as_ref(),
dsc,
};

spans.retain(
Expand Down
Loading
Loading