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