Skip to main content

relay_event_normalization/normalize/span/
mod.rs

1//! Span normalization logic.
2
3use regex::Regex;
4use relay_conventions::attributes::{
5    SENTRY__DSC__PROJECT_ID, SENTRY__DSC__TRACE_ID, SENTRY__DSC__TRANSACTION,
6};
7use relay_event_schema::protocol::{Event, SpanData, TraceContext};
8use relay_protocol::Annotated;
9use std::sync::LazyLock;
10
11use crate::NormalizationConfig;
12
13pub mod ai;
14pub mod country_subregion;
15pub mod description;
16pub mod exclusive_time;
17pub mod tag_extraction;
18
19/// Regex used to scrub hex IDs and multi-digit numbers from table names and other identifiers.
20pub static TABLE_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
21    Regex::new(
22        r"(?ix)
23        [0-9a-f]{8}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{12} |
24        [0-9a-f]{8,} |
25        \d\d+
26        ",
27    )
28    .unwrap()
29});
30
31/// Applies [`relay_conventions`] configured normalizations to transaction spans.
32pub fn normalize_conventions(event: &mut Event) {
33    if let Some(data) = event
34        .contexts
35        .value_mut()
36        .as_mut()
37        .and_then(|c| c.get_mut::<TraceContext>())
38        .map(|c| &mut c.data)
39    {
40        crate::eap::normalize_attribute_names(data);
41    }
42
43    if let Some(spans) = event.spans.value_mut() {
44        for data in spans
45            .iter_mut()
46            .filter_map(|span| span.value_mut().as_mut())
47            .map(|span| &mut span.data)
48        {
49            crate::eap::normalize_attribute_names(data);
50        }
51    }
52}
53
54/// Replaces snake_case app start spans op with dot.case op.
55///
56/// This is done for the affected React Native SDK versions (from 3 to 4.4).
57pub fn normalize_app_start_spans(event: &mut Event) {
58    if !event.sdk_name().eq("sentry.javascript.react-native")
59        || !(event.sdk_version().starts_with("4.4")
60            || event.sdk_version().starts_with("4.3")
61            || event.sdk_version().starts_with("4.2")
62            || event.sdk_version().starts_with("4.1")
63            || event.sdk_version().starts_with("4.0")
64            || event.sdk_version().starts_with('3'))
65    {
66        return;
67    }
68
69    if let Some(spans) = event.spans.value_mut() {
70        for span in spans {
71            if let Some(span) = span.value_mut()
72                && let Some(op) = span.op.value()
73            {
74                if op == "app_start_cold" {
75                    span.op.set_value(Some("app.start.cold".to_owned()));
76                    break;
77                } else if op == "app_start_warm" {
78                    span.op.set_value(Some("app.start.warm".to_owned()));
79                    break;
80                }
81            }
82        }
83    }
84}
85
86/// Writes DSC attributes needed for dynamic sampling into the spans' `data`.
87pub fn normalize_dsc_for_event_spans(event: &mut Event, config: &NormalizationConfig) {
88    if let Some(ctx) = event.context_mut::<TraceContext>() {
89        normalize_dsc_for_span_data(&mut ctx.data, config);
90    }
91    if let Some(spans) = event.spans.value_mut() {
92        for span in spans {
93            if let Some(span) = span.value_mut() {
94                normalize_dsc_for_span_data(&mut span.data, config);
95            }
96        }
97    }
98}
99
100/// Writes DSC attributes needed for dynamic sampling into `span_data`.
101pub fn normalize_dsc_for_span_data(
102    span_data: &mut Annotated<SpanData>,
103    config: &NormalizationConfig,
104) {
105    let Some(dsc) = config.dsc else {
106        return;
107    };
108
109    let data = span_data.get_or_insert_with(SpanData::default);
110    data.insert_value(SENTRY__DSC__TRACE_ID, dsc.trace_id.to_string());
111    if let Some(project_id) = &dsc.project_id {
112        data.insert_value(SENTRY__DSC__PROJECT_ID, project_id.to_string());
113    }
114    match &dsc.transaction {
115        // To match the behaviour of the `count_per_root` metric, which had its tags
116        // removed if they were over the limit.
117        Some(tx) if tx.len() <= config.max_tag_value_length => {
118            data.insert_value(SENTRY__DSC__TRANSACTION, tx.clone())
119        }
120        // Keep an empty value around for the DS job
121        Some(_) => data.insert_value(SENTRY__DSC__TRANSACTION, "".to_owned()),
122        None => drop(data.remove(SENTRY__DSC__TRANSACTION)),
123    }
124}