Skip to content

Commit b629b62

Browse files
authored
feat(spans): Retain empty attributes (#4174)
Currently well-known span attributes may be set to `""`, but custom attributes (which end up in the `other` field) are removed if their value is empty. To be consistent in itself and with [open telemetry](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute), we should allow empty non-null values. Apply this change also to `event.contexts.trace.data` because it is supposed to be consistent with `span.data`.
1 parent 0adf112 commit b629b62

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
- Allow profile chunks without release. ([#4155](https://github.com/getsentry/relay/pull/4155))
88
- Add validation for timestamps sent from the future. ([#4163](https://github.com/getsentry/relay/pull/4163))
99

10+
11+
**Features:**
12+
13+
- Retain empty string values in `span.data` and `event.contexts.trace.data`. ([#4174](https://github.com/getsentry/relay/pull/4174))
14+
1015
**Internal:**
1116

1217
- Add a metric that counts span volume in the root project for dynamic sampling (`c:spans/count_per_root_project@none`). ([#4134](https://github.com/getsentry/relay/pull/4134))

relay-event-schema/src/protocol/contexts/trace.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ pub struct TraceContext {
135135
pub sampled: Annotated<bool>,
136136

137137
/// Data of the trace's root span.
138-
#[metastructure(pii = "maybe", skip_serialization = "empty")]
138+
#[metastructure(pii = "maybe", skip_serialization = "null")]
139139
pub data: Annotated<SpanData>,
140140

141141
/// Additional arbitrary fields for forwards compatibility.
@@ -197,7 +197,8 @@ mod tests {
197197
"tok": "test"
198198
},
199199
"custom_field": "something"
200-
}
200+
},
201+
"custom_field_empty": ""
201202
},
202203
"other": "value",
203204
"type": "trace"
@@ -222,15 +223,15 @@ mod tests {
222223
);
223224
map
224225
}),
225-
other: {
226-
let mut map = Object::new();
227-
map.insert(
228-
"custom_field".into(),
229-
Annotated::new(Value::String("something".into())),
230-
);
231-
map
232-
},
226+
other: Object::from([(
227+
"custom_field".into(),
228+
Annotated::new(Value::String("something".into())),
229+
)]),
233230
}),
231+
other: Object::from([(
232+
"custom_field_empty".into(),
233+
Annotated::new(Value::String("".into())),
234+
)]),
234235
..Default::default()
235236
}),
236237
other: {

relay-event-schema/src/protocol/span.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ pub struct SpanData {
506506
additional_properties,
507507
pii = "true",
508508
retain = "true",
509-
skip_serialization = "empty"
509+
skip_serialization = "null" // applies to child elements
510510
)]
511511
pub other: Object<Value>,
512512
}
@@ -931,4 +931,38 @@ mod tests {
931931
assert_eq!(data.get_value("code\\.namespace"), Some(Val::String("ns")));
932932
assert_eq!(data.get_value("unknown"), None);
933933
}
934+
935+
#[test]
936+
fn test_span_data_empty_well_known_field() {
937+
let span = r#"{
938+
"data": {
939+
"lcp.url": ""
940+
}
941+
}"#;
942+
let span: Annotated<Span> = Annotated::from_json(span).unwrap();
943+
assert_eq!(span.to_json().unwrap(), r#"{"data":{"lcp.url":""}}"#);
944+
}
945+
946+
#[test]
947+
fn test_span_data_empty_custom_field() {
948+
let span = r#"{
949+
"data": {
950+
"custom_field_empty": ""
951+
}
952+
}"#;
953+
let span: Annotated<Span> = Annotated::from_json(span).unwrap();
954+
assert_eq!(
955+
span.to_json().unwrap(),
956+
r#"{"data":{"custom_field_empty":""}}"#
957+
);
958+
}
959+
960+
#[test]
961+
fn test_span_data_completely_empty() {
962+
let span = r#"{
963+
"data": {}
964+
}"#;
965+
let span: Annotated<Span> = Annotated::from_json(span).unwrap();
966+
assert_eq!(span.to_json().unwrap(), r#"{"data":{}}"#);
967+
}
934968
}

0 commit comments

Comments
 (0)