relay_event_schema/protocol/span/
convert.rs

1//! This module defines bidirectional field mappings between spans and transactions.
2
3use crate::protocol::{BrowserContext, Event, ProfileContext, Span, SpanData, TraceContext};
4
5impl From<&Event> for Span {
6    fn from(event: &Event) -> Self {
7        let Event {
8            transaction,
9
10            platform,
11            timestamp,
12            start_timestamp,
13            received,
14            release,
15            environment,
16            tags,
17
18            measurements,
19            _metrics,
20            performance_issues_spans,
21            ..
22        } = event;
23
24        let trace = event.context::<TraceContext>();
25
26        // Fill data from trace context:
27        let mut data = trace
28            .map(|c| c.data.clone().map_value(SpanData::from))
29            .unwrap_or_default();
30
31        // Overwrite specific fields:
32        let span_data = data.get_or_insert_with(Default::default);
33        span_data.segment_name = transaction.clone();
34        span_data.release = release.clone();
35        span_data.environment = environment.clone();
36        if let Some(browser) = event.context::<BrowserContext>() {
37            span_data.browser_name = browser.name.clone();
38        }
39        if let Some(client_sdk) = event.client_sdk.value() {
40            span_data.sdk_name = client_sdk.name.clone();
41            span_data.sdk_version = client_sdk.version.clone();
42        }
43
44        Self {
45            timestamp: timestamp.clone(),
46            start_timestamp: start_timestamp.clone(),
47            exclusive_time: trace.map(|c| c.exclusive_time.clone()).unwrap_or_default(),
48            op: trace.map(|c| c.op.clone()).unwrap_or_default(),
49            span_id: trace.map(|c| c.span_id.clone()).unwrap_or_default(),
50            parent_span_id: trace.map(|c| c.parent_span_id.clone()).unwrap_or_default(),
51            trace_id: trace.map(|c| c.trace_id.clone()).unwrap_or_default(),
52            segment_id: trace.map(|c| c.span_id.clone()).unwrap_or_default(),
53            is_segment: true.into(),
54            // NB: Technically, this span may not be an actual remote span if this is a child
55            // transaction created within the same service as its parent. We still set `is_remote`
56            // as the best proxy to ensure this span will be detected as a segment by the spans
57            // pipeline.
58            is_remote: true.into(),
59            status: trace.map(|c| c.status.clone()).unwrap_or_default(),
60            description: transaction.clone(),
61            tags: tags.clone().map_value(|t| t.into()),
62            origin: trace.map(|c| c.origin.clone()).unwrap_or_default(),
63            profile_id: event
64                .context::<ProfileContext>()
65                .map(|c| c.profile_id.clone())
66                .unwrap_or_default(),
67            data,
68            links: trace.map(|c| c.links.clone()).unwrap_or_default(),
69            sentry_tags: Default::default(),
70            received: received.clone(),
71            measurements: measurements.clone(),
72            platform: platform.clone(),
73            was_transaction: true.into(),
74            kind: Default::default(),
75            performance_issues_spans: performance_issues_spans.clone(),
76            other: Default::default(),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use relay_protocol::Annotated;
84
85    use super::*;
86
87    #[test]
88    fn convert() {
89        let event = Annotated::<Event>::from_json(
90            r#"{
91                "type": "transaction",
92                "platform": "php",
93                "sdk": {"name": "sentry.php", "version": "1.2.3"},
94                "release": "myapp@1.0.0",
95                "environment": "prod",
96                "transaction": "my 1st transaction",
97                "contexts": {
98                    "browser": {"name": "Chrome"},
99                    "profile": {"profile_id": "a0aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"},
100                    "trace": {
101                        "trace_id": "4C79F60C11214EB38604F4AE0781BFB2",
102                        "span_id": "FA90FDEAD5F74052",
103                        "type": "trace",
104                        "origin": "manual",
105                        "op": "myop",
106                        "status": "ok",
107                        "exclusive_time": 123.4,
108                        "parent_span_id": "FA90FDEAD5F74051",
109                        "data": {
110                            "custom_attribute": 42
111                        },
112                        "links": [
113                            {
114                                "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
115                                "span_id": "fa90fdead5f74052",
116                                "sampled": true,
117                                "attributes": {
118                                    "sentry.link.type": "previous_trace"
119                                }
120                            }
121                        ]
122                    }
123                },
124                "measurements": {
125                    "memory": {
126                        "value": 9001.0,
127                        "unit": "byte"
128                    }
129                }
130            }"#,
131        )
132        .unwrap()
133        .into_value()
134        .unwrap();
135
136        let span_from_event = Span::from(&event);
137        insta::assert_debug_snapshot!(span_from_event, @r###"
138        Span {
139            timestamp: ~,
140            start_timestamp: ~,
141            exclusive_time: 123.4,
142            op: "myop",
143            span_id: SpanId("fa90fdead5f74052"),
144            parent_span_id: SpanId("fa90fdead5f74051"),
145            trace_id: TraceId("4c79f60c11214eb38604f4ae0781bfb2"),
146            segment_id: SpanId("fa90fdead5f74052"),
147            is_segment: true,
148            is_remote: true,
149            status: Ok,
150            description: "my 1st transaction",
151            tags: ~,
152            origin: "manual",
153            profile_id: EventId(
154                a0aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab,
155            ),
156            data: SpanData {
157                app_start_type: ~,
158                gen_ai_request_max_tokens: ~,
159                gen_ai_pipeline_name: ~,
160                gen_ai_usage_total_tokens: ~,
161                gen_ai_usage_input_tokens: ~,
162                gen_ai_usage_input_tokens_cached: ~,
163                gen_ai_usage_output_tokens: ~,
164                gen_ai_usage_output_tokens_reasoning: ~,
165                gen_ai_response_model: ~,
166                gen_ai_request_model: ~,
167                gen_ai_usage_total_cost: ~,
168                gen_ai_cost_total_tokens: ~,
169                gen_ai_cost_input_tokens: ~,
170                gen_ai_cost_output_tokens: ~,
171                gen_ai_prompt: ~,
172                gen_ai_request_messages: ~,
173                gen_ai_tool_input: ~,
174                gen_ai_tool_output: ~,
175                gen_ai_response_tool_calls: ~,
176                gen_ai_response_text: ~,
177                gen_ai_response_object: ~,
178                gen_ai_response_streaming: ~,
179                gen_ai_response_tokens_per_second: ~,
180                gen_ai_request_available_tools: ~,
181                gen_ai_request_frequency_penalty: ~,
182                gen_ai_request_presence_penalty: ~,
183                gen_ai_request_seed: ~,
184                gen_ai_request_temperature: ~,
185                gen_ai_request_top_k: ~,
186                gen_ai_request_top_p: ~,
187                gen_ai_response_finish_reason: ~,
188                gen_ai_response_id: ~,
189                gen_ai_system: ~,
190                gen_ai_tool_name: ~,
191                gen_ai_operation_name: ~,
192                gen_ai_operation_type: ~,
193                browser_name: "Chrome",
194                code_filepath: ~,
195                code_lineno: ~,
196                code_function: ~,
197                code_namespace: ~,
198                db_operation: ~,
199                db_system: ~,
200                db_collection_name: ~,
201                environment: "prod",
202                release: LenientString(
203                    "myapp@1.0.0",
204                ),
205                http_decoded_response_content_length: ~,
206                http_request_method: ~,
207                http_response_content_length: ~,
208                http_response_transfer_size: ~,
209                resource_render_blocking_status: ~,
210                server_address: ~,
211                cache_hit: ~,
212                cache_key: ~,
213                cache_item_size: ~,
214                http_response_status_code: ~,
215                thread_name: ~,
216                thread_id: ~,
217                segment_name: "my 1st transaction",
218                ui_component_name: ~,
219                url_scheme: ~,
220                user: ~,
221                user_email: ~,
222                user_full_name: ~,
223                user_geo_country_code: ~,
224                user_geo_city: ~,
225                user_geo_subdivision: ~,
226                user_geo_region: ~,
227                user_hash: ~,
228                user_id: ~,
229                user_name: ~,
230                user_roles: ~,
231                exclusive_time: ~,
232                profile_id: ~,
233                replay_id: ~,
234                sdk_name: "sentry.php",
235                sdk_version: "1.2.3",
236                frames_slow: ~,
237                frames_frozen: ~,
238                frames_total: ~,
239                frames_delay: ~,
240                messaging_destination_name: ~,
241                messaging_message_retry_count: ~,
242                messaging_message_receive_latency: ~,
243                messaging_message_body_size: ~,
244                messaging_message_id: ~,
245                messaging_operation_name: ~,
246                messaging_operation_type: ~,
247                user_agent_original: ~,
248                url_full: ~,
249                client_address: ~,
250                route: ~,
251                previous_route: ~,
252                lcp_element: ~,
253                lcp_size: ~,
254                lcp_id: ~,
255                lcp_url: ~,
256                span_name: ~,
257                other: {
258                    "custom_attribute": I64(
259                        42,
260                    ),
261                },
262            },
263            links: [
264                SpanLink {
265                    trace_id: TraceId("4c79f60c11214eb38604f4ae0781bfb2"),
266                    span_id: SpanId("fa90fdead5f74052"),
267                    sampled: true,
268                    attributes: {
269                        "sentry.link.type": String(
270                            "previous_trace",
271                        ),
272                    },
273                    other: {},
274                },
275            ],
276            sentry_tags: ~,
277            received: ~,
278            measurements: Measurements(
279                {
280                    "memory": Measurement {
281                        value: 9001.0,
282                        unit: Information(
283                            Byte,
284                        ),
285                    },
286                },
287            ),
288            platform: "php",
289            was_transaction: true,
290            kind: ~,
291            performance_issues_spans: ~,
292            other: {},
293        }
294        "###);
295    }
296}