1#[cfg(feature = "gen-tonic-messages")]
2pub(crate) fn build_span_flags(parent_span_is_remote: bool, base_flags: u32) -> u32 {
5 use crate::proto::tonic::trace::v1::SpanFlags;
6 let mut flags = base_flags;
7 flags |= SpanFlags::ContextHasIsRemoteMask as u32;
8 if parent_span_is_remote {
9 flags |= SpanFlags::ContextIsRemoteMask as u32;
10 }
11 flags
12}
13
14#[cfg(feature = "gen-tonic-messages")]
15pub mod tonic {
16 use crate::proto::tonic::resource::v1::Resource;
17 use crate::proto::tonic::trace::v1::{span, status, ResourceSpans, ScopeSpans, Span, Status};
18 use crate::transform::common::{
19 to_nanos,
20 tonic::{Attributes, ResourceAttributesWithSchema},
21 };
22 use opentelemetry::trace;
23 use opentelemetry::trace::{Link, SpanId, SpanKind};
24 use opentelemetry_sdk::trace::SpanData;
25 use std::collections::HashMap;
26
27 impl From<SpanKind> for span::SpanKind {
28 fn from(span_kind: SpanKind) -> Self {
29 match span_kind {
30 SpanKind::Client => span::SpanKind::Client,
31 SpanKind::Consumer => span::SpanKind::Consumer,
32 SpanKind::Internal => span::SpanKind::Internal,
33 SpanKind::Producer => span::SpanKind::Producer,
34 SpanKind::Server => span::SpanKind::Server,
35 }
36 }
37 }
38
39 impl From<&trace::Status> for status::StatusCode {
40 fn from(status: &trace::Status) -> Self {
41 match status {
42 trace::Status::Ok => status::StatusCode::Ok,
43 trace::Status::Unset => status::StatusCode::Unset,
44 trace::Status::Error { .. } => status::StatusCode::Error,
45 }
46 }
47 }
48
49 impl From<Link> for span::Link {
50 fn from(link: Link) -> Self {
51 span::Link {
52 trace_id: link.span_context.trace_id().to_bytes().to_vec(),
53 span_id: link.span_context.span_id().to_bytes().to_vec(),
54 trace_state: link.span_context.trace_state().header(),
55 attributes: Attributes::from(link.attributes).0,
56 dropped_attributes_count: link.dropped_attributes_count,
57 flags: super::build_span_flags(
58 link.span_context.is_remote(),
59 link.span_context.trace_flags().to_u8() as u32,
60 ),
61 }
62 }
63 }
64 impl From<opentelemetry_sdk::trace::SpanData> for Span {
65 fn from(source_span: opentelemetry_sdk::trace::SpanData) -> Self {
66 let span_kind: span::SpanKind = source_span.span_kind.into();
67 Span {
68 trace_id: source_span.span_context.trace_id().to_bytes().to_vec(),
69 span_id: source_span.span_context.span_id().to_bytes().to_vec(),
70 trace_state: source_span.span_context.trace_state().header(),
71 parent_span_id: {
72 if source_span.parent_span_id != SpanId::INVALID {
73 source_span.parent_span_id.to_bytes().to_vec()
74 } else {
75 vec![]
76 }
77 },
78 flags: super::build_span_flags(
79 source_span.parent_span_is_remote,
80 source_span.span_context.trace_flags().to_u8() as u32,
81 ),
82 name: source_span.name.into_owned(),
83 kind: span_kind as i32,
84 start_time_unix_nano: to_nanos(source_span.start_time),
85 end_time_unix_nano: to_nanos(source_span.end_time),
86 dropped_attributes_count: source_span.dropped_attributes_count,
87 attributes: Attributes::from(source_span.attributes).0,
88 dropped_events_count: source_span.events.dropped_count,
89 events: source_span
90 .events
91 .into_iter()
92 .map(|event| span::Event {
93 time_unix_nano: to_nanos(event.timestamp),
94 name: event.name.into(),
95 attributes: Attributes::from(event.attributes).0,
96 dropped_attributes_count: event.dropped_attributes_count,
97 })
98 .collect(),
99 dropped_links_count: source_span.links.dropped_count,
100 links: source_span.links.into_iter().map(Into::into).collect(),
101 status: Some(Status {
102 code: status::StatusCode::from(&source_span.status).into(),
103 message: match source_span.status {
104 trace::Status::Error { description } => description.to_string(),
105 _ => Default::default(),
106 },
107 }),
108 }
109 }
110 }
111
112 impl ResourceSpans {
113 pub fn new(source_span: SpanData, resource: &ResourceAttributesWithSchema) -> Self {
114 let span_kind: span::SpanKind = source_span.span_kind.into();
115 ResourceSpans {
116 resource: Some(Resource {
117 attributes: resource.attributes.0.clone(),
118 dropped_attributes_count: 0,
119 entity_refs: vec![],
120 }),
121 schema_url: resource.schema_url.clone().unwrap_or_default(),
122 scope_spans: vec![ScopeSpans {
123 schema_url: source_span
124 .instrumentation_scope
125 .schema_url()
126 .map(ToOwned::to_owned)
127 .unwrap_or_default(),
128 scope: Some((source_span.instrumentation_scope, None).into()),
129 spans: vec![Span {
130 trace_id: source_span.span_context.trace_id().to_bytes().to_vec(),
131 span_id: source_span.span_context.span_id().to_bytes().to_vec(),
132 trace_state: source_span.span_context.trace_state().header(),
133 parent_span_id: {
134 if source_span.parent_span_id != SpanId::INVALID {
135 source_span.parent_span_id.to_bytes().to_vec()
136 } else {
137 vec![]
138 }
139 },
140 flags: super::build_span_flags(
141 source_span.parent_span_is_remote,
142 source_span.span_context.trace_flags().to_u8() as u32,
143 ),
144 name: source_span.name.into_owned(),
145 kind: span_kind as i32,
146 start_time_unix_nano: to_nanos(source_span.start_time),
147 end_time_unix_nano: to_nanos(source_span.end_time),
148 dropped_attributes_count: source_span.dropped_attributes_count,
149 attributes: Attributes::from(source_span.attributes).0,
150 dropped_events_count: source_span.events.dropped_count,
151 events: source_span
152 .events
153 .into_iter()
154 .map(|event| span::Event {
155 time_unix_nano: to_nanos(event.timestamp),
156 name: event.name.into(),
157 attributes: Attributes::from(event.attributes).0,
158 dropped_attributes_count: event.dropped_attributes_count,
159 })
160 .collect(),
161 dropped_links_count: source_span.links.dropped_count,
162 links: source_span.links.into_iter().map(Into::into).collect(),
163 status: Some(Status {
164 code: status::StatusCode::from(&source_span.status).into(),
165 message: match source_span.status {
166 trace::Status::Error { description } => description.to_string(),
167 _ => Default::default(),
168 },
169 }),
170 }],
171 }],
172 }
173 }
174 }
175
176 pub fn group_spans_by_resource_and_scope(
177 spans: Vec<SpanData>,
178 resource: &ResourceAttributesWithSchema,
179 ) -> Vec<ResourceSpans> {
180 let scope_map = spans.iter().fold(
182 HashMap::new(),
183 |mut scope_map: HashMap<&opentelemetry::InstrumentationScope, Vec<&SpanData>>, span| {
184 let instrumentation = &span.instrumentation_scope;
185 scope_map.entry(instrumentation).or_default().push(span);
186 scope_map
187 },
188 );
189
190 let scope_spans = scope_map
192 .into_iter()
193 .map(|(instrumentation, span_records)| ScopeSpans {
194 scope: Some((instrumentation, None).into()),
195 schema_url: instrumentation
196 .schema_url()
197 .map(ToOwned::to_owned)
198 .unwrap_or_default(),
199 spans: span_records
200 .into_iter()
201 .map(|span_data| span_data.clone().into())
202 .collect(),
203 })
204 .collect();
205
206 vec![ResourceSpans {
208 resource: Some(Resource {
209 attributes: resource.attributes.0.clone(),
210 dropped_attributes_count: 0,
211 entity_refs: vec![],
212 }),
213 scope_spans,
214 schema_url: resource.schema_url.clone().unwrap_or_default(),
215 }]
216 }
217}
218
219#[cfg(all(test, feature = "gen-tonic-messages"))]
220mod span_flags_tests {
221 use crate::proto::tonic::trace::v1::{Span, SpanFlags};
222 use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
223 use opentelemetry::InstrumentationScope;
224 use opentelemetry_sdk::trace::SpanData;
225 use std::borrow::Cow;
226
227 #[test]
228 fn test_build_span_flags_local_parent() {
229 let flags = super::build_span_flags(false, 0); assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); }
232
233 #[test]
234 fn test_build_span_flags_remote_parent() {
235 let flags = super::build_span_flags(true, 0); assert_eq!(
237 flags,
238 (SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
239 ); }
241
242 #[test]
243 fn test_build_span_flags_no_parent() {
244 let flags = super::build_span_flags(false, 0); assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); }
247
248 #[test]
249 fn test_build_span_flags_preserves_base_flags() {
250 let flags = super::build_span_flags(false, 0x01); assert_eq!(flags, 0x01 | (SpanFlags::ContextHasIsRemoteMask as u32)); }
253
254 #[test]
255 fn test_span_transformation_with_flags() {
256 let span_data = SpanData {
257 span_context: SpanContext::new(
258 TraceId::from(789),
259 SpanId::from(101112),
260 TraceFlags::default(),
261 false,
262 TraceState::default(),
263 ),
264 parent_span_id: SpanId::from(456),
265 parent_span_is_remote: false,
266 span_kind: opentelemetry::trace::SpanKind::Internal,
267 name: Cow::Borrowed("test_span"),
268 start_time: std::time::SystemTime::now(),
269 end_time: std::time::SystemTime::now(),
270 attributes: vec![],
271 dropped_attributes_count: 0,
272 events: opentelemetry_sdk::trace::SpanEvents::default(),
273 links: opentelemetry_sdk::trace::SpanLinks::default(),
274 status: opentelemetry::trace::Status::Unset,
275 instrumentation_scope: InstrumentationScope::builder("test").build(),
276 };
277
278 let otlp_span: Span = span_data.into();
279 assert_eq!(otlp_span.flags, SpanFlags::ContextHasIsRemoteMask as u32); }
281
282 #[test]
283 fn test_span_transformation_with_remote_parent() {
284 let span_data = SpanData {
285 span_context: SpanContext::new(
286 TraceId::from(789),
287 SpanId::from(101112),
288 TraceFlags::default(),
289 false,
290 TraceState::default(),
291 ),
292 parent_span_id: SpanId::from(456),
293 parent_span_is_remote: true,
294 span_kind: opentelemetry::trace::SpanKind::Internal,
295 name: Cow::Borrowed("test_span"),
296 start_time: std::time::SystemTime::now(),
297 end_time: std::time::SystemTime::now(),
298 attributes: vec![],
299 dropped_attributes_count: 0,
300 events: opentelemetry_sdk::trace::SpanEvents::default(),
301 links: opentelemetry_sdk::trace::SpanLinks::default(),
302 status: opentelemetry::trace::Status::Unset,
303 instrumentation_scope: InstrumentationScope::builder("test").build(),
304 };
305
306 let otlp_span: Span = span_data.into();
307 assert_eq!(
308 otlp_span.flags,
309 (SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
310 ); }
312}
313
314#[cfg(test)]
315mod tests {
316 use crate::tonic::common::v1::any_value::Value;
317 use crate::transform::common::tonic::ResourceAttributesWithSchema;
318 use opentelemetry::time::now;
319 use opentelemetry::trace::{
320 SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId, TraceState,
321 };
322 use opentelemetry::InstrumentationScope;
323 use opentelemetry::KeyValue;
324 use opentelemetry_sdk::resource::Resource;
325 use opentelemetry_sdk::trace::SpanData;
326 use opentelemetry_sdk::trace::{SpanEvents, SpanLinks};
327 use std::borrow::Cow;
328 use std::time::Duration;
329
330 fn create_test_span_data(instrumentation_name: &'static str) -> SpanData {
331 let span_context = SpanContext::new(
332 TraceId::from(123),
333 SpanId::from(456),
334 TraceFlags::default(),
335 false,
336 TraceState::default(),
337 );
338
339 SpanData {
340 span_context,
341 parent_span_id: SpanId::from(0),
342 parent_span_is_remote: false,
343 span_kind: SpanKind::Internal,
344 name: Cow::Borrowed("test_span"),
345 start_time: now(),
346 end_time: now() + Duration::from_secs(1),
347 attributes: vec![KeyValue::new("key", "value")],
348 dropped_attributes_count: 0,
349 events: SpanEvents::default(),
350 links: SpanLinks::default(),
351 status: Status::Unset,
352 instrumentation_scope: InstrumentationScope::builder(instrumentation_name).build(),
353 }
354 }
355
356 #[test]
357 fn test_group_spans_by_resource_and_scope_single_scope() {
358 let resource = Resource::builder_empty()
359 .with_attribute(KeyValue::new("resource_key", "resource_value"))
360 .build();
361 let span_data = create_test_span_data("lib1");
362
363 let spans = vec![span_data.clone()];
364 let resource: ResourceAttributesWithSchema = (&resource).into(); let grouped_spans =
367 crate::transform::trace::tonic::group_spans_by_resource_and_scope(spans, &resource);
368
369 assert_eq!(grouped_spans.len(), 1);
370
371 let resource_spans = &grouped_spans[0];
372 assert_eq!(
373 resource_spans.resource.as_ref().unwrap().attributes.len(),
374 1
375 );
376 assert_eq!(
377 resource_spans.resource.as_ref().unwrap().attributes[0].key,
378 "resource_key"
379 );
380 assert_eq!(
381 resource_spans.resource.as_ref().unwrap().attributes[0]
382 .value
383 .clone()
384 .unwrap()
385 .value
386 .unwrap(),
387 Value::StringValue("resource_value".to_string())
388 );
389
390 let scope_spans = &resource_spans.scope_spans;
391 assert_eq!(scope_spans.len(), 1);
392
393 let scope_span = &scope_spans[0];
394 assert_eq!(scope_span.scope.as_ref().unwrap().name, "lib1");
395 assert_eq!(scope_span.spans.len(), 1);
396
397 assert_eq!(
398 scope_span.spans[0].trace_id,
399 span_data.span_context.trace_id().to_bytes().to_vec()
400 );
401 }
402
403 #[test]
404 fn test_group_spans_by_resource_and_scope_multiple_scopes() {
405 let resource = Resource::builder_empty()
406 .with_attribute(KeyValue::new("resource_key", "resource_value"))
407 .build();
408 let span_data1 = create_test_span_data("lib1");
409 let span_data2 = create_test_span_data("lib1");
410 let span_data3 = create_test_span_data("lib2");
411
412 let spans = vec![span_data1.clone(), span_data2.clone(), span_data3.clone()];
413 let resource: ResourceAttributesWithSchema = (&resource).into(); let grouped_spans =
416 crate::transform::trace::tonic::group_spans_by_resource_and_scope(spans, &resource);
417
418 assert_eq!(grouped_spans.len(), 1);
419
420 let resource_spans = &grouped_spans[0];
421 assert_eq!(
422 resource_spans.resource.as_ref().unwrap().attributes.len(),
423 1
424 );
425 assert_eq!(
426 resource_spans.resource.as_ref().unwrap().attributes[0].key,
427 "resource_key"
428 );
429 assert_eq!(
430 resource_spans.resource.as_ref().unwrap().attributes[0]
431 .value
432 .clone()
433 .unwrap()
434 .value
435 .unwrap(),
436 Value::StringValue("resource_value".to_string())
437 );
438
439 let scope_spans = &resource_spans.scope_spans;
440 assert_eq!(scope_spans.len(), 2);
441
442 let mut lib1_scope_span = None;
444 let mut lib2_scope_span = None;
445
446 for scope_span in scope_spans {
447 match scope_span.scope.as_ref().unwrap().name.as_str() {
448 "lib1" => lib1_scope_span = Some(scope_span),
449 "lib2" => lib2_scope_span = Some(scope_span),
450 _ => {}
451 }
452 }
453
454 let lib1_scope_span = lib1_scope_span.expect("lib1 scope span not found");
455 let lib2_scope_span = lib2_scope_span.expect("lib2 scope span not found");
456
457 assert_eq!(lib1_scope_span.scope.as_ref().unwrap().name, "lib1");
458 assert_eq!(lib2_scope_span.scope.as_ref().unwrap().name, "lib2");
459
460 assert_eq!(lib1_scope_span.spans.len(), 2);
461 assert_eq!(lib2_scope_span.spans.len(), 1);
462
463 assert_eq!(
464 lib1_scope_span.spans[0].trace_id,
465 span_data1.span_context.trace_id().to_bytes().to_vec()
466 );
467 assert_eq!(
468 lib1_scope_span.spans[1].trace_id,
469 span_data2.span_context.trace_id().to_bytes().to_vec()
470 );
471 assert_eq!(
472 lib2_scope_span.spans[0].trace_id,
473 span_data3.span_context.trace_id().to_bytes().to_vec()
474 );
475 }
476
477 #[test]
478 fn test_scope_spans_uses_instrumentation_schema_url_not_resource() {
479 let resource = Resource::builder_empty()
480 .with_schema_url(vec![], "http://resource-schema")
481 .build();
482
483 let instrumentation_scope = InstrumentationScope::builder("test-lib")
484 .with_schema_url("http://instrumentation-schema")
485 .build();
486
487 let span_data = SpanData {
488 span_context: SpanContext::new(
489 TraceId::from(123),
490 SpanId::from(456),
491 TraceFlags::default(),
492 false,
493 TraceState::default(),
494 ),
495 parent_span_id: SpanId::from(0),
496 parent_span_is_remote: false,
497 span_kind: SpanKind::Internal,
498 name: Cow::Borrowed("test_span"),
499 start_time: now(),
500 end_time: now() + Duration::from_secs(1),
501 attributes: vec![],
502 dropped_attributes_count: 0,
503 events: SpanEvents::default(),
504 links: SpanLinks::default(),
505 status: Status::Unset,
506 instrumentation_scope,
507 };
508
509 let resource: ResourceAttributesWithSchema = (&resource).into();
510 let grouped_spans = crate::transform::trace::tonic::group_spans_by_resource_and_scope(
511 vec![span_data],
512 &resource,
513 );
514
515 assert_eq!(grouped_spans.len(), 1);
516 let resource_spans = &grouped_spans[0];
517 assert_eq!(resource_spans.schema_url, "http://resource-schema");
518
519 let scope_spans = &resource_spans.scope_spans;
520 assert_eq!(scope_spans.len(), 1);
521 assert_eq!(scope_spans[0].schema_url, "http://instrumentation-schema");
522 }
523}