Skip to content

Commit ed3f140

Browse files
gggritsoloewenheim
andauthored
feat(otlp): Derive a sentry.description attribute for V2 spans (#4832)
Very similar to #4796, but for the `span.description` attribute. Supports [Span Field Transition](https://linear.app/getsentry/document/span-field-transition-e0e4b8e59f11). V2 spans probably will still have a description, but OTLP spans will not. To make the transition easier, and to create a better experience in the span waterfall, this PR derives a description for HTTP and database spans based on the logic that exists in the [JavaScript SDK](https://github.com/getsentry/sentry-javascript/blob/master/packages/opentelemetry/src/utils/parseSpanDescription.ts#L41-L94). It's _slightly_ different from the SDK logic, but not in any important way, from what I can tell. 1. If the payload includes a description, obeys the description as sent, even if it's an empty string, and sets it as the `sentry.description` attribute 2. If the payload does not include a `description` field, generate a description and put it in the `sentry.description` attribute 3. If we cannot determine a good description, fall back to using the `name` field, since it's similar in purpose I also removed the little bit of current `description` processing, since it all lives near the `op` processing now. --------- Co-authored-by: Sebastian Zivota <loewenheim@users.noreply.github.com>
1 parent 19b5869 commit ed3f140

File tree

7 files changed

+546
-60
lines changed

7 files changed

+546
-60
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Update opentelemetry-proto and sentry-protos dependencies. ([#4847](https://github.com/getsentry/relay/pull/4847))
1313
- Take into account more types of tokens when doing AI cost calculation. ([#4840](https://github.com/getsentry/relay/pull/4840))
1414
- Use the `FiniteF64` type for measurements. ([#4828](https://github.com/getsentry/relay/pull/4828))
15+
- Derive a `sentry.description` attribute for V2 spans ([#4832](https://github.com/getsentry/relay/pull/4832))
1516

1617
## 25.6.1
1718

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

relay-spans/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ opentelemetry-proto = { workspace = true, features = [
2424
relay-event-schema = { workspace = true }
2525
relay-protocol = { workspace = true }
2626
serde_json = { workspace = true }
27+
url = { workspace = true }
2728

2829
[dev-dependencies]
2930
insta = { workspace = true }

relay-spans/src/otel_to_sentry.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use relay_protocol::Error;
1111
/// if the OTEL span's `name` is empty.
1212
/// * The Sentry span's `op` field will be inferred based on the OTEL span's `sentry.op` attribute,
1313
/// or other available attributes if `sentry.op` is not provided.
14-
/// * The Sentry span's `description` field may be set based on `db` or `http` attributes
15-
/// if the OTEL span's `sentry.description` attribute is empty.
14+
/// * The Sentry span's `description` field will be inferred based on the OTEL span's
15+
/// `sentry.description` attribute, or other available attributes if `sentry.description` is not
16+
/// provided.
1617
/// * The Sentry span's `status` field is set based on the OTEL span's `status` field and
1718
/// `http.status_code` and `rpc.grpc.status_code` attributes.
1819
/// * The Sentry span's `exclusive_time` field is set based on the OTEL span's `exclusive_time_nano`
@@ -39,7 +40,7 @@ mod tests {
3940
"spanId": "e342abb1214ca181",
4041
"parentSpanId": "0c7a7dea069bf5a6",
4142
"name": "middleware - fastify -> @fastify/multipart",
42-
"kind": 1,
43+
"kind": 2,
4344
"startTimeUnixNano": "1697620454980000000",
4445
"endTimeUnixNano": "1697620454980078800",
4546
"attributes": [
@@ -115,7 +116,7 @@ mod tests {
115116
"timestamp": 1697620454.980079,
116117
"start_timestamp": 1697620454.98,
117118
"exclusive_time": 1000.0,
118-
"op": "http",
119+
"op": "http.server",
119120
"span_id": "e342abb1214ca181",
120121
"parent_span_id": "0c7a7dea069bf5a6",
121122
"trace_id": "89143b0763095bd9c9955e8175d1fb23",
@@ -133,7 +134,7 @@ mod tests {
133134
"sentry.sample_rate": 1
134135
},
135136
"links": [],
136-
"kind": "internal"
137+
"kind": "server"
137138
}
138139
"###);
139140
}
@@ -170,6 +171,7 @@ mod tests {
170171
"parent_span_id": "0c7a7dea069bf5a6",
171172
"trace_id": "89143b0763095bd9c9955e8175d1fb23",
172173
"status": "unknown",
174+
"description": "middleware - fastify -> @fastify/multipart",
173175
"data": {
174176
"sentry.name": "middleware - fastify -> @fastify/multipart"
175177
},
@@ -203,6 +205,7 @@ mod tests {
203205
"parent_span_id": "0c7a7dea069bf5a6",
204206
"trace_id": "89143b0763095bd9c9955e8175d1fb23",
205207
"status": "unknown",
208+
"description": "middleware - fastify -> @fastify/multipart",
206209
"data": {
207210
"sentry.name": "middleware - fastify -> @fastify/multipart"
208211
},
@@ -223,6 +226,12 @@ mod tests {
223226
"startTimeUnixNano": "1697620454980000000",
224227
"endTimeUnixNano": "1697620454980078800",
225228
"attributes": [
229+
{
230+
"key" : "db.system",
231+
"value": {
232+
"stringValue": "mysql"
233+
}
234+
},
226235
{
227236
"key" : "db.name",
228237
"value": {
@@ -251,13 +260,14 @@ mod tests {
251260
"timestamp": 1697620454.980079,
252261
"start_timestamp": 1697620454.98,
253262
"exclusive_time": 0.0788,
254-
"op": "default",
263+
"op": "db",
255264
"span_id": "e342abb1214ca181",
256265
"parent_span_id": "0c7a7dea069bf5a6",
257266
"trace_id": "89143b0763095bd9c9955e8175d1fb23",
258267
"status": "unknown",
259268
"description": "SELECT \"table\".\"col\" FROM \"table\" WHERE \"table\".\"col\" = %s",
260269
"data": {
270+
"db.system": "mysql",
261271
"db.name": "database",
262272
"db.statement": "SELECT \"table\".\"col\" FROM \"table\" WHERE \"table\".\"col\" = %s",
263273
"db.type": "sql",
@@ -339,7 +349,7 @@ mod tests {
339349
"spanId": "e342abb1214ca181",
340350
"parentSpanId": "0c7a7dea069bf5a6",
341351
"name": "http client request",
342-
"kind": 3,
352+
"kind": 2,
343353
"startTimeUnixNano": "1697620454980000000",
344354
"endTimeUnixNano": "1697620454980078800",
345355
"attributes": [
@@ -365,7 +375,7 @@ mod tests {
365375
"timestamp": 1697620454.980079,
366376
"start_timestamp": 1697620454.98,
367377
"exclusive_time": 0.0788,
368-
"op": "http.client",
378+
"op": "http.server",
369379
"span_id": "e342abb1214ca181",
370380
"parent_span_id": "0c7a7dea069bf5a6",
371381
"trace_id": "89143b0763095bd9c9955e8175d1fb23",
@@ -377,7 +387,7 @@ mod tests {
377387
"url.path": "/api/search?q=foobar"
378388
},
379389
"links": [],
380-
"kind": "client"
390+
"kind": "server"
381391
}
382392
"###);
383393
}

relay-spans/src/otel_to_sentry_v2.rs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -54,36 +54,23 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> Result<SentrySpanV2, Error> {
5454

5555
let mut sentry_attributes = Attributes::new();
5656
let mut name = if name.is_empty() { None } else { Some(name) };
57-
let mut description = None;
58-
let mut http_method = None;
59-
let mut http_route = None;
6057

6158
for (key, value) in attributes.into_iter().flat_map(|attribute| {
6259
let value = attribute.value?.value?;
6360
Some((attribute.key, value))
6461
}) {
6562
match key.as_str() {
66-
"sentry.description" => {
67-
description = otel_value_to_string(value.clone());
68-
}
6963
key if key.starts_with("db") => {
7064
name = name.or(Some("db".to_owned()));
71-
if key == "db.statement" {
72-
description = description.or_else(|| otel_value_to_string(value.clone()));
73-
}
7465
}
7566
"http.method" | "http.request.method" => {
7667
let http_op = match kind {
7768
2 => "http.server",
7869
3 => "http.client",
7970
_ => "http",
8071
};
81-
http_method = otel_value_to_string(value.clone());
8272
name = name.or(Some(http_op.to_owned()));
8373
}
84-
"http.route" | "url.path" => {
85-
http_route = otel_value_to_string(value.clone());
86-
}
8774
_ => (),
8875
}
8976

@@ -92,15 +79,6 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> Result<SentrySpanV2, Error> {
9279
}
9380
}
9481

95-
if let (Some(http_method), Some(http_route)) = (http_method, http_route) {
96-
description = description.or_else(|| Some(format!("{http_method} {http_route}")));
97-
}
98-
99-
// Put the fixed up description back into the attributes
100-
if let Some(description) = description {
101-
sentry_attributes.insert("sentry.description".into(), description);
102-
}
103-
10482
let sentry_links: Vec<Annotated<SpanV2Link>> = links
10583
.into_iter()
10684
.map(|link| otel_to_sentry_link(link).map(Into::into))
@@ -126,17 +104,6 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> Result<SentrySpanV2, Error> {
126104
Ok(event_span)
127105
}
128106

129-
fn otel_value_to_string(value: OtelValue) -> Option<String> {
130-
match value {
131-
OtelValue::StringValue(v) => Some(v),
132-
OtelValue::BoolValue(v) => Some(v.to_string()),
133-
OtelValue::IntValue(v) => Some(v.to_string()),
134-
OtelValue::DoubleValue(v) => Some(v.to_string()),
135-
OtelValue::BytesValue(v) => String::from_utf8(v).ok(),
136-
_ => None,
137-
}
138-
}
139-
140107
fn otel_flags_is_remote(value: u32) -> Option<bool> {
141108
if value & OtelSpanFlags::ContextHasIsRemoteMask as u32 == 0 {
142109
None
@@ -418,10 +385,6 @@ mod tests {
418385
"db.type": {
419386
"type": "string",
420387
"value": "sql"
421-
},
422-
"sentry.description": {
423-
"type": "string",
424-
"value": "SELECT \"table\".\"col\" FROM \"table\" WHERE \"table\".\"col\" = %s"
425388
}
426389
}
427390
}
@@ -543,10 +506,6 @@ mod tests {
543506
"type": "string",
544507
"value": "GET"
545508
},
546-
"sentry.description": {
547-
"type": "string",
548-
"value": "GET /api/search?q=foobar"
549-
},
550509
"url.path": {
551510
"type": "string",
552511
"value": "/api/search?q=foobar"

0 commit comments

Comments
 (0)