Skip to content

Commit 90f0dfa

Browse files
mjqLms24s1gr1d
authored
feat(links): write span links to Kafka (#4601)
Span links were added in #4486, but are currently only accessible as part of transaction events. We would like to get them in EAP. The first step in that process is to add them to serialized Kafka spans. The integration tests confirm production into Kafka for every current path for links to enter the system: - in a transaction event's trace context - in a transaction event's child spans - in a `span` envelope item - in an `otel_span` envelope item - via OTLP JSON - via OTLP gRPC Note that our version of the `opentelemetry-proto` Python library is too old to make `flags` available so not all tests could check the `sampled` handling. I'll handle that in a future PR. --------- Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io> Co-authored-by: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
1 parent 27f00ae commit 90f0dfa

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- Add `/v1/traces` (without a trailing slash) as a spec-compliant alternative for our OTLP traces endpoint. ([#4655](https://github.com/getsentry/relay/pull/4655))
4343
- Improve handling of failed Redis connections. ([#4657](https://github.com/getsentry/relay/pull/4657))
4444
- Expose new metrics from the async pool. ([#4658](https://github.com/getsentry/relay/pull/4658))
45+
- Serialize span's `links` information when producing to Kafka. ([#4601](https://github.com/getsentry/relay/pull/4601))
4546

4647
## 25.3.0
4748

relay-server/src/services/store.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,16 @@ struct CheckInKafkaMessage {
13871387
retention_days: u16,
13881388
}
13891389

1390+
#[derive(Debug, Deserialize, Serialize)]
1391+
struct SpanLink<'a> {
1392+
pub trace_id: &'a str,
1393+
pub span_id: &'a str,
1394+
#[serde(default, skip_serializing_if = "Option::is_none")]
1395+
pub sampled: Option<bool>,
1396+
#[serde(borrow)]
1397+
pub attributes: Option<&'a RawValue>,
1398+
}
1399+
13901400
#[derive(Debug, Deserialize, Serialize)]
13911401
struct SpanMeasurement {
13921402
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -1413,6 +1423,8 @@ struct SpanKafkaMessage<'a> {
14131423
data: Option<&'a RawValue>,
14141424
#[serde(default, skip_serializing_if = "Option::is_none")]
14151425
kind: Option<&'a str>,
1426+
#[serde(default, skip_serializing_if = "none_or_empty_vec")]
1427+
links: Option<Vec<SpanLink<'a>>>,
14161428
#[serde(borrow, default, skip_serializing_if = "Option::is_none")]
14171429
measurements: Option<BTreeMap<Cow<'a, str>, Option<SpanMeasurement>>>,
14181430
#[serde(default)]
@@ -1528,6 +1540,13 @@ fn none_or_empty_object(value: &Option<&RawValue>) -> bool {
15281540
}
15291541
}
15301542

1543+
fn none_or_empty_vec<T>(value: &Option<Vec<T>>) -> bool {
1544+
match &value {
1545+
Some(vec) => vec.is_empty(),
1546+
None => true,
1547+
}
1548+
}
1549+
15311550
#[derive(Clone, Debug, Serialize)]
15321551
struct ProfileChunkKafkaMessage {
15331552
organization_id: OrganizationId,

tests/integration/test_spans.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,28 @@ def test_span_extraction(
6262
event = make_transaction({"event_id": "cbf6960622e14a45abc1f03b2055b186"})
6363
event["contexts"]["trace"]["status"] = "success"
6464
event["contexts"]["trace"]["origin"] = "manual"
65+
event["contexts"]["trace"]["links"] = [
66+
{
67+
"trace_id": "1f62a8b040f340bda5d830223def1d83",
68+
"span_id": "dbbbbbbbbbbbbbbd",
69+
"sampled": True,
70+
"attributes": {"txn_key": 123},
71+
},
72+
]
6573
end = datetime.now(timezone.utc) - timedelta(seconds=1)
6674
duration = timedelta(milliseconds=500)
6775
start = end - duration
6876
event["spans"] = [
6977
{
7078
"description": "GET /api/0/organizations/?member=1",
79+
"links": [
80+
{
81+
"trace_id": "0f62a8b040f340bda5d830223def1d82",
82+
"span_id": "cbbbbbbbbbbbbbbc",
83+
"sampled": True,
84+
"attributes": {"span_key": "span_value"},
85+
},
86+
],
7187
"op": "http",
7288
"origin": "manual",
7389
"parent_span_id": "968cff94913ebb07",
@@ -111,6 +127,14 @@ def test_span_extraction(
111127
"exclusive_time_ms": 500.0,
112128
"is_segment": False,
113129
"is_remote": False,
130+
"links": [
131+
{
132+
"trace_id": "0f62a8b040f340bda5d830223def1d82",
133+
"span_id": "cbbbbbbbbbbbbbbc",
134+
"sampled": True,
135+
"attributes": {"span_key": "span_value"},
136+
},
137+
],
114138
"organization_id": 1,
115139
"origin": "manual",
116140
"parent_span_id": "968cff94913ebb07",
@@ -162,6 +186,14 @@ def test_span_extraction(
162186
"exclusive_time_ms": 1500.0,
163187
"is_segment": True,
164188
"is_remote": True,
189+
"links": [
190+
{
191+
"trace_id": "1f62a8b040f340bda5d830223def1d83",
192+
"span_id": "dbbbbbbbbbbbbbbd",
193+
"sampled": True,
194+
"attributes": {"txn_key": 123},
195+
},
196+
],
165197
"organization_id": 1,
166198
"origin": "manual",
167199
"project_id": 42,
@@ -339,6 +371,20 @@ def envelope_with_spans(
339371
},
340372
},
341373
],
374+
"links": [
375+
{
376+
"traceId": "89143b0763095bd9c9955e8175d1fb24",
377+
"spanId": "e342abb1214ca183",
378+
"attributes": [
379+
{
380+
"key": "link_double_key",
381+
"value": {
382+
"doubleValue": 1.23,
383+
},
384+
},
385+
],
386+
},
387+
],
342388
},
343389
).encode()
344390
),
@@ -365,6 +411,16 @@ def envelope_with_spans(
365411
"data": {
366412
"browser.name": "Chrome",
367413
},
414+
"links": [
415+
{
416+
"trace_id": "99143b0763095bd9c9955e8175d1fb25",
417+
"span_id": "e342abb1214ca183",
418+
"sampled": True,
419+
"attributes": {
420+
"link_bool_key": True,
421+
},
422+
},
423+
],
368424
},
369425
).encode()
370426
),
@@ -474,6 +530,20 @@ def make_otel_span(start, end):
474530
},
475531
},
476532
],
533+
"links": [
534+
{
535+
"traceId": "89143b0763095bd9c9955e8175d1fb24",
536+
"spanId": "e342abb1214ca183",
537+
"attributes": [
538+
{
539+
"key": "link_int_key",
540+
"value": {
541+
"intValue": "123",
542+
},
543+
},
544+
],
545+
},
546+
],
477547
},
478548
],
479549
},
@@ -553,6 +623,18 @@ def test_span_ingestion(
553623
value=AnyValue(string_value="MyComponent"),
554624
),
555625
],
626+
links=[
627+
Span.Link(
628+
trace_id=bytes.fromhex("89143b0763095bd9c9955e8175d1fb24"),
629+
span_id=bytes.fromhex("e0b809703e783d01"),
630+
attributes=[
631+
KeyValue(
632+
key="link_str_key",
633+
value=AnyValue(string_value="link_str_value"),
634+
)
635+
],
636+
)
637+
],
556638
)
557639
scope_spans = ScopeSpans(spans=[protobuf_span])
558640
resource_spans = ResourceSpans(scope_spans=[scope_spans])
@@ -589,6 +671,14 @@ def test_span_ingestion(
589671
"is_segment": True,
590672
"is_remote": False,
591673
"kind": "unspecified",
674+
"links": [
675+
{
676+
"trace_id": "89143b0763095bd9c9955e8175d1fb24",
677+
"span_id": "e342abb1214ca183",
678+
"sampled": False,
679+
"attributes": {"link_double_key": 1.23},
680+
}
681+
],
592682
"organization_id": 1,
593683
"project_id": 42,
594684
"retention_days": 90,
@@ -618,6 +708,16 @@ def test_span_ingestion(
618708
"exclusive_time_ms": 345.0,
619709
"is_segment": True,
620710
"is_remote": False,
711+
"links": [
712+
{
713+
"trace_id": "99143b0763095bd9c9955e8175d1fb25",
714+
"span_id": "e342abb1214ca183",
715+
"sampled": True,
716+
"attributes": {
717+
"link_bool_key": True,
718+
},
719+
},
720+
],
621721
"measurements": {"score.total": {"value": 0.12121616}},
622722
"organization_id": 1,
623723
"project_id": 42,
@@ -673,6 +773,16 @@ def test_span_ingestion(
673773
"is_segment": True,
674774
"is_remote": False,
675775
"kind": "producer",
776+
"links": [
777+
{
778+
"trace_id": "89143b0763095bd9c9955e8175d1fb24",
779+
"span_id": "e342abb1214ca183",
780+
"sampled": False,
781+
"attributes": {
782+
"link_int_key": 123,
783+
},
784+
},
785+
],
676786
"organization_id": 1,
677787
"project_id": 42,
678788
"retention_days": 90,
@@ -726,6 +836,16 @@ def test_span_ingestion(
726836
"is_segment": False,
727837
"is_remote": False,
728838
"kind": "consumer",
839+
"links": [
840+
{
841+
"trace_id": "89143b0763095bd9c9955e8175d1fb24",
842+
"span_id": "e0b809703e783d01",
843+
"sampled": False,
844+
"attributes": {
845+
"link_str_key": "link_str_value",
846+
},
847+
},
848+
],
729849
"organization_id": 1,
730850
"parent_span_id": "f0f0f0abcdef1234",
731851
"project_id": 42,

0 commit comments

Comments
 (0)