1use 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 let mut data = trace
28 .map(|c| c.data.clone().map_value(SpanData::from))
29 .unwrap_or_default();
30
31 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 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_input_tokens_cache_write: ~,
164 gen_ai_usage_input_tokens_cache_miss: ~,
165 gen_ai_usage_output_tokens: ~,
166 gen_ai_usage_output_tokens_reasoning: ~,
167 gen_ai_usage_output_tokens_prediction_accepted: ~,
168 gen_ai_usage_output_tokens_prediction_rejected: ~,
169 gen_ai_response_model: ~,
170 gen_ai_request_model: ~,
171 gen_ai_cost_total_tokens: ~,
172 gen_ai_cost_input_tokens: ~,
173 gen_ai_cost_output_tokens: ~,
174 gen_ai_prompt: ~,
175 gen_ai_request_messages: ~,
176 gen_ai_tool_input: ~,
177 gen_ai_tool_output: ~,
178 gen_ai_response_tool_calls: ~,
179 gen_ai_response_text: ~,
180 gen_ai_response_object: ~,
181 gen_ai_response_streaming: ~,
182 gen_ai_response_tokens_per_second: ~,
183 gen_ai_request_available_tools: ~,
184 gen_ai_request_frequency_penalty: ~,
185 gen_ai_request_presence_penalty: ~,
186 gen_ai_request_seed: ~,
187 gen_ai_request_temperature: ~,
188 gen_ai_request_top_k: ~,
189 gen_ai_request_top_p: ~,
190 gen_ai_response_finish_reason: ~,
191 gen_ai_response_id: ~,
192 gen_ai_system: ~,
193 gen_ai_tool_name: ~,
194 gen_ai_operation_name: ~,
195 gen_ai_operation_type: ~,
196 mcp_prompt_result: ~,
197 mcp_tool_result_content: ~,
198 browser_name: "Chrome",
199 code_filepath: ~,
200 code_lineno: ~,
201 code_function: ~,
202 code_namespace: ~,
203 db_operation: ~,
204 db_system: ~,
205 db_collection_name: ~,
206 environment: "prod",
207 release: LenientString(
208 "myapp@1.0.0",
209 ),
210 http_decoded_response_content_length: ~,
211 http_request_method: ~,
212 http_response_content_length: ~,
213 http_response_transfer_size: ~,
214 resource_render_blocking_status: ~,
215 server_address: ~,
216 cache_hit: ~,
217 cache_key: ~,
218 cache_item_size: ~,
219 http_response_status_code: ~,
220 thread_name: ~,
221 thread_id: ~,
222 segment_name: "my 1st transaction",
223 ui_component_name: ~,
224 url_scheme: ~,
225 user: ~,
226 user_email: ~,
227 user_full_name: ~,
228 user_geo_country_code: ~,
229 user_geo_city: ~,
230 user_geo_subdivision: ~,
231 user_geo_region: ~,
232 user_hash: ~,
233 user_id: ~,
234 user_name: ~,
235 user_roles: ~,
236 exclusive_time: ~,
237 profile_id: ~,
238 replay_id: ~,
239 sdk_name: "sentry.php",
240 sdk_version: "1.2.3",
241 frames_slow: ~,
242 frames_frozen: ~,
243 frames_total: ~,
244 frames_delay: ~,
245 messaging_destination_name: ~,
246 messaging_message_retry_count: ~,
247 messaging_message_receive_latency: ~,
248 messaging_message_body_size: ~,
249 messaging_message_id: ~,
250 messaging_operation_name: ~,
251 messaging_operation_type: ~,
252 user_agent_original: ~,
253 url_full: ~,
254 client_address: ~,
255 route: ~,
256 previous_route: ~,
257 lcp_element: ~,
258 lcp_size: ~,
259 lcp_id: ~,
260 lcp_url: ~,
261 span_name: ~,
262 other: {
263 "custom_attribute": I64(
264 42,
265 ),
266 },
267 },
268 links: [
269 SpanLink {
270 trace_id: TraceId("4c79f60c11214eb38604f4ae0781bfb2"),
271 span_id: SpanId("fa90fdead5f74052"),
272 sampled: true,
273 attributes: {
274 "sentry.link.type": String(
275 "previous_trace",
276 ),
277 },
278 other: {},
279 },
280 ],
281 sentry_tags: ~,
282 received: ~,
283 measurements: Measurements(
284 {
285 "memory": Measurement {
286 value: 9001.0,
287 unit: Information(
288 Byte,
289 ),
290 },
291 },
292 ),
293 platform: "php",
294 was_transaction: true,
295 kind: ~,
296 performance_issues_spans: ~,
297 other: {},
298 }
299 "###);
300 }
301}