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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Don't reject attributes that don't have values, but do have metadata. ([#6098](https://github.com/getsentry/relay/pull/6098))
- Infer span names PII-safely. ([#6112](https://github.com/getsentry/relay/pull/6112))
- Unset segment info for web vital spans. ([#6042](https://github.com/getsentry/relay/pull/6042))
- Set sentry.trace.status on segment spans. ([#6140](https://github.com/getsentry/relay/pull/6140))

**Internal**:

Expand Down
115 changes: 114 additions & 1 deletion relay-event-normalization/src/eap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use relay_common::time::UnixTimestamp;
use relay_conventions::attributes::*;
use relay_conventions::{AttributeInfo, ReplacementName, WriteBehavior};
use relay_event_schema::protocol::{
Attribute, AttributeType, Attributes, BrowserContext, Geo, SpanV2,
Attribute, AttributeType, Attributes, BrowserContext, Geo, SpanV2, SpanV2Status,
};
use relay_protocol::{Annotated, Empty, Error, ErrorKind, Meta, Remark, RemarkType, Value};
use relay_sampling::DynamicSamplingContext;
Expand Down Expand Up @@ -447,6 +447,35 @@ pub fn normalize_dsc(
}
}

/// Sets the `sentry.trace.status` attribute on segment spans.
///
/// The value is derived from the `sentry.status` attribute if present, falling back to the span's
/// top-level `status` field.
pub fn normalize_trace_status(
attributes: &mut Annotated<Attributes>,
is_segment: &Annotated<bool>,
status: &Annotated<SpanV2Status>,
) {
if is_segment.value().is_none_or(|is_segment| !*is_segment) {
return;
}

let attributes = attributes.get_or_insert_with(Default::default);
Comment thread
mjq marked this conversation as resolved.
if attributes.contains_key("sentry.trace.status") {
return;
}

let trace_status = attributes
.get_value("sentry.status")
.and_then(|v| v.as_str())
.map(|s| s.to_owned())
.or_else(|| status.value().map(|s| s.to_string()));

if let Some(trace_status) = trace_status {
attributes.insert("sentry.trace.status", trace_status);
}
}

/// Normalizes the client sample rate attribute to be in the range `(0, 1]`.
///
/// This is only relevant for spans as other eap types re not sampled.
Expand Down Expand Up @@ -983,6 +1012,90 @@ mod tests {
"#);
}

#[test]
fn test_normalize_trace_status_not_segment() {
let mut attributes = Annotated::empty();
normalize_trace_status(
&mut attributes,
&Annotated::new(false),
&Annotated::new(SpanV2Status::Ok),
);
assert!(attributes.value().is_none());
}

#[test]
fn test_normalize_trace_status_already_set() {
let mut attributes = Annotated::from_json(
r#"{"sentry.trace.status": {"type": "string", "value": "internal_error"}}"#,
)
.unwrap();
normalize_trace_status(
&mut attributes,
&Annotated::new(true),
&Annotated::new(SpanV2Status::Error),
);
assert_eq!(
attributes
.value()
.unwrap()
.get_value("sentry.trace.status")
.and_then(|v| v.as_str()),
Some("internal_error"),
);
}

#[test]
fn test_normalize_trace_status_from_sentry_status_attribute() {
let mut attributes = Annotated::from_json(
r#"{"sentry.status": {"type": "string", "value": "internal_error"}}"#,
)
.unwrap();
normalize_trace_status(
&mut attributes,
&Annotated::new(true),
&Annotated::new(SpanV2Status::Error),
);
assert_eq!(
attributes
.value()
.unwrap()
.get_value("sentry.trace.status")
.and_then(|v| v.as_str()),
Some("internal_error"),
);
}

#[test]
fn test_normalize_trace_status_from_span_status() {
let mut attributes = Annotated::empty();
normalize_trace_status(
&mut attributes,
&Annotated::new(true),
&Annotated::new(SpanV2Status::Error),
);
assert_eq!(
attributes
.value()
.unwrap()
.get_value("sentry.trace.status")
.and_then(|v| v.as_str()),
Some("error"),
);
}

#[test]
fn test_normalize_trace_status_no_status() {
let mut attributes = Annotated::empty();
normalize_trace_status(&mut attributes, &Annotated::new(true), &Annotated::empty());
assert!(
attributes
.value()
.unwrap()
.get_value("sentry.trace.status")
.is_none(),
);
}

#[test]
fn test_normalize_received_none() {
let mut attributes = Default::default();
Expand Down
11 changes: 11 additions & 0 deletions relay-server/src/processing/spans/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,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, headers.dsc());
eap::normalize_trace_status(&mut span.attributes, &span.is_segment, &span.status);
if ctx.is_processing() {
eap::normalize_ai(&mut span.attributes, duration, model_metdata);
}
Expand Down Expand Up @@ -1037,6 +1038,16 @@ mod tests {
});
}

#[test]
fn test_normalize_trace_status_on_segment_span() {
let (mut span, headers, geo_lookup, ctx) = prepare_normalize_span_params(&[], &[]);
span.value_mut().as_mut().unwrap().is_segment = Annotated::new(true);

normalize_span(&mut span, Default::default(), &headers, &geo_lookup, ctx).unwrap();

assert_attributes_contains(&span, &[("sentry.trace.status", "ok")], &[]);
}

#[test]
#[allow(deprecated, reason = "This test is meant to access legacy attributes.")]
fn test_insights_backend_queries_support_modern() {
Expand Down
20 changes: 16 additions & 4 deletions tests/integration/test_spansv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def test_spansv2_basic(
"value": time_within(ts, expect_resolution="ns"),
},
"sentry.op": {"type": "string", "value": "default"},
"sentry.trace.status": {"type": "string", "value": "ok"},
},
"_meta": {
"attributes": {
Expand Down Expand Up @@ -310,7 +311,7 @@ def test_spansv2_trimming_basic(
"attributes": {
"custom.array.attribute": {
"type": "array",
"value": ["A string", "Another longer string", "Yet anothe..."],
"value": ["A string", "Another lo...", None],
},
"custom.string.attribute": {
"type": "string",
Expand Down Expand Up @@ -340,15 +341,16 @@ def test_spansv2_trimming_basic(
"value": time_within(ts, expect_resolution="ns"),
},
"sentry.op": {"type": "string", "value": "default"},
"sentry.trace.status": {"type": "string", "value": "ok"},
},
"_meta": {
"attributes": {
"": {"len": 565},
"": {"len": 586},
"custom.array.attribute": {
"value": {
"2": {
"1": {
"": {
"len": 18,
"len": 21,
"rem": [
[
"!limit",
Expand All @@ -359,6 +361,16 @@ def test_spansv2_trimming_basic(
],
},
},
"2": {
"": {
"rem": [
[
"trimmed",
"x",
],
],
},
},
},
},
"custom.invalid.attribute": {
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_spansv2_otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def test_span_ingestion(
"value": time_within(ts, expect_resolution="ns"),
},
"sentry.op": {"type": "string", "value": "default"},
"sentry.trace.status": {"type": "string", "value": "ok"},
"sentry.origin": {"type": "string", "value": "auto.otlp.spans"},
"sentry.segment.id": {"type": "string", "value": "f0b809703e783d00"},
"sentry.segment.name": {"type": "string", "value": "A Proto Span"},
Expand Down
Loading