diff --git a/CHANGELOG.md b/CHANGELOG.md index d686e75a1b..136dc7b689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Add web vitals support for mobile browsers. ([#3762](https://github.com/getsentry/relay/pull/3762)) - Ingest profiler_id in the profile context and in spans. ([#3714](https://github.com/getsentry/relay/pull/3714), [#3784](https://github.com/getsentry/relay/pull/3784)) - Support extrapolation of metrics extracted from sampled data, as long as the sample rate is set in the DynamicSamplingContext. ([#3753](https://github.com/getsentry/relay/pull/3753)) +- Extract thread ID and name in spans. ([#3771](https://github.com/getsentry/relay/pull/3771)) ## 24.6.0 diff --git a/relay-event-normalization/src/normalize/span/tag_extraction.rs b/relay-event-normalization/src/normalize/span/tag_extraction.rs index 526fd1e0cc..98933958bd 100644 --- a/relay-event-normalization/src/normalize/span/tag_extraction.rs +++ b/relay-event-normalization/src/normalize/span/tag_extraction.rs @@ -82,6 +82,8 @@ pub enum SpanTagKey { TraceStatus, MessagingDestinationName, MessagingMessageId, + ThreadName, + ThreadId, ProfilerId, } @@ -134,7 +136,8 @@ impl SpanTagKey { SpanTagKey::TraceStatus => "trace.status", SpanTagKey::MessagingDestinationName => "messaging.destination.name", SpanTagKey::MessagingMessageId => "messaging.message.id", - + SpanTagKey::ThreadName => "thread.name", + SpanTagKey::ThreadId => "thread.id", SpanTagKey::ProfilerId => "profiler_id", } } @@ -343,6 +346,19 @@ fn extract_shared_tags(event: &Event) -> BTreeMap { event.platform.as_str().unwrap_or("other").into(), ); + if let Some(data) = event + .context::() + .and_then(|trace_context| trace_context.data.value()) + { + if let Some(thread_id) = data.thread_id.value() { + tags.insert(SpanTagKey::ThreadId, thread_id.to_string()); + } + + if let Some(thread_name) = data.thread_name.value() { + tags.insert(SpanTagKey::ThreadName, thread_name.to_string()); + } + } + tags } @@ -763,6 +779,16 @@ pub fn extract_tags( span_tags.insert(SpanTagKey::BrowserName, browser_name.clone()); } + if let Some(data) = span.data.value() { + if let Some(thread_id) = data.thread_id.value() { + span_tags.insert(SpanTagKey::ThreadId, thread_id.to_string()); + } + + if let Some(thread_name) = data.thread_name.value().and_then(|name| name.as_str()) { + span_tags.insert(SpanTagKey::ThreadName, thread_name.into()); + } + } + span_tags } @@ -2486,4 +2512,100 @@ LIMIT 1 "admin@sentry.io" ); } + + #[test] + fn extract_thread_id_name_from_span_data_into_sentry_tags() { + let json = r#" + { + "type": "transaction", + "platform": "javascript", + "start_timestamp": "2021-04-26T07:59:01+0100", + "timestamp": "2021-04-26T08:00:00+0100", + "transaction": "foo", + "contexts": { + "trace": { + "trace_id": "ff62a8b040f340bda5d830223def1d81", + "span_id": "bd429c44b67a3eb4" + } + }, + "spans": [ + { + "op": "before_first_display", + "span_id": "bd429c44b67a3eb1", + "start_timestamp": 1597976300.0000000, + "timestamp": 1597976302.0000000, + "trace_id": "ff62a8b040f340bda5d830223def1d81", + "data": { + "thread.name": "main", + "thread.id": 42 + } + } + ] + } + "#; + + let mut event = Annotated::::from_json(json).unwrap(); + + normalize_event( + &mut event, + &NormalizationConfig { + enrich_spans: true, + ..Default::default() + }, + ); + + let spans = get_value!(event.spans!); + let span = &spans[0]; + + assert_eq!(get_value!(span.sentry_tags["thread.id"]!), "42",); + assert_eq!(get_value!(span.sentry_tags["thread.name"]!), "main",); + } + + #[test] + fn extract_thread_id_name_from_trace_context_into_sentry_tags() { + let json = r#" + { + "type": "transaction", + "platform": "python", + "start_timestamp": "2021-04-26T07:59:01+0100", + "timestamp": "2021-04-26T08:00:00+0100", + "transaction": "foo", + "contexts": { + "trace": { + "op": "queue.process", + "status": "ok", + "data": { + "thread.name": "main", + "thread.id": 42 + } + } + }, + "spans": [ + { + "op": "before_first_display", + "span_id": "bd429c44b67a3eb1", + "start_timestamp": 1597976300.0000000, + "timestamp": 1597976302.0000000, + "trace_id": "ff62a8b040f340bda5d830223def1d81" + } + ] + } + "#; + + let mut event = Annotated::::from_json(json).unwrap(); + + normalize_event( + &mut event, + &NormalizationConfig { + enrich_spans: true, + ..Default::default() + }, + ); + + let spans = get_value!(event.spans!); + let span = &spans[0]; + + assert_eq!(get_value!(span.sentry_tags["thread.id"]!), "42",); + assert_eq!(get_value!(span.sentry_tags["thread.name"]!), "main",); + } } diff --git a/relay-event-schema/src/protocol/contexts/trace.rs b/relay-event-schema/src/protocol/contexts/trace.rs index 77cde10527..377d1e1519 100644 --- a/relay-event-schema/src/protocol/contexts/trace.rs +++ b/relay-event-schema/src/protocol/contexts/trace.rs @@ -6,7 +6,7 @@ use relay_jsonschema_derive::JsonSchema; use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Object, Value}; use crate::processor::ProcessValue; -use crate::protocol::{OperationType, OriginType, SpanStatus}; +use crate::protocol::{OperationType, OriginType, SpanStatus, ThreadId}; /// A 32-character hex string as described in the W3C trace context spec. #[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)] @@ -191,6 +191,14 @@ pub struct Data { #[metastructure(field = "messaging.message.body.size")] pub messaging_message_body_size: Annotated, + /// The ID of the thread from which the transaction originated from + #[metastructure(field = "thread.id")] + pub thread_id: Annotated, + + /// The name of the thread from which the transaction originated from + #[metastructure(field = "thread.name")] + pub thread_name: Annotated, + /// Additional arbitrary fields for forwards compatibility. #[metastructure( additional_properties, diff --git a/relay-event-schema/src/protocol/span.rs b/relay-event-schema/src/protocol/span.rs index 84f97e902c..3207babd69 100644 --- a/relay-event-schema/src/protocol/span.rs +++ b/relay-event-schema/src/protocol/span.rs @@ -7,7 +7,7 @@ use relay_protocol::{Annotated, Empty, FromValue, Getter, IntoValue, Object, Val use crate::processor::ProcessValue; use crate::protocol::{ EventId, JsonLenientString, LenientString, Measurements, MetricsSummary, OperationType, - OriginType, SpanId, SpanStatus, Timestamp, TraceId, + OriginType, SpanId, SpanStatus, ThreadId, Timestamp, TraceId, }; #[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)] @@ -314,6 +314,10 @@ pub struct SpanData { #[metastructure(field = "thread.name")] pub thread_name: Annotated, + /// ID of thread from where the span originated. + #[metastructure(field = "thread.id")] + pub thread_id: Annotated, + /// Name of the segment that this span belongs to (see `segment_id`). /// /// This corresponds to the transaction name in the transaction-based model. @@ -673,6 +677,7 @@ mod tests { ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, diff --git a/relay-event-schema/src/protocol/span/convert.rs b/relay-event-schema/src/protocol/span/convert.rs index d283c2acd6..7907a0a5ba 100644 --- a/relay-event-schema/src/protocol/span/convert.rs +++ b/relay-event-schema/src/protocol/span/convert.rs @@ -300,6 +300,7 @@ mod tests { ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: "my 1st transaction", ui_component_name: ~, url_scheme: ~, diff --git a/relay-event-schema/src/protocol/thread.rs b/relay-event-schema/src/protocol/thread.rs index 81a46fb018..2c1ace18dd 100644 --- a/relay-event-schema/src/protocol/thread.rs +++ b/relay-event-schema/src/protocol/thread.rs @@ -1,3 +1,5 @@ +use std::fmt; + #[cfg(feature = "jsonschema")] use relay_jsonschema_derive::JsonSchema; use relay_protocol::{ @@ -71,6 +73,15 @@ impl Empty for ThreadId { } } +impl fmt::Display for ThreadId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ThreadId::Int(id) => write!(f, "{}", id), + ThreadId::String(id) => write!(f, "{}", id), + } + } +} + /// Possible lock types responsible for a thread's blocked state #[derive(Debug, Copy, Clone, Eq, PartialEq, ProcessValue, Empty)] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] diff --git a/relay-server/src/metrics_extraction/snapshots/relay_server__metrics_extraction__event__tests__extract_span_metrics_mobile.snap b/relay-server/src/metrics_extraction/snapshots/relay_server__metrics_extraction__event__tests__extract_span_metrics_mobile.snap index bcac833962..e08223a419 100644 --- a/relay-server/src/metrics_extraction/snapshots/relay_server__metrics_extraction__event__tests__extract_span_metrics_mobile.snap +++ b/relay-server/src/metrics_extraction/snapshots/relay_server__metrics_extraction__event__tests__extract_span_metrics_mobile.snap @@ -104,6 +104,7 @@ expression: "(&event.value().unwrap().spans, metrics)" ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, @@ -426,6 +427,7 @@ expression: "(&event.value().unwrap().spans, metrics)" ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, @@ -526,6 +528,7 @@ expression: "(&event.value().unwrap().spans, metrics)" ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, @@ -674,6 +677,7 @@ expression: "(&event.value().unwrap().spans, metrics)" ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, @@ -774,6 +778,7 @@ expression: "(&event.value().unwrap().spans, metrics)" ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: ~, ui_component_name: ~, url_scheme: ~, diff --git a/relay-server/tests/snapshots/test_fixtures__event_schema.snap b/relay-server/tests/snapshots/test_fixtures__event_schema.snap index 876de92c66..8d6695f3c5 100644 --- a/relay-server/tests/snapshots/test_fixtures__event_schema.snap +++ b/relay-server/tests/snapshots/test_fixtures__event_schema.snap @@ -1079,6 +1079,26 @@ expression: "relay_event_schema::protocol::event_json_schema()" "type": "null" } ] + }, + "thread.id": { + "description": " The ID of the thread from which the transaction originated from", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "thread.name": { + "description": " The name of the thread from which the transaction originated from", + "default": null, + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/relay-spans/src/span.rs b/relay-spans/src/span.rs index 507e868f56..8443a3ced0 100644 --- a/relay-spans/src/span.rs +++ b/relay-spans/src/span.rs @@ -654,6 +654,7 @@ mod tests { ai_input_messages: ~, ai_responses: ~, thread_name: ~, + thread_id: ~, segment_name: "my 1st transaction", ui_component_name: ~, url_scheme: ~,