relay_event_normalization/normalize/span/
reparent_broken_spans.rs

1//! Span tree normalization.
2
3use std::collections::BTreeSet;
4use std::mem;
5
6use relay_event_schema::protocol::{Event, TraceContext};
7use relay_protocol::Error;
8
9/// Enforce that every span has a valid parent. If any `parent_span_id` is pointing nowhere, the
10/// span is re-parented onto the root span.
11///
12/// This is to avoid any nasty surprises in the span buffer specifically. Other readers of spans
13/// (such as the frontend's tree component) were already able to deal with detached spans.
14pub fn reparent_broken_spans(event: &mut Event) {
15    let Some(spans) = event.spans.value_mut() else {
16        return;
17    };
18
19    let Some(contexts) = event.contexts.value_mut() else {
20        return;
21    };
22
23    let Some(trace_context) = contexts.get_mut::<TraceContext>() else {
24        return;
25    };
26
27    let Some(root_span_id) = trace_context.span_id.value() else {
28        return;
29    };
30
31    let valid_span_ids = spans
32        .iter()
33        .filter_map(|span| span.value())
34        .filter_map(|span| span.span_id.value())
35        .chain(Some(root_span_id))
36        .cloned()
37        .collect::<BTreeSet<_>>();
38
39    for span in spans {
40        let Some(span) = span.value_mut() else {
41            continue;
42        };
43
44        let Some(parent_span_id) = span.parent_span_id.value_mut() else {
45            continue;
46        };
47
48        if valid_span_ids.contains(&*parent_span_id) {
49            continue;
50        };
51
52        let invalid_parent = mem::replace(parent_span_id, root_span_id.clone());
53        let meta = span.parent_span_id.meta_mut();
54        meta.add_error(Error::invalid("span ID does not exist"));
55        meta.set_original_value(Some(invalid_parent));
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use relay_protocol::FromValue;
62    use relay_protocol::SerializableAnnotated;
63
64    use super::*;
65
66    #[test]
67    fn basic() {
68        let mut data = Event::from_value(
69            serde_json::json!({
70                "type": "transaction",
71                "start_timestamp": 1609455600,
72                "end_timestamp": 1609455605,
73                "contexts": {
74                    "trace": {
75                        "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
76                        "span_id": "aaaaaaaaaaaaaaaa",
77                        "parent_span_id": "ffffffffffffffff",
78                    }
79                },
80                "spans": [
81                    {
82                        "span_id": "bbbbbbbbbbbbbbbb",
83                        "parent_span_id": "aaaaaaaaaaaaaaaa"
84                    },
85                    {
86                        "span_id": "bbbbbbbbbbbbbbbb",
87                        "parent_span_id": "dddddddddddddddd",
88                    },
89                    {
90                        "span_id": "bbbbbbbbbbbbbbbb",
91                        "parent_span_id": "eeeeeeeeeeeeeeee",
92                    },
93                ],
94            })
95            .into(),
96        );
97
98        reparent_broken_spans(data.value_mut().as_mut().unwrap());
99
100        insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
101        {
102          "type": "transaction",
103          "start_timestamp": 1609455600.0,
104          "contexts": {
105            "trace": {
106              "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
107              "span_id": "aaaaaaaaaaaaaaaa",
108              "parent_span_id": "ffffffffffffffff",
109              "type": "trace"
110            }
111          },
112          "spans": [
113            {
114              "span_id": "bbbbbbbbbbbbbbbb",
115              "parent_span_id": "aaaaaaaaaaaaaaaa"
116            },
117            {
118              "span_id": "bbbbbbbbbbbbbbbb",
119              "parent_span_id": "aaaaaaaaaaaaaaaa"
120            },
121            {
122              "span_id": "bbbbbbbbbbbbbbbb",
123              "parent_span_id": "aaaaaaaaaaaaaaaa"
124            }
125          ],
126          "end_timestamp": 1609455605,
127          "_meta": {
128            "spans": {
129              "1": {
130                "parent_span_id": {
131                  "": {
132                    "err": [
133                      [
134                        "invalid_data",
135                        {
136                          "reason": "span ID does not exist"
137                        }
138                      ]
139                    ],
140                    "val": "dddddddddddddddd"
141                  }
142                }
143              },
144              "2": {
145                "parent_span_id": {
146                  "": {
147                    "err": [
148                      [
149                        "invalid_data",
150                        {
151                          "reason": "span ID does not exist"
152                        }
153                      ]
154                    ],
155                    "val": "eeeeeeeeeeeeeeee"
156                  }
157                }
158              }
159            }
160          }
161        }
162        "###);
163    }
164}