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