diff --git a/CHANGELOG.md b/CHANGELOG.md index 8696d82c1e6..2e5cd251aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - 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)) +- 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)) ## 26.5.0 diff --git a/relay-cabi/src/processing.rs b/relay-cabi/src/processing.rs index 388780a8bc8..44d889cb992 100644 --- a/relay-cabi/src/processing.rs +++ b/relay-cabi/src/processing.rs @@ -275,7 +275,7 @@ pub unsafe extern "C" fn relay_store_normalizer_normalize_event( span_op_defaults: Default::default(), // only supported in relay performance_issues_spans: Default::default(), derive_trace_id: Default::default(), - dsc: None, + enriched_dsc: None, }; normalize_event(&mut event, &normalization_config); diff --git a/relay-event-normalization/src/eap/mod.rs b/relay-event-normalization/src/eap/mod.rs index d2405559eed..150bb185ef3 100644 --- a/relay-event-normalization/src/eap/mod.rs +++ b/relay-event-normalization/src/eap/mod.rs @@ -11,7 +11,6 @@ use relay_conventions::attributes::*; use relay_conventions::{AttributeInfo, 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; @@ -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; @@ -346,7 +345,7 @@ 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 @@ -354,9 +353,11 @@ pub fn normalize_user_geo( pub fn normalize_dsc( attributes: &mut Annotated, is_segment: &Annotated, - dsc: Option<&DynamicSamplingContext>, + enriched_dsc: Option<&EnrichedDsc>, ) { - let Some(dsc) = dsc else { return }; + let Some(enriched_dsc) = enriched_dsc else { + return; + }; let attributes = attributes.get_or_insert_with(Default::default); @@ -364,24 +365,32 @@ pub fn normalize_dsc( 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, enriched_dsc.dsc.trace_id.to_string()); - if let Some(transaction) = &dsc.transaction { + if let Some(transaction) = &enriched_dsc.dsc.transaction { attributes.insert(SENTRY__DSC__TRANSACTION, transaction.clone()); } + attributes.insert( + SENTRY__DSC__PROJECT_ID, + enriched_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, + enriched_dsc.dsc.public_key.to_string(), + ); + if let Some(release) = &enriched_dsc.dsc.release { attributes.insert(SENTRY__DSC__RELEASE, release.clone()); } - if let Some(environment) = &dsc.environment { + if let Some(environment) = &enriched_dsc.dsc.environment { attributes.insert(SENTRY__DSC__ENVIRONMENT, environment.clone()); } - if let Some(sample_rate) = dsc.sample_rate { + if let Some(sample_rate) = enriched_dsc.dsc.sample_rate { attributes.insert(SENTRY__DSC__SAMPLE_RATE, sample_rate); } - if let Some(sampled) = dsc.sampled { + if let Some(sampled) = enriched_dsc.dsc.sampled { attributes.insert(SENTRY__DSC__SAMPLED, sampled); } } @@ -730,7 +739,9 @@ pub fn write_legacy_attributes(attributes: &mut Annotated) { #[cfg(test)] mod tests { + use relay_base_schema::project::ProjectId; use relay_protocol::{Empty, SerializableAnnotated, assert_annotated_snapshot}; + use relay_sampling::DynamicSamplingContext; use super::*; @@ -759,10 +770,19 @@ 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::new(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" @@ -774,10 +794,19 @@ 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::new(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" @@ -793,10 +822,19 @@ 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::new(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" diff --git a/relay-event-normalization/src/event.rs b/relay-event-normalization/src/event.rs index 4fb966bbf16..ef5cfdef0e7 100644 --- a/relay-event-normalization/src/event.rs +++ b/relay-event-normalization/src/event.rs @@ -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; @@ -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, }; @@ -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>, - /// 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. @@ -165,7 +164,7 @@ pub struct NormalizationConfig<'a> { /// It is persisted into the event payload for correlation. pub replay_id: Option, - /// 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. @@ -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 enriched_dsc: Option<&'a EnrichedDsc<'a>>, } impl Default for NormalizationConfig<'_> { @@ -214,7 +213,7 @@ impl Default for NormalizationConfig<'_> { span_op_defaults: Default::default(), performance_issues_spans: Default::default(), derive_trace_id: Default::default(), - dsc: None, + enriched_dsc: None, } } } @@ -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_dsc_for_event_spans(event, config.enriched_dsc); span::normalize_app_start_spans(event); - span::normalize_dsc_for_event_spans(event, config.dsc); span::exclusive_time::compute_span_exclusive_time(event); } diff --git a/relay-event-normalization/src/normalize/mod.rs b/relay-event-normalization/src/normalize/mod.rs index 2ffc58cfe01..cbaf7f56ebd 100644 --- a/relay-event-normalization/src/normalize/mod.rs +++ b/relay-event-normalization/src/normalize/mod.rs @@ -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; @@ -364,6 +366,25 @@ pub struct ModelMetadataEntry { pub context_size: Option, } +/// [`DynamicSamplingContext`] plus additional attributes used for dsc span normalization. +#[derive(Debug, Clone)] +pub struct EnrichedDsc<'a> { + /// 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, +} + +impl<'a> EnrichedDsc<'a> { + /// Creates a new [`EnrichedDsc`]. + pub fn new(dsc: &'a DynamicSamplingContext, sampling_project_id: ProjectId) -> Self { + EnrichedDsc { + dsc, + sampling_project_id, + } + } +} + #[cfg(test)] mod tests { use chrono::{TimeZone, Utc}; diff --git a/relay-event-normalization/src/normalize/span/mod.rs b/relay-event-normalization/src/normalize/span/mod.rs index a291c7e40fc..c5c516cb92f 100644 --- a/relay-event-normalization/src/normalize/span/mod.rs +++ b/relay-event-normalization/src/normalize/span/mod.rs @@ -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; @@ -61,14 +62,14 @@ 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, enriched_dsc: Option<&EnrichedDsc>) { if let Some(ctx) = event.context_mut::() { - normalize_dsc_for_span_data(&mut ctx.data, dsc); + normalize_dsc_for_span_data(&mut ctx.data, enriched_dsc); } if let Some(spans) = event.spans.value_mut() { for span in spans { if let Some(span) = span.value_mut() { - normalize_dsc_for_span_data(&mut span.data, dsc); + normalize_dsc_for_span_data(&mut span.data, enriched_dsc); } } } @@ -79,9 +80,11 @@ pub fn normalize_dsc_for_event_spans(event: &mut Event, dsc: Option<&DynamicSamp /// 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, - dsc: Option<&DynamicSamplingContext>, + enriched_dsc: Option<&EnrichedDsc>, ) { - let Some(dsc) = dsc else { return }; + let Some(enriched_dsc) = enriched_dsc else { + return; + }; let data = span_data.get_or_insert_with(SpanData::default); if data.other.contains_key(SENTRY__DSC__TRACE_ID) { @@ -89,13 +92,17 @@ pub fn normalize_dsc_for_span_data( } data.other.insert( SENTRY__DSC__TRACE_ID.to_owned(), - Annotated::new(Value::String(dsc.trace_id.to_string())), + Annotated::new(Value::String(enriched_dsc.dsc.trace_id.to_string())), ); - if let Some(transaction) = &dsc.transaction { + if let Some(transaction) = &enriched_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(enriched_dsc.sampling_project_id.to_string())), + ); } diff --git a/relay-server/src/processing/legacy_spans/normalize.rs b/relay-server/src/processing/legacy_spans/normalize.rs index fba750c91a9..926babdf9b0 100644 --- a/relay-server/src/processing/legacy_spans/normalize.rs +++ b/relay-server/src/processing/legacy_spans/normalize.rs @@ -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, @@ -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)] @@ -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 enriched_dsc: Option<&'a EnrichedDsc<'a>>, } fn set_segment_attributes(span: &mut Annotated) { @@ -111,7 +111,7 @@ pub fn normalize( client_ip, geo_lookup, span_op_defaults, - dsc, + enriched_dsc, } = config; set_segment_attributes(annotated_span); @@ -211,7 +211,7 @@ pub fn normalize( normalize_performance_score(span, *performance_score); - span::normalize_dsc_for_span_data(&mut span.data, *dsc); + span::normalize_dsc_for_span_data(&mut span.data, *enriched_dsc); ai::enrich_ai_span(span, *ai_model_metadata); @@ -554,7 +554,7 @@ mod tests { client_ip: Some(IpAddr("2.125.160.216".to_owned())), geo_lookup: &GEO_LOOKUP, span_op_defaults: Default::default(), - dsc: None, + enriched_dsc: None, } } diff --git a/relay-server/src/processing/legacy_spans/process.rs b/relay-server/src/processing/legacy_spans/process.rs index 731fd492692..e80848dfafb 100644 --- a/relay-server/src/processing/legacy_spans/process.rs +++ b/relay-server/src/processing/legacy_spans/process.rs @@ -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; @@ -45,6 +47,11 @@ pub fn normalize( let aggregator_config = ctx.config.aggregator_config_for(MetricNamespace::Spans); let model_data = ctx.global_config.ai_model_metadata(); let dsc = spans.headers.dsc().cloned(); + let dsc = dsc.as_ref(); + let sampling_project_id = ctx.sampling_project_info.and_then(|p| p.project_id); + let enriched_dsc = dsc + .zip(sampling_project_id) + .map(|(d, s)| EnrichedDsc::new(d, s)); let norm = normalize::NormalizeSpanConfig { received_at: spans.received_at(), timestamp_range: aggregator_config.timestamp_range(), @@ -66,7 +73,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(), + enriched_dsc: enriched_dsc.as_ref(), }; spans.retain( diff --git a/relay-server/src/processing/spans/process.rs b/relay-server/src/processing/spans/process.rs index 4990a64e9db..09e708bd8ea 100644 --- a/relay-server/src/processing/spans/process.rs +++ b/relay-server/src/processing/spans/process.rs @@ -3,7 +3,7 @@ use std::time::Duration; use relay_dynamic_config::Feature; use relay_event_normalization::eap::ClientUserAgentInfo; -use relay_event_normalization::{GeoIpLookup, RequiredMode, SchemaProcessor, eap}; +use relay_event_normalization::{EnrichedDsc, GeoIpLookup, RequiredMode, SchemaProcessor, eap}; use relay_event_schema::processor::{ProcessingState, ValueType, process_value}; use relay_event_schema::protocol::{Span, SpanId, SpanV2}; use relay_protocol::Annotated; @@ -207,7 +207,11 @@ fn normalize_span( ); if let Some(span) = span.value_mut() { - let dsc = headers.dsc(); + let enriched_dsc = headers + .dsc() + .zip(ctx.sampling_project_info.and_then(|p| p.project_id)) + .map(|(d, s)| EnrichedDsc::new(d, s)); + let enriched_dsc = enriched_dsc.as_ref(); let duration = span_duration(span); let allowed_hosts = ctx.global_config.options.http_span_allowed_hosts.as_slice(); let model_metdata = ctx.global_config.ai_model_metadata(); @@ -233,7 +237,7 @@ fn normalize_span( } eap::normalize_user_agent(&mut span.attributes, client_ua_info); eap::normalize_user_geo(&mut span.attributes, |ip| geo_lookup.lookup(ip)); - eap::normalize_dsc(&mut span.attributes, &span.is_segment, dsc); + eap::normalize_dsc(&mut span.attributes, &span.is_segment, enriched_dsc); if ctx.is_processing() { eap::normalize_ai(&mut span.attributes, duration, model_metdata); } diff --git a/relay-server/src/processing/utils/event.rs b/relay-server/src/processing/utils/event.rs index bc605e8e79d..aff625c5df8 100644 --- a/relay-server/src/processing/utils/event.rs +++ b/relay-server/src/processing/utils/event.rs @@ -12,6 +12,7 @@ use relay_base_schema::project::ProjectId; use relay_config::Config; use relay_config::NormalizationLevel; use relay_dynamic_config::Feature; +use relay_event_normalization::EnrichedDsc; use relay_event_normalization::GeoIpLookup; use relay_event_normalization::{ ClockDriftProcessor, normalize_event as normalize_event_inner, validate_event, @@ -250,6 +251,11 @@ pub fn normalize( ); } + let enriched_dsc = headers + .dsc() + .zip(ctx.sampling_project_info.and_then(|p| p.project_id)) + .map(|(d, s)| EnrichedDsc::new(d, s)); + let normalization_config = NormalizationConfig { project_id: Some(project_id.value()), client: request_meta.client().map(str::to_owned), @@ -302,7 +308,7 @@ pub fn normalize( .project_info .has_feature(Feature::PerformanceIssuesSpans), derive_trace_id: project_info.has_feature(Feature::AddDefaultTraceID), - dsc: headers.dsc(), + enriched_dsc: enriched_dsc.as_ref(), }; metric!(timer(RelayTimers::EventProcessingNormalization), { diff --git a/tests/integration/test_ai.py b/tests/integration/test_ai.py index 67c44d1af6b..4aefca9e8d3 100644 --- a/tests/integration/test_ai.py +++ b/tests/integration/test_ai.py @@ -420,6 +420,7 @@ def test_ai_spans_example_transaction( "type": "string", "value": "generateText weather-chat", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -539,6 +540,7 @@ def test_ai_spans_example_transaction( "type": "string", "value": "generate_text gpt-4o", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -639,6 +641,7 @@ def test_ai_spans_example_transaction( "value": "POST " "https://api.openai.com/v1/responses", }, "sentry.domain": {"type": "string", "value": "*.openai.com"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -735,6 +738,7 @@ def test_ai_spans_example_transaction( "type": "string", "value": "execute_tool getWeather", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -798,6 +802,7 @@ def test_ai_spans_example_transaction( "value": "GET " "https://wttr.in/San%20Francisco", }, "sentry.domain": {"type": "string", "value": "wttr.in"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -887,6 +892,7 @@ def test_ai_spans_example_transaction( "type": "string", "value": "execute_tool getWeather", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -950,6 +956,7 @@ def test_ai_spans_example_transaction( "value": "GET https://wttr.in/London", }, "sentry.domain": {"type": "string", "value": "wttr.in"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -1069,6 +1076,7 @@ def test_ai_spans_example_transaction( "type": "string", "value": "generate_text gpt-4o", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -1166,6 +1174,7 @@ def test_ai_spans_example_transaction( "value": "POST " "https://api.openai.com/v1/responses", }, "sentry.domain": {"type": "string", "value": "*.openai.com"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", @@ -1244,6 +1253,7 @@ def test_ai_spans_example_transaction( "gen_ai.usage.output_tokens": {"type": "integer", "value": 65}, "gen_ai.usage.total_tokens": {"type": "double", "value": 310.0}, "sentry.description": {"type": "string", "value": "main"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a9351cd574f092f6acad48e250981f11", diff --git a/tests/integration/test_spans.py b/tests/integration/test_spans.py index 1de7f91cbca..797abe9ad74 100644 --- a/tests/integration/test_spans.py +++ b/tests/integration/test_spans.py @@ -156,6 +156,7 @@ def test_span_extraction( }, "sentry.is_remote": {"type": "boolean", "value": False}, "sentry.segment.id": {"type": "string", "value": "968cff94913ebb07"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a0fa8803753e40fd8124b21eeb2986b5", @@ -232,6 +233,7 @@ def test_span_extraction( "sentry.user.id": {"type": "string", "value": user_id}, "sentry.user.ip": {"type": "string", "value": "192.168.0.1"}, "sentry.user": {"type": "string", "value": f"id:{user_id}"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "a0fa8803753e40fd8124b21eeb2986b5", @@ -1269,6 +1271,7 @@ def test_spans_dsc_normalization( "data": { "sentry.dsc.trace_id": "a0fa8803753e40fd8124b21eeb2986b5", "sentry.dsc.transaction": "/transaction/already/exists", + "sentry.dsc.project_id": "41", }, }, ] @@ -1279,9 +1282,21 @@ def test_spans_dsc_normalization( def get_transaction(span_id: str): return spans[span_id]["attributes"]["sentry.dsc.transaction"]["value"] + def get_project_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.project_id"]["value"] + + def get_trace_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.trace_id"]["value"] + assert spans["968cff94913ebb07"]["is_segment"] is True assert spans["bbbbbbbbbbbbbbbb"]["is_segment"] is False assert spans["cccccccccccccccc"]["is_segment"] is False assert get_transaction("968cff94913ebb07") == "hi" assert get_transaction("bbbbbbbbbbbbbbbb") == "hi" assert get_transaction("cccccccccccccccc") == "/transaction/already/exists" + assert get_project_id("968cff94913ebb07") == "42" + assert get_project_id("bbbbbbbbbbbbbbbb") == "42" + assert get_project_id("cccccccccccccccc") == "41" + assert get_trace_id("968cff94913ebb07") == "a0fa8803753e40fd8124b21eeb2986b5" + assert get_trace_id("bbbbbbbbbbbbbbbb") == "a0fa8803753e40fd8124b21eeb2986b5" + assert get_trace_id("cccccccccccccccc") == "a0fa8803753e40fd8124b21eeb2986b5" diff --git a/tests/integration/test_spans_standalone.py b/tests/integration/test_spans_standalone.py index 76684ab3ac9..cba2bd074af 100644 --- a/tests/integration/test_spans_standalone.py +++ b/tests/integration/test_spans_standalone.py @@ -259,6 +259,7 @@ def test_lcp_span( "type": "string", "value": "/insights/projects/", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "d3d20f000885466b8c8f947c9b92b8d3", @@ -481,6 +482,7 @@ def test_cls_span( "type": "string", "value": "/insights/projects/", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "d3d20f000885466b8c8f947c9b92b8d3", @@ -685,6 +687,7 @@ def test_inp_span( "type": "string", "value": "/insights/projects/", }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "d3d20f000885466b8c8f947c9b92b8d3", @@ -813,6 +816,7 @@ def test_spans_standalone_dsc_normalization( "data": { "sentry.dsc.trace_id": "5b8efff798038103d269b633813fc60c", "sentry.dsc.transaction": "/transaction/already/exists", + "sentry.dsc.project_id": "41", }, }, trace_info={ @@ -828,9 +832,21 @@ def test_spans_standalone_dsc_normalization( def get_transaction(span_id: str): return spans[span_id]["attributes"]["sentry.dsc.transaction"]["value"] + def get_project_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.project_id"]["value"] + + def get_trace_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.trace_id"]["value"] + assert spans["aaaaaaaaaaaaaaaa"]["is_segment"] is True assert spans["bbbbbbbbbbbbbbbb"]["is_segment"] is False assert spans["cccccccccccccccc"]["is_segment"] is False assert get_transaction("aaaaaaaaaaaaaaaa") == "/my/fancy/endpoint" assert get_transaction("bbbbbbbbbbbbbbbb") == "/my/fancy/endpoint" assert get_transaction("cccccccccccccccc") == "/transaction/already/exists" + assert get_project_id("aaaaaaaaaaaaaaaa") == "42" + assert get_project_id("bbbbbbbbbbbbbbbb") == "42" + assert get_project_id("cccccccccccccccc") == "41" + assert get_trace_id("aaaaaaaaaaaaaaaa") == "5b8efff798038103d269b633813fc60c" + assert get_trace_id("bbbbbbbbbbbbbbbb") == "5b8efff798038103d269b633813fc60c" + assert get_trace_id("cccccccccccccccc") == "5b8efff798038103d269b633813fc60c" diff --git a/tests/integration/test_spansv2.py b/tests/integration/test_spansv2.py index c2e016e09a8..0a42c4bc080 100644 --- a/tests/integration/test_spansv2.py +++ b/tests/integration/test_spansv2.py @@ -130,6 +130,7 @@ def test_spansv2_basic( }, "sentry.dsc.release": {"type": "string", "value": "foo@1.0"}, "sentry.dsc.transaction": {"type": "string", "value": "/my/fancy/endpoint"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -238,7 +239,7 @@ def test_spansv2_trimming_basic( # This is sufficient for all builtin attributes not # to be trimmed. The span fields that aren't trimmed # also still count for the size limit. - "trimming": {"span": {"maxSize": 431}}, + "trimming": {"span": {"maxSize": 454}}, } ) @@ -322,6 +323,7 @@ def test_spansv2_trimming_basic( }, "sentry.dsc.release": {"type": "string", "value": "foo@1.0"}, "sentry.dsc.transaction": {"type": "string", "value": "/my/fancy/endpoint"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -334,7 +336,7 @@ def test_spansv2_trimming_basic( }, "_meta": { "attributes": { - "": {"len": 483}, + "": {"len": 506}, "custom.array.attribute": { "value": { "2": { @@ -1169,6 +1171,7 @@ def test_spanv2_with_string_pii_scrubbing( "trace_id": "5b8efff798038103d269b633813fc60c", "span_id": "eee19b7ec3c1b174", "attributes": { + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -1306,6 +1309,7 @@ def test_spanv2_meta_pii_scrubbing_complex_attribute(mini_sentry, relay): "type": "array", "value": ["normal", "[creditcard]", "other"], }, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -1453,6 +1457,7 @@ def test_spansv2_attribute_normalization( "value": "SELECT id FROM users WHERE id = 1 AND name = 'Test'", }, "sentry.domain": {"type": "string", "value": ",users,"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -1504,6 +1509,7 @@ def test_spansv2_attribute_normalization( "sentry.action": {"type": "string", "value": "GET"}, "server.address": {"type": "string", "value": "*.service.io"}, "sentry.domain": {"type": "string", "value": "*.service.io"}, + "sentry.dsc.project_id": {"type": "string", "value": "42"}, "sentry.dsc.trace_id": { "type": "string", "value": "5b8efff798038103d269b633813fc60c", @@ -1905,31 +1911,12 @@ def test_spansv2_dsc_normalization( "name": "child", "status": "ok", }, - { - "start_timestamp": ts.timestamp(), - "end_timestamp": ts.timestamp() + 0.5, - "trace_id": "5b8efff798038103d269b633813fc60c", - "span_id": "cccccccccccccccc", - "is_segment": True, - "name": "root", - "status": "ok", - "attributes": { - "sentry.dsc.trace_id": { - "type": "string", - "value": "5b8efff798038103d269b633813fc60c", - }, - "sentry.dsc.transaction": { - "type": "string", - "value": "/transaction/already/exists", - }, - }, - }, { "start_timestamp": ts.timestamp(), "end_timestamp": ts.timestamp() + 0.3, "trace_id": "5b8efff798038103d269b633813fc60c", - "span_id": "dddddddddddddddd", - "parent_span_id": "cccccccccccccccc", + "span_id": "cccccccccccccccc", + "parent_span_id": "aaaaaaaaaaaaaaaa", "is_segment": False, "name": "child", "status": "ok", @@ -1942,6 +1929,7 @@ def test_spansv2_dsc_normalization( "type": "string", "value": "/transaction/already/exists", }, + "sentry.dsc.project_id": {"type": "string", "value": "41"}, }, }, trace_info={ @@ -1953,21 +1941,25 @@ def test_spansv2_dsc_normalization( relay.send_envelope(project_id, envelope) + def get_transaction(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.transaction"]["value"] + + def get_project_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.project_id"]["value"] + + def get_trace_id(span_id: str): + return spans[span_id]["attributes"]["sentry.dsc.trace_id"]["value"] + spans = {s["span_id"]: s for s in spans_consumer.get_spans()} assert spans["aaaaaaaaaaaaaaaa"]["is_segment"] is True assert spans["bbbbbbbbbbbbbbbb"]["is_segment"] is False - assert spans["cccccccccccccccc"]["is_segment"] is True - assert spans["dddddddddddddddd"]["is_segment"] is False - for span_id in [ - "aaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbb", - "cccccccccccccccc", - "dddddddddddddddd", - ]: - expected = ( - "/my/fancy/endpoint" - if span_id in ["aaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbb"] - else "/transaction/already/exists" - ) - actual = spans[span_id]["attributes"]["sentry.dsc.transaction"]["value"] - assert actual == expected + assert spans["cccccccccccccccc"]["is_segment"] is False + assert get_transaction("aaaaaaaaaaaaaaaa") == "/my/fancy/endpoint" + assert get_transaction("bbbbbbbbbbbbbbbb") == "/my/fancy/endpoint" + assert get_transaction("cccccccccccccccc") == "/transaction/already/exists" + assert get_project_id("aaaaaaaaaaaaaaaa") == "42" + assert get_project_id("bbbbbbbbbbbbbbbb") == "42" + assert get_project_id("cccccccccccccccc") == "41" + assert get_trace_id("aaaaaaaaaaaaaaaa") == "5b8efff798038103d269b633813fc60c" + assert get_trace_id("bbbbbbbbbbbbbbbb") == "5b8efff798038103d269b633813fc60c" + assert get_trace_id("cccccccccccccccc") == "5b8efff798038103d269b633813fc60c"