relay_profiling/
extract_from_transaction.rs

1use std::collections::BTreeMap;
2
3use chrono::SecondsFormat;
4
5use relay_event_schema::protocol::{AppContext, AsPair, Event, SpanStatus, TraceContext};
6
7pub fn extract_transaction_metadata(event: &Event) -> BTreeMap<String, String> {
8    let mut tags = BTreeMap::new();
9
10    if let Some(release) = event.release.as_str() {
11        tags.insert("release".to_owned(), release.to_owned());
12    }
13    if let Some(dist) = event.dist.as_str() {
14        tags.insert("dist".to_owned(), dist.to_owned());
15    }
16    if let Some(environment) = event.environment.as_str() {
17        tags.insert("environment".to_owned(), environment.to_owned());
18    }
19
20    if let Some(transaction) = event.transaction.as_str() {
21        tags.insert("transaction".to_owned(), transaction.to_owned());
22    }
23
24    if let Some(trace_context) = event.context::<TraceContext>() {
25        let status = extract_transaction_status(trace_context);
26        tags.insert("transaction.status".to_owned(), status.to_string());
27
28        if let Some(op) = trace_context.op.value() {
29            tags.insert("transaction.op".to_owned(), op.to_owned());
30        }
31
32        if let Some(segment_id) = trace_context.span_id.value() {
33            tags.insert("segment_id".to_owned(), segment_id.to_string());
34        }
35    }
36
37    if let Some(http_method) = extract_http_method(event) {
38        tags.insert("http.method".to_owned(), http_method);
39    }
40
41    if let Some(timestamp) = event.start_timestamp.value() {
42        tags.insert(
43            "transaction.start".to_owned(),
44            timestamp
45                .into_inner()
46                .to_rfc3339_opts(SecondsFormat::Nanos, false),
47        );
48    }
49
50    if let Some(timestamp) = event.timestamp.value() {
51        tags.insert(
52            "transaction.end".to_owned(),
53            timestamp
54                .into_inner()
55                .to_rfc3339_opts(SecondsFormat::Nanos, false),
56        );
57    }
58
59    if let Some(app_context) = event.context::<AppContext>() {
60        if let Some(app_identifier) = app_context.app_identifier.value() {
61            tags.insert("app.identifier".to_owned(), app_identifier.to_owned());
62        }
63    }
64
65    if let Some(client_sdk) = event.client_sdk.value() {
66        if let Some(sdk_name) = client_sdk.name.value() {
67            tags.insert("client_sdk.name".to_owned(), sdk_name.to_owned());
68        }
69        if let Some(sdk_version) = client_sdk.version.value() {
70            tags.insert("client_sdk.version".to_owned(), sdk_version.to_owned());
71        }
72    }
73
74    tags
75}
76
77pub fn extract_transaction_tags(event: &Event) -> BTreeMap<String, String> {
78    let mut tags = BTreeMap::new();
79
80    // XXX(slow): event tags are a flat array
81    if let Some(event_tags) = event.tags.value() {
82        for tag_entry in &**event_tags {
83            if let Some(entry) = tag_entry.value() {
84                let (key, value) = entry.as_pair();
85                if let (Some(key), Some(value)) = (key.as_str(), value.as_str()) {
86                    tags.insert(key.to_owned(), value.to_owned());
87                }
88            }
89        }
90    }
91
92    tags
93}
94
95/// Extract transaction status, defaulting to [`SpanStatus::Unknown`].
96///
97/// Must be consistent with `process_trace_context` in `relay_event_normalization`.
98fn extract_transaction_status(trace_context: &TraceContext) -> SpanStatus {
99    *trace_context.status.value().unwrap_or(&SpanStatus::Unknown)
100}
101
102/// Extract HTTP method
103/// See <https://github.com/getsentry/snuba/blob/2e038c13a50735d58cc9397a29155ab5422a62e5/snuba/datasets/errors_processor.py#L64-L67>.
104fn extract_http_method(transaction: &Event) -> Option<String> {
105    let request = transaction.request.value()?;
106    let method = request.method.value()?;
107    Some(method.clone())
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use relay_protocol::FromValue;
114
115    #[test]
116    fn test_extract_transaction_metadata() {
117        let event = Event::from_value(
118            serde_json::json!({
119                "release": "myrelease",
120                "dist": "mydist",
121                "environment": "myenvironment",
122                "transaction": "mytransaction",
123                "contexts": {
124                    "app": {
125                        "app_identifier": "io.sentry.myexample",
126                    },
127                    "trace": {
128                        "status": "ok",
129                        "op": "myop",
130                    },
131                },
132                "request": {
133                    "method": "GET",
134                },
135                "timestamp": "2011-05-02T17:41:36Z",
136                "start_timestamp": "2011-05-02T17:40:36Z",
137                "sdk": {
138                    "name": "sentry.python",
139                    "version": "2.10.7",
140                },
141            })
142            .into(),
143        );
144
145        let metadata = extract_transaction_metadata(&event.0.unwrap());
146        insta::assert_debug_snapshot!(metadata, @r###"
147        {
148            "app.identifier": "io.sentry.myexample",
149            "client_sdk.name": "sentry.python",
150            "client_sdk.version": "2.10.7",
151            "dist": "mydist",
152            "environment": "myenvironment",
153            "http.method": "GET",
154            "release": "myrelease",
155            "transaction": "mytransaction",
156            "transaction.end": "2011-05-02T17:41:36.000000000+00:00",
157            "transaction.op": "myop",
158            "transaction.start": "2011-05-02T17:40:36.000000000+00:00",
159            "transaction.status": "ok",
160        }
161        "###);
162    }
163}