Skip to main content

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        // For root spans, the name should just be the transaction name.
35        span_data.span_name = transaction.clone();
36        span_data.release = release.clone();
37        span_data.environment = environment.clone();
38        if let Some(browser) = event.context::<BrowserContext>() {
39            span_data.browser_name = browser.name.clone();
40        }
41        if let Some(client_sdk) = event.client_sdk.value() {
42            span_data.sdk_name = client_sdk.name.clone();
43            span_data.sdk_version = client_sdk.version.clone();
44        }
45        if let Some(request) = event.request.value()
46            && let Some(query) = request.query_string.value()
47            && let Some(qs) = query.to_query_string()
48        {
49            span_data.http_query = format!("?{qs}").into();
50            span_data.url_query = qs.into();
51        }
52
53        Self {
54            timestamp: timestamp.clone(),
55            start_timestamp: start_timestamp.clone(),
56            exclusive_time: trace.map(|c| c.exclusive_time.clone()).unwrap_or_default(),
57            op: trace.map(|c| c.op.clone()).unwrap_or_default(),
58            span_id: trace.map(|c| c.span_id.clone()).unwrap_or_default(),
59            parent_span_id: trace.map(|c| c.parent_span_id.clone()).unwrap_or_default(),
60            trace_id: trace.map(|c| c.trace_id.clone()).unwrap_or_default(),
61            segment_id: trace.map(|c| c.span_id.clone()).unwrap_or_default(),
62            is_segment: true.into(),
63            // NB: Technically, this span may not be an actual remote span if this is a child
64            // transaction created within the same service as its parent. We still set `is_remote`
65            // as the best proxy to ensure this span will be detected as a segment by the spans
66            // pipeline.
67            is_remote: true.into(),
68            status: trace.map(|c| c.status.clone()).unwrap_or_default(),
69            description: transaction.clone(),
70            tags: tags.clone().map_value(|t| t.into()),
71            origin: trace.map(|c| c.origin.clone()).unwrap_or_default(),
72            profile_id: event
73                .context::<ProfileContext>()
74                .map(|c| c.profile_id.clone())
75                .unwrap_or_default(),
76            data,
77            links: trace.map(|c| c.links.clone()).unwrap_or_default(),
78            sentry_tags: Default::default(),
79            received: received.clone(),
80            measurements: measurements.clone(),
81            platform: platform.clone(),
82            was_transaction: true.into(),
83            kind: Default::default(),
84            performance_issues_spans: performance_issues_spans.clone(),
85            other: Default::default(),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use relay_protocol::Annotated;
93
94    use super::*;
95
96    #[test]
97    fn convert() {
98        let event = Annotated::<Event>::from_json(
99            r#"{
100                "type": "transaction",
101                "platform": "php",
102                "sdk": {"name": "sentry.php", "version": "1.2.3"},
103                "release": "myapp@1.0.0",
104                "environment": "prod",
105                "transaction": "my 1st transaction",
106                "contexts": {
107                    "browser": {"name": "Chrome"},
108                    "profile": {"profile_id": "a0aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"},
109                    "trace": {
110                        "trace_id": "4C79F60C11214EB38604F4AE0781BFB2",
111                        "span_id": "FA90FDEAD5F74052",
112                        "type": "trace",
113                        "origin": "manual",
114                        "op": "myop",
115                        "status": "ok",
116                        "exclusive_time": 123.4,
117                        "parent_span_id": "FA90FDEAD5F74051",
118                        "data": {
119                            "custom_attribute": 42
120                        },
121                        "links": [
122                            {
123                                "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
124                                "span_id": "fa90fdead5f74052",
125                                "sampled": true,
126                                "attributes": {
127                                    "sentry.link.type": "previous_trace"
128                                }
129                            }
130                        ]
131                    }
132                },
133                "request": {
134                    "url": "http://example.com/api/0/organizations/",
135                    "method": "GET",
136                    "query_string": "project=1&sort=date"
137                },
138                "measurements": {
139                    "memory": {
140                        "value": 9001.0,
141                        "unit": "byte"
142                    }
143                }
144            }"#,
145        )
146        .unwrap()
147        .into_value()
148        .unwrap();
149
150        let span_from_event = Span::from(&event);
151        insta::assert_debug_snapshot!(span_from_event, @r###"
152        Span {
153            timestamp: ~,
154            start_timestamp: ~,
155            exclusive_time: 123.4,
156            op: "myop",
157            span_id: SpanId("fa90fdead5f74052"),
158            parent_span_id: SpanId("fa90fdead5f74051"),
159            trace_id: TraceId("4c79f60c11214eb38604f4ae0781bfb2"),
160            segment_id: SpanId("fa90fdead5f74052"),
161            is_segment: true,
162            is_remote: true,
163            status: Ok,
164            description: "my 1st transaction",
165            tags: ~,
166            origin: "manual",
167            profile_id: EventId(
168                a0aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab,
169            ),
170            data: SpanData {
171                app_start_type: ~,
172                gen_ai_request_max_tokens: ~,
173                gen_ai_pipeline_name: ~,
174                gen_ai_usage_total_tokens: ~,
175                gen_ai_usage_input_tokens: ~,
176                gen_ai_usage_input_tokens_cached: ~,
177                gen_ai_usage_input_tokens_cache_write: ~,
178                gen_ai_usage_input_tokens_cache_miss: ~,
179                gen_ai_usage_output_tokens: ~,
180                gen_ai_usage_output_tokens_reasoning: ~,
181                gen_ai_usage_output_tokens_prediction_accepted: ~,
182                gen_ai_usage_output_tokens_prediction_rejected: ~,
183                gen_ai_response_model: ~,
184                gen_ai_request_model: ~,
185                gen_ai_context_window_size: ~,
186                gen_ai_context_utilization: ~,
187                gen_ai_cost_total_tokens: ~,
188                gen_ai_cost_input_tokens: ~,
189                gen_ai_cost_output_tokens: ~,
190                gen_ai_input_messages: ~,
191                gen_ai_tool_call_arguments: ~,
192                gen_ai_tool_call_result: ~,
193                gen_ai_output_messages: ~,
194                gen_ai_response_object: ~,
195                gen_ai_response_streaming: ~,
196                gen_ai_response_tokens_per_second: ~,
197                gen_ai_response_time_to_first_token: ~,
198                gen_ai_tool_definitions: ~,
199                gen_ai_request_frequency_penalty: ~,
200                gen_ai_request_presence_penalty: ~,
201                gen_ai_request_seed: ~,
202                gen_ai_request_temperature: ~,
203                gen_ai_request_top_k: ~,
204                gen_ai_request_top_p: ~,
205                gen_ai_response_finish_reasons: ~,
206                gen_ai_response_id: ~,
207                gen_ai_provider_name: ~,
208                gen_ai_system_instructions: ~,
209                gen_ai_tool_name: ~,
210                gen_ai_operation_name: ~,
211                gen_ai_operation_type: ~,
212                gen_ai_agent_name: ~,
213                gen_ai_function_id: ~,
214                mcp_prompt_result: ~,
215                mcp_tool_result_content: ~,
216                browser_name: "Chrome",
217                code_filepath: ~,
218                code_lineno: ~,
219                code_function: ~,
220                code_namespace: ~,
221                db_operation: ~,
222                db_system: ~,
223                db_collection_name: ~,
224                environment: "prod",
225                release: LenientString(
226                    "myapp@1.0.0",
227                ),
228                http_decoded_response_content_length: ~,
229                http_request_method: ~,
230                http_response_content_length: ~,
231                http_response_transfer_size: ~,
232                resource_render_blocking_status: ~,
233                server_address: ~,
234                cache_hit: ~,
235                cache_key: ~,
236                cache_item_size: ~,
237                http_response_status_code: ~,
238                thread_name: ~,
239                thread_id: ~,
240                segment_name: "my 1st transaction",
241                ui_component_name: ~,
242                url_scheme: ~,
243                user: ~,
244                user_email: ~,
245                user_full_name: ~,
246                user_geo_country_code: ~,
247                user_geo_city: ~,
248                user_geo_subdivision: ~,
249                user_geo_region: ~,
250                user_hash: ~,
251                user_id: ~,
252                user_name: ~,
253                user_roles: ~,
254                exclusive_time: ~,
255                profile_id: ~,
256                replay_id: ~,
257                sdk_name: "sentry.php",
258                sdk_version: "1.2.3",
259                frames_slow: ~,
260                frames_frozen: ~,
261                frames_total: ~,
262                frames_delay: ~,
263                messaging_destination_name: ~,
264                messaging_message_retry_count: ~,
265                messaging_message_receive_latency: ~,
266                messaging_message_body_size: ~,
267                messaging_message_id: ~,
268                messaging_operation_name: ~,
269                messaging_operation_type: ~,
270                user_agent_original: ~,
271                url_full: ~,
272                url_query: "project=1&sort=date",
273                http_query: "?project=1&sort=date",
274                client_address: ~,
275                route: ~,
276                previous_route: ~,
277                lcp_element: ~,
278                lcp_size: ~,
279                lcp_id: ~,
280                lcp_url: ~,
281                span_name: "my 1st transaction",
282                other: {
283                    "custom_attribute": I64(
284                        42,
285                    ),
286                },
287            },
288            links: [
289                SpanLink {
290                    trace_id: TraceId("4c79f60c11214eb38604f4ae0781bfb2"),
291                    span_id: SpanId("fa90fdead5f74052"),
292                    sampled: true,
293                    attributes: {
294                        "sentry.link.type": String(
295                            "previous_trace",
296                        ),
297                    },
298                    other: {},
299                },
300            ],
301            sentry_tags: ~,
302            received: ~,
303            measurements: Measurements(
304                {
305                    "memory": Measurement {
306                        value: 9001.0,
307                        unit: Information(
308                            Byte,
309                        ),
310                    },
311                },
312            ),
313            platform: "php",
314            was_transaction: true,
315            kind: ~,
316            performance_issues_spans: ~,
317            other: {},
318        }
319        "###);
320    }
321}