diff --git a/CHANGELOG.md b/CHANGELOG.md index d73455db768..fab67a52709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Adopt new `AsyncPool` for the `EnvelopeProcessorService` and `StoreService`. ([#4520](https://github.com/getsentry/relay/pull/4520)) - Update mapping of OTLP spans to Sentry spans in the experimental OTL traces endpoint. ([#4505](https://github.com/getsentry/relay/pull/4505)) - Expose metrics for the `AsyncPool`. ([#4538](https://github.com/getsentry/relay/pull/4538)) +- Infer span `description` for spans with `category` set to `db` from query attribute. ([#4541](https://github.com/getsentry/relay/pull/4541)) - Expose service utilization metrics through the internal relay metric endpoint. ([#4543](https://github.com/getsentry/relay/pull/4543)) - Always set observed time for OTel logs in Relay. ([#4559](https://github.com/getsentry/relay/pull/4559)) diff --git a/relay-event-schema/src/protocol/span.rs b/relay-event-schema/src/protocol/span.rs index 8ad33f5db02..f1ba39d8354 100644 --- a/relay-event-schema/src/protocol/span.rs +++ b/relay-event-schema/src/protocol/span.rs @@ -393,6 +393,13 @@ pub struct SpanData { #[metastructure(field = "db.operation")] pub db_operation: Annotated, + /// The database query being executed. + /// + /// E.g. SELECT * FROM wuser_table where username = ?; SET mykey ? + /// See [OpenTelemetry docs for a list of well-known identifiers](https://opentelemetry.io/docs/specs/semconv/database/database-spans/#common-attributes). + #[metastructure(field = "db.query.text", legacy_alias = "db.statement")] + pub db_query_text: Annotated, + /// An identifier for the database management system (DBMS) product being used. /// /// See [OpenTelemetry docs for a list of well-known identifiers](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#notes-and-well-known-identifiers-for-dbsystem). @@ -1028,6 +1035,7 @@ mod tests { let data = r#"{ "foo": 2, "bar": "3", + "db.query.text": "SELECT * FROM table", "db.system": "mysql", "code.filepath": "task.py", "code.lineno": 123, @@ -1072,6 +1080,7 @@ mod tests { "ns", ), db_operation: ~, + db_query_text: "SELECT * FROM table", db_system: String( "mysql", ), diff --git a/relay-event-schema/src/protocol/span/convert.rs b/relay-event-schema/src/protocol/span/convert.rs index bc6bb117075..9f5a4713acd 100644 --- a/relay-event-schema/src/protocol/span/convert.rs +++ b/relay-event-schema/src/protocol/span/convert.rs @@ -170,6 +170,7 @@ mod tests { code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: "prod", 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 51b50d15908..9dd0d419fc6 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 @@ -123,6 +123,7 @@ expression: "(&event.value().unwrap().spans, metrics.project_metrics)" code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: ~, @@ -645,6 +646,7 @@ expression: "(&event.value().unwrap().spans, metrics.project_metrics)" code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: ~, @@ -802,6 +804,7 @@ expression: "(&event.value().unwrap().spans, metrics.project_metrics)" code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: ~, @@ -1041,6 +1044,7 @@ expression: "(&event.value().unwrap().spans, metrics.project_metrics)" code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: ~, @@ -1198,6 +1202,7 @@ expression: "(&event.value().unwrap().spans, metrics.project_metrics)" code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: ~, diff --git a/relay-server/src/services/processor/span/processing.rs b/relay-server/src/services/processor/span/processing.rs index e3cf0eac82c..03f8de00509 100644 --- a/relay-server/src/services/processor/span/processing.rs +++ b/relay-server/src/services/processor/span/processing.rs @@ -644,6 +644,12 @@ fn normalize( ); span.sentry_tags = Annotated::new(tags); + // This inference depends on `sentry_tags.category`, which is set during tag + // extraction above. Please be careful if trying to reorder this operation. + if span.description.value().is_empty() { + span.description = infer_span_description(span).into(); + } + normalize_performance_score(span, performance_score); if let Some(model_costs_config) = ai_model_costs { extract_ai_measurements(span, model_costs_config); @@ -803,6 +809,17 @@ fn validate(span: &mut Annotated) -> Result<(), ValidationError> { Ok(()) } +// Infer a span's `description` based on its attributes. Note that tag +// extraction must have already run before this is called, as it relies on the +// values of `span.sentry_tags`. +fn infer_span_description(span: &Span) -> Option { + let category = span.sentry_tags.value()?.category.value()?; + match category.as_str() { + "db" => span.data.value()?.db_query_text.value()?.to_owned().into(), + _ => None, + } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; @@ -1431,4 +1448,28 @@ mod tests { &EventId("480ffcc911174ade9106b40ffbd822f5".parse().unwrap()) ); } + + #[test] + fn infers_db_span_description() { + let mut span = Annotated::from_json( + r#"{ + "start_timestamp": 0, + "timestamp": 1, + "trace_id": "922dda2462ea4ac2b6a4b339bee90863", + "span_id": "922dda2462ea4ac2", + "data": { + "db.query.text": "SELECT * FROM users WHERE id = 1", + "sentry.category": "db" + } + }"#, + ) + .unwrap(); + + normalize(&mut span, normalize_config()).unwrap(); + + assert_eq!( + get_value!(span.description!).as_str(), + "SELECT * FROM users WHERE id = 1" + ); + } } diff --git a/relay-spans/src/span.rs b/relay-spans/src/span.rs index 83894980e8f..8e43b624127 100644 --- a/relay-spans/src/span.rs +++ b/relay-spans/src/span.rs @@ -123,9 +123,6 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan { } key if key.starts_with("db") => { op = op.or(Some("db".to_string())); - if key == "db.statement" { - description = description.or_else(|| otel_value_to_string(value)); - } } "http.method" | "http.request.method" => { let http_op = match kind { @@ -642,6 +639,7 @@ mod tests { code_function: ~, code_namespace: ~, db_operation: ~, + db_query_text: ~, db_system: ~, db_collection_name: ~, environment: "prod",