Skip to main content

relay_event_normalization/normalize/span/
mod.rs

1//! Span normalization logic.
2
3use regex::Regex;
4use relay_event_schema::protocol::{Event, SpanData, TraceContext};
5use relay_protocol::Annotated;
6use std::sync::LazyLock;
7
8use crate::EnrichedDsc;
9
10pub mod ai;
11pub mod country_subregion;
12pub mod description;
13pub mod exclusive_time;
14pub mod tag_extraction;
15
16/// Regex used to scrub hex IDs and multi-digit numbers from table names and other identifiers.
17pub static TABLE_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
18    Regex::new(
19        r"(?ix)
20        [0-9a-f]{8}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{12} |
21        [0-9a-f]{8,} |
22        \d\d+
23        ",
24    )
25    .unwrap()
26});
27
28/// Replaces snake_case app start spans op with dot.case op.
29///
30/// This is done for the affected React Native SDK versions (from 3 to 4.4).
31pub fn normalize_app_start_spans(event: &mut Event) {
32    if !event.sdk_name().eq("sentry.javascript.react-native")
33        || !(event.sdk_version().starts_with("4.4")
34            || event.sdk_version().starts_with("4.3")
35            || event.sdk_version().starts_with("4.2")
36            || event.sdk_version().starts_with("4.1")
37            || event.sdk_version().starts_with("4.0")
38            || event.sdk_version().starts_with('3'))
39    {
40        return;
41    }
42
43    if let Some(spans) = event.spans.value_mut() {
44        for span in spans {
45            if let Some(span) = span.value_mut()
46                && let Some(op) = span.op.value()
47            {
48                if op == "app_start_cold" {
49                    span.op.set_value(Some("app.start.cold".to_owned()));
50                    break;
51                } else if op == "app_start_warm" {
52                    span.op.set_value(Some("app.start.warm".to_owned()));
53                    break;
54                }
55            }
56        }
57    }
58}
59
60/// Writes DSC attributes needed for dynamic sampling into the spans' `data`.
61///
62/// If `sentry.dsc.trace_id` is already present in a span's `data`, the function does nothing for
63/// that span.
64pub fn normalize_dsc_for_event_spans(event: &mut Event, dsc: Option<EnrichedDsc>) {
65    if let Some(ctx) = event.context_mut::<TraceContext>() {
66        normalize_dsc_for_span_data(&mut ctx.data, dsc);
67    }
68    if let Some(spans) = event.spans.value_mut() {
69        for span in spans {
70            if let Some(span) = span.value_mut() {
71                normalize_dsc_for_span_data(&mut span.data, dsc);
72            }
73        }
74    }
75}
76
77/// Writes DSC attributes needed for dynamic sampling into `span_data`.
78///
79/// If `sentry.dsc.trace_id` is already present in `span_data`, the function does nothing.
80pub fn normalize_dsc_for_span_data(span_data: &mut Annotated<SpanData>, dsc: Option<EnrichedDsc>) {
81    let Some(dsc) = dsc else {
82        return;
83    };
84
85    let data = span_data.get_or_insert_with(SpanData::default);
86    if data.sentry_dsc_trace_id.value().is_some() {
87        return;
88    }
89    data.sentry_dsc_trace_id = Annotated::new(dsc.dsc.trace_id.to_string());
90    data.sentry_dsc_project_id = Annotated::new(dsc.sampling_project_id.to_string());
91    if let Some(transaction) = &dsc.dsc.transaction {
92        data.sentry_dsc_transaction = Annotated::new(transaction.to_string());
93    }
94}