1use super::json_types::{
7 self, AnyValue, ExportTraceServiceRequest, InstrumentationScope, KeyValue, OtlpSpan,
8 OtlpSpanEvent, OtlpSpanLink, Resource, ResourceSpans, ScopeSpans, Status,
9};
10use super::OtlpResourceInfo;
11use crate::span::v04::{Span, SpanEvent, SpanLink};
12use crate::span::TraceData;
13use std::borrow::Borrow;
14
15const MAX_ATTRIBUTES_PER_SPAN: usize = 128;
17
18pub fn map_traces_to_otlp<T: TraceData>(
26 trace_chunks: Vec<Vec<Span<T>>>,
27 resource_info: &OtlpResourceInfo,
28) -> ExportTraceServiceRequest {
29 let resource = build_resource(resource_info);
30 let mut all_spans: Vec<OtlpSpan> = Vec::new();
31 for chunk in &trace_chunks {
32 for span in chunk {
33 all_spans.push(map_span(span, &resource_info.service));
34 }
35 }
36 let scope_spans = ScopeSpans {
37 scope: Some(InstrumentationScope::default()),
38 spans: all_spans,
39 schema_url: None,
40 };
41 let resource_spans = ResourceSpans {
42 resource: Some(resource),
43 scope_spans: vec![scope_spans],
44 };
45 ExportTraceServiceRequest {
46 resource_spans: vec![resource_spans],
47 }
48}
49
50fn build_resource(resource_info: &OtlpResourceInfo) -> Resource {
51 let mut attributes: Vec<KeyValue> = Vec::new();
52 if !resource_info.service.is_empty() {
53 attributes.push(KeyValue {
54 key: "service.name".to_string(),
55 value: AnyValue::StringValue(resource_info.service.clone()),
56 });
57 }
58 if !resource_info.env.is_empty() {
59 attributes.push(KeyValue {
60 key: "deployment.environment.name".to_string(),
61 value: AnyValue::StringValue(resource_info.env.clone()),
62 });
63 }
64 if !resource_info.app_version.is_empty() {
65 attributes.push(KeyValue {
66 key: "service.version".to_string(),
67 value: AnyValue::StringValue(resource_info.app_version.clone()),
68 });
69 }
70 attributes.push(KeyValue {
71 key: "telemetry.sdk.name".to_string(),
72 value: AnyValue::StringValue("datadog".to_string()),
73 });
74 if !resource_info.language.is_empty() {
75 attributes.push(KeyValue {
76 key: "telemetry.sdk.language".to_string(),
77 value: AnyValue::StringValue(resource_info.language.clone()),
78 });
79 }
80 if !resource_info.tracer_version.is_empty() {
81 attributes.push(KeyValue {
82 key: "telemetry.sdk.version".to_string(),
83 value: AnyValue::StringValue(resource_info.tracer_version.clone()),
84 });
85 }
86 if !resource_info.runtime_id.is_empty() {
87 attributes.push(KeyValue {
88 key: "runtime-id".to_string(),
89 value: AnyValue::StringValue(resource_info.runtime_id.clone()),
90 });
91 }
92 Resource { attributes }
93}
94
95fn map_span<T: TraceData>(span: &Span<T>, resource_service: &str) -> OtlpSpan {
96 let trace_id_high: u128 = span
100 .meta
101 .get("_dd.p.tid")
102 .and_then(|v| u64::from_str_radix(v.borrow(), 16).ok())
103 .unwrap_or(0) as u128;
104 let trace_id_128 = (trace_id_high << 64) | span.trace_id;
105 let trace_id_hex = format!("{:032x}", trace_id_128);
106 let span_id_hex = format!("{:016x}", span.span_id);
107 let parent_span_id = if span.parent_id != 0 {
108 Some(format!("{:016x}", span.parent_id))
109 } else {
110 None
111 };
112 let start_nano = span.start;
113 let end_nano = span.start + span.duration;
114 let start_time_unix_nano = start_nano.to_string();
115 let end_time_unix_nano = end_nano.to_string();
116 let kind = span
119 .meta
120 .get("span.kind")
121 .map(|v| tag_to_otlp_kind(v.borrow()))
122 .unwrap_or_else(|| dd_type_to_otlp_kind(span.r#type.borrow()));
123 let (attributes, dropped_attributes_count) = map_attributes(span, resource_service);
124 let error_msg = span.meta.get("error.msg").map(|v| v.borrow().to_string());
125 let status = if span.error != 0 {
126 Status {
127 message: error_msg,
128 code: json_types::status_code::ERROR,
129 }
130 } else {
131 Status {
132 message: None,
133 code: json_types::status_code::UNSET,
134 }
135 };
136 let flags = span
138 .metrics
139 .get("_sampling_priority_v1")
140 .map(|p| if *p >= 1.0 { 1u32 } else { 0u32 });
141 let trace_state = span
142 .meta
143 .get("tracestate")
144 .map(|v| v.borrow().to_string())
145 .filter(|s| !s.is_empty());
146 let links = span.span_links.iter().map(map_span_link).collect();
147 let (events, dropped_events_count) = map_span_events(&span.span_events);
148 OtlpSpan {
149 trace_id: trace_id_hex,
150 span_id: span_id_hex,
151 parent_span_id,
152 trace_state,
153 name: span.resource.borrow().to_string(),
154 kind,
155 start_time_unix_nano,
156 end_time_unix_nano,
157 attributes,
158 status,
159 links,
160 events,
161 dropped_attributes_count: if dropped_attributes_count > 0 {
162 Some(dropped_attributes_count as u32)
163 } else {
164 None
165 },
166 dropped_events_count: if dropped_events_count > 0 {
167 Some(dropped_events_count as u32)
168 } else {
169 None
170 },
171 flags,
172 }
173}
174
175fn map_span_link<T: TraceData>(link: &SpanLink<T>) -> OtlpSpanLink {
176 let trace_id_128 = ((link.trace_id_high as u128) << 64) | (link.trace_id as u128);
177 let trace_id_hex = format!("{:032x}", trace_id_128);
178 let span_id_hex = format!("{:016x}", link.span_id);
179 let trace_state = if link.tracestate.borrow().is_empty() {
180 None
181 } else {
182 Some(link.tracestate.borrow().to_string())
183 };
184 let attributes: Vec<KeyValue> = link
185 .attributes
186 .iter()
187 .map(|(k, v)| KeyValue {
188 key: k.borrow().to_string(),
189 value: AnyValue::StringValue(v.borrow().to_string()),
190 })
191 .collect();
192 OtlpSpanLink {
193 trace_id: trace_id_hex,
194 span_id: span_id_hex,
195 trace_state,
196 attributes,
197 dropped_attributes_count: None,
198 }
199}
200
201fn map_span_events<T: TraceData>(events: &[SpanEvent<T>]) -> (Vec<OtlpSpanEvent>, usize) {
202 const MAX_EVENTS_PER_SPAN: usize = 128;
203 let mut otlp_events = Vec::with_capacity(events.len().min(MAX_EVENTS_PER_SPAN));
204 for ev in events.iter().take(MAX_EVENTS_PER_SPAN) {
205 let attributes: Vec<KeyValue> = ev
206 .attributes
207 .iter()
208 .map(|(k, v)| event_attr_to_key_value(k, v))
209 .collect();
210 otlp_events.push(OtlpSpanEvent {
211 time_unix_nano: ev.time_unix_nano.to_string(),
212 name: ev.name.borrow().to_string(),
213 attributes,
214 dropped_attributes_count: None,
215 });
216 }
217 let dropped = events.len().saturating_sub(otlp_events.len());
218 (otlp_events, dropped)
219}
220
221fn event_attr_to_key_value<T: TraceData>(
222 k: &T::Text,
223 v: &crate::span::v04::AttributeAnyValue<T>,
224) -> KeyValue {
225 use crate::span::v04::AttributeArrayValue;
226 let value = match v {
227 crate::span::v04::AttributeAnyValue::SingleValue(av) => match av {
228 AttributeArrayValue::String(s) => AnyValue::StringValue(s.borrow().to_string()),
229 AttributeArrayValue::Boolean(b) => AnyValue::BoolValue(*b),
230 AttributeArrayValue::Integer(i) => AnyValue::IntValue(*i),
231 AttributeArrayValue::Double(d) => AnyValue::DoubleValue(*d),
232 },
233 crate::span::v04::AttributeAnyValue::Array(items) => {
234 let values = items
235 .iter()
236 .map(|item| match item {
237 AttributeArrayValue::String(s) => AnyValue::StringValue(s.borrow().to_string()),
238 AttributeArrayValue::Boolean(b) => AnyValue::BoolValue(*b),
239 AttributeArrayValue::Integer(i) => AnyValue::IntValue(*i),
240 AttributeArrayValue::Double(d) => AnyValue::DoubleValue(*d),
241 })
242 .collect();
243 AnyValue::ArrayValue(crate::otlp_encoder::json_types::ArrayValue { values })
244 }
245 };
246 KeyValue {
247 key: k.borrow().to_string(),
248 value,
249 }
250}
251
252fn tag_to_otlp_kind(t: &str) -> i32 {
254 match t.to_lowercase().as_str() {
255 "server" => json_types::span_kind::SERVER,
256 "client" => json_types::span_kind::CLIENT,
257 "producer" => json_types::span_kind::PRODUCER,
258 "consumer" => json_types::span_kind::CONSUMER,
259 "internal" => json_types::span_kind::INTERNAL,
260 _ => json_types::span_kind::UNSPECIFIED,
261 }
262}
263
264fn dd_type_to_otlp_kind(t: &str) -> i32 {
266 match t.to_lowercase().as_str() {
267 "server" | "web" | "http" => json_types::span_kind::SERVER,
268 "client" => json_types::span_kind::CLIENT,
269 "producer" => json_types::span_kind::PRODUCER,
270 "consumer" => json_types::span_kind::CONSUMER,
271 _ => json_types::span_kind::INTERNAL,
272 }
273}
274
275fn map_attributes<T: TraceData>(span: &Span<T>, resource_service: &str) -> (Vec<KeyValue>, usize) {
276 let mut attrs: Vec<KeyValue> = Vec::new();
277 let span_service = span.service.borrow();
279 let has_per_span_service = !span_service.is_empty() && span_service != resource_service;
280 if has_per_span_service {
281 attrs.push(KeyValue {
282 key: "service.name".to_string(),
283 value: AnyValue::StringValue(span_service.to_string()),
284 });
285 }
286 let operation_name = span.name.borrow();
287 let has_operation_name = !operation_name.is_empty();
288 if has_operation_name {
289 attrs.push(KeyValue {
290 key: "operation.name".to_string(),
291 value: AnyValue::StringValue(operation_name.to_string()),
292 });
293 }
294 let span_type = span.r#type.borrow();
295 let has_span_type = !span_type.is_empty();
296 if has_span_type {
297 attrs.push(KeyValue {
298 key: "span.type".to_string(),
299 value: AnyValue::StringValue(span_type.to_string()),
300 });
301 }
302 let resource_name = span.resource.borrow();
303 let has_resource_name = !resource_name.is_empty();
304 if has_resource_name {
305 attrs.push(KeyValue {
306 key: "resource.name".to_string(),
307 value: AnyValue::StringValue(resource_name.to_string()),
308 });
309 }
310 for (k, v) in span.meta.iter() {
311 if attrs.len() >= MAX_ATTRIBUTES_PER_SPAN {
312 break;
313 }
314 attrs.push(KeyValue {
315 key: k.borrow().to_string(),
316 value: AnyValue::StringValue(v.borrow().to_string()),
317 });
318 }
319 for (k, v) in span.metrics.iter() {
320 if attrs.len() >= MAX_ATTRIBUTES_PER_SPAN {
321 break;
322 }
323 let value = if v.fract() == 0.0 && (*v >= i64::MIN as f64 && *v <= i64::MAX as f64) {
324 AnyValue::IntValue(*v as i64)
325 } else {
326 AnyValue::DoubleValue(*v)
327 };
328 attrs.push(KeyValue {
329 key: k.borrow().to_string(),
330 value,
331 });
332 }
333 for (k, v) in span.meta_struct.iter() {
334 if attrs.len() >= MAX_ATTRIBUTES_PER_SPAN {
335 break;
336 }
337 attrs.push(KeyValue {
338 key: k.borrow().to_string(),
339 value: AnyValue::BytesValue(v.borrow().to_vec()),
340 });
341 }
342 let total = (if has_per_span_service { 1 } else { 0 })
343 + (if has_operation_name { 1 } else { 0 })
344 + (if has_span_type { 1 } else { 0 })
345 + (if has_resource_name { 1 } else { 0 })
346 + span.meta.len()
347 + span.metrics.len()
348 + span.meta_struct.len();
349 let dropped = total.saturating_sub(attrs.len());
350 (attrs, dropped)
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::otlp_encoder::OtlpResourceInfo;
357 use crate::span::BytesData;
358
359 #[test]
360 fn test_trace_id_span_id_format() {
361 let resource_info = OtlpResourceInfo::default();
362 let span: Span<BytesData> = Span {
363 trace_id: 0xD269B633813FC60C_u128, span_id: 0xEEE19B7EC3C1B174,
365 parent_id: 0xEEE19B7EC3C1B173,
366 name: libdd_tinybytes::BytesString::from_static("test"),
367 service: libdd_tinybytes::BytesString::from_static("svc"),
368 resource: libdd_tinybytes::BytesString::from_static("res"),
369 r#type: libdd_tinybytes::BytesString::from_static("web"),
370 start: 1544712660000000000,
371 duration: 1000000000,
372 error: 0,
373 ..Default::default()
374 };
375 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
376 let rs = &req.resource_spans[0];
377 let otlp_span = &rs.scope_spans[0].spans[0];
378 assert_eq!(otlp_span.trace_id, "0000000000000000d269b633813fc60c");
379 assert_eq!(otlp_span.span_id, "eee19b7ec3c1b174");
380 assert_eq!(
381 otlp_span.parent_span_id.as_deref(),
382 Some("eee19b7ec3c1b173")
383 );
384 assert_eq!(otlp_span.kind, json_types::span_kind::SERVER);
385 assert_eq!(otlp_span.start_time_unix_nano, "1544712660000000000");
386 assert_eq!(otlp_span.end_time_unix_nano, "1544712661000000000");
387 assert_eq!(rs.scope_spans[0].scope.as_ref().unwrap().name, None);
388 }
389
390 #[test]
391 fn test_status_error_message_from_meta() {
392 let resource_info = OtlpResourceInfo::default();
393 let mut span: Span<BytesData> = Span {
394 trace_id: 1,
395 span_id: 2,
396 name: libdd_tinybytes::BytesString::from_static("err_span"),
397 start: 0,
398 duration: 1,
399 error: 1,
400 ..Default::default()
401 };
402 span.meta.insert(
403 libdd_tinybytes::BytesString::from_static("error.msg"),
404 libdd_tinybytes::BytesString::from_static("something broke"),
405 );
406 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
407 let otlp_span = &req.resource_spans[0].scope_spans[0].spans[0];
408 let status = &otlp_span.status;
409 assert_eq!(status.code, json_types::status_code::ERROR);
410 assert_eq!(status.message.as_deref(), Some("something broke"));
411 }
412
413 #[test]
414 fn test_metrics_as_int_or_double() {
415 let resource_info = OtlpResourceInfo::default();
416 let mut span: Span<BytesData> = Span {
417 trace_id: 1,
418 span_id: 2,
419 name: libdd_tinybytes::BytesString::from_static("m"),
420 start: 0,
421 duration: 1,
422 ..Default::default()
423 };
424 span.metrics
425 .insert(libdd_tinybytes::BytesString::from_static("count"), 42.0);
426 span.metrics.insert(
427 libdd_tinybytes::BytesString::from_static("rate"),
428 std::f64::consts::PI,
429 );
430 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
431 let json = serde_json::to_value(&req).unwrap();
432 let attrs = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"];
433 let count_kv = attrs
434 .as_array()
435 .unwrap()
436 .iter()
437 .find(|a| a["key"] == "count")
438 .unwrap();
439 assert_eq!(count_kv["value"]["intValue"], "42");
440 let rate_kv = attrs
441 .as_array()
442 .unwrap()
443 .iter()
444 .find(|a| a["key"] == "rate")
445 .unwrap();
446 let rate = rate_kv["value"]["doubleValue"].as_f64().unwrap();
447 assert!((rate - std::f64::consts::PI).abs() < 1e-9);
448 }
449
450 #[test]
451 fn test_128bit_trace_id_from_dd_p_tid() {
452 let resource_info = OtlpResourceInfo::default();
455 let mut span: Span<BytesData> = Span {
456 trace_id: 0xD269B633813FC60C_u128, span_id: 1,
458 name: libdd_tinybytes::BytesString::from_static("s"),
459 start: 0,
460 duration: 1,
461 ..Default::default()
462 };
463 span.meta.insert(
464 "_dd.p.tid".into(),
465 libdd_tinybytes::BytesString::from_static("5b8efff798038103"),
466 );
467 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
468 let otlp_span = &req.resource_spans[0].scope_spans[0].spans[0];
469 assert_eq!(otlp_span.trace_id, "5b8efff798038103d269b633813fc60c");
470 }
471
472 #[test]
473 fn test_128bit_trace_id_without_dd_p_tid() {
474 let resource_info = OtlpResourceInfo::default();
476 let span: Span<BytesData> = Span {
477 trace_id: 0xD269B633813FC60C_u128,
478 span_id: 1,
479 name: libdd_tinybytes::BytesString::from_static("s"),
480 start: 0,
481 duration: 1,
482 ..Default::default()
483 };
484 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
485 let otlp_span = &req.resource_spans[0].scope_spans[0].spans[0];
486 assert_eq!(otlp_span.trace_id, "0000000000000000d269b633813fc60c");
487 }
488
489 #[test]
490 fn test_tracestate_from_meta() {
491 let resource_info = OtlpResourceInfo::default();
492 let mut span: Span<BytesData> = Span {
493 trace_id: 1,
494 span_id: 2,
495 name: libdd_tinybytes::BytesString::from_static("s"),
496 start: 0,
497 duration: 1,
498 ..Default::default()
499 };
500 span.meta.insert(
501 "tracestate".into(),
502 libdd_tinybytes::BytesString::from_static("vendor1=abc,rojo=00f067"),
503 );
504 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
505 let otlp_span = &req.resource_spans[0].scope_spans[0].spans[0];
506 assert_eq!(
507 otlp_span.trace_state.as_deref(),
508 Some("vendor1=abc,rojo=00f067")
509 );
510 }
511
512 #[test]
513 fn test_meta_struct_as_bytes_value() {
514 use libdd_tinybytes::Bytes;
515 let resource_info = OtlpResourceInfo::default();
516 let mut span: Span<BytesData> = Span {
517 trace_id: 1,
518 span_id: 2,
519 name: libdd_tinybytes::BytesString::from_static("s"),
520 start: 0,
521 duration: 1,
522 ..Default::default()
523 };
524 span.meta_struct
525 .insert("my_key".into(), Bytes::from(vec![1u8, 2, 3]));
526 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
527 let json = serde_json::to_value(&req).unwrap();
528 let attrs = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"];
529 let kv = attrs
530 .as_array()
531 .unwrap()
532 .iter()
533 .find(|a| a["key"] == "my_key")
534 .expect("my_key attribute not found");
535 assert_eq!(kv["value"]["bytesValue"], "AQID");
537 }
538
539 #[test]
540 fn test_operation_name_attribute() {
541 let resource_info = OtlpResourceInfo::default();
542 let span: Span<BytesData> = Span {
543 trace_id: 1,
544 span_id: 2,
545 name: libdd_tinybytes::BytesString::from_static("my.operation"),
546 start: 0,
547 duration: 1,
548 ..Default::default()
549 };
550 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
551 let json = serde_json::to_value(&req).unwrap();
552 let attrs = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"];
553 let kv = attrs
554 .as_array()
555 .unwrap()
556 .iter()
557 .find(|a| a["key"] == "operation.name")
558 .expect("operation.name attribute not found");
559 assert_eq!(kv["value"]["stringValue"], "my.operation");
560 }
561
562 #[test]
563 fn test_span_type_attribute() {
564 let resource_info = OtlpResourceInfo::default();
565 let span: Span<BytesData> = Span {
566 trace_id: 1,
567 span_id: 2,
568 name: libdd_tinybytes::BytesString::from_static("s"),
569 r#type: libdd_tinybytes::BytesString::from_static("grpc"),
570 start: 0,
571 duration: 1,
572 ..Default::default()
573 };
574 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
575 let json = serde_json::to_value(&req).unwrap();
576 let attrs = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"];
577 let kv = attrs
578 .as_array()
579 .unwrap()
580 .iter()
581 .find(|a| a["key"] == "span.type")
582 .expect("span.type attribute not found");
583 assert_eq!(kv["value"]["stringValue"], "grpc");
584 }
585
586 #[test]
587 fn test_resource_name_attribute() {
588 let resource_info = OtlpResourceInfo::default();
589 let span: Span<BytesData> = Span {
590 trace_id: 1,
591 span_id: 2,
592 name: libdd_tinybytes::BytesString::from_static("s"),
593 resource: libdd_tinybytes::BytesString::from_static("GET /api/users"),
594 start: 0,
595 duration: 1,
596 ..Default::default()
597 };
598 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
599 let json = serde_json::to_value(&req).unwrap();
600 let otlp_span = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0];
601 assert_eq!(otlp_span["name"], "GET /api/users");
603 let kv = otlp_span["attributes"]
605 .as_array()
606 .unwrap()
607 .iter()
608 .find(|a| a["key"] == "resource.name")
609 .expect("resource.name attribute not found");
610 assert_eq!(kv["value"]["stringValue"], "GET /api/users");
611 }
612
613 #[test]
614 fn test_empty_resource_name_not_emitted() {
615 let resource_info = OtlpResourceInfo::default();
619 let span: Span<BytesData> = Span {
620 trace_id: 1,
621 span_id: 2,
622 name: libdd_tinybytes::BytesString::from_static("s"),
623 start: 0,
625 duration: 1,
626 ..Default::default()
627 };
628 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
629 let json = serde_json::to_value(&req).unwrap();
630 let attrs = json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"]
631 .as_array()
632 .unwrap();
633 assert!(
634 !attrs.iter().any(|a| a["key"] == "resource.name"),
635 "resource.name should not be emitted when resource is empty"
636 );
637 }
638
639 #[test]
640 fn test_per_span_service_name_attribute() {
641 let resource_info = OtlpResourceInfo {
644 service: "resource-svc".to_string(),
645 ..Default::default()
646 };
647 let span: Span<BytesData> = Span {
648 trace_id: 1,
649 span_id: 2,
650 name: libdd_tinybytes::BytesString::from_static("s"),
651 service: libdd_tinybytes::BytesString::from_static("span-svc"),
652 start: 0,
653 duration: 1,
654 ..Default::default()
655 };
656 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
657 let json = serde_json::to_value(&req).unwrap();
658 let attrs = &json["resourceSpans"][0]["scopeSpans"][0]["spans"][0]["attributes"];
659 let kv = attrs
660 .as_array()
661 .unwrap()
662 .iter()
663 .find(|a| a["key"] == "service.name")
664 .expect("service.name attribute not found");
665 assert_eq!(kv["value"]["stringValue"], "span-svc");
666 }
667
668 #[test]
669 fn test_unsampled_span_flags_zero() {
670 let resource_info = OtlpResourceInfo::default();
672 let mut span: Span<BytesData> = Span {
673 trace_id: 1,
674 span_id: 2,
675 name: libdd_tinybytes::BytesString::from_static("s"),
676 start: 0,
677 duration: 1,
678 ..Default::default()
679 };
680 span.metrics.insert("_sampling_priority_v1".into(), 0.0);
681 let req = map_traces_to_otlp(vec![vec![span]], &resource_info);
682 let otlp_span = &req.resource_spans[0].scope_spans[0].spans[0];
683 assert_eq!(otlp_span.flags, Some(0));
684 }
685}