opentelemetry_datadog/
lib.rs

1//! # OpenTelemetry Datadog Exporter
2//!
3//! An OpenTelemetry datadog exporter implementation
4//!
5//! See the [Datadog Docs](https://docs.datadoghq.com/agent/) for information on how to run the datadog-agent
6//!
7//! ## Quirks
8//!
9//! There are currently some incompatibilities between Datadog and OpenTelemetry, and this manifests
10//! as minor quirks to this exporter.
11//!
12//! Firstly Datadog uses operation_name to describe what OpenTracing would call a component.
13//! Or to put it another way, in OpenTracing the operation / span name's are relatively
14//! granular and might be used to identify a specific endpoint. In datadog, however, they
15//! are less granular - it is expected in Datadog that a service will have single
16//! primary span name that is the root of all traces within that service, with an additional piece of
17//! metadata called resource_name providing granularity. See [here](https://docs.datadoghq.com/tracing/guide/configuring-primary-operation/)
18//!
19//! The Datadog Golang API takes the approach of using a `resource.name` OpenTelemetry attribute to set the
20//! resource_name. See [here](https://github.com/DataDog/dd-trace-go/blob/ecb0b805ef25b00888a2fb62d465a5aa95e7301e/ddtrace/opentracer/tracer.go#L10)
21//!
22//! Unfortunately, this breaks compatibility with other OpenTelemetry exporters which expect
23//! a more granular operation name - as per the OpenTracing specification.
24//!
25//! This exporter therefore takes a different approach of naming the span with the name of the
26//! tracing provider, and using the span name to set the resource_name. This should in most cases
27//! lead to the behaviour that users expect.
28//!
29//! Datadog additionally has a span_type string that alters the rendering of the spans in the web UI.
30//! This can be set as the `span.type` OpenTelemetry span attribute.
31//!
32//! For standard values see [here](https://github.com/DataDog/dd-trace-go/blob/ecb0b805ef25b00888a2fb62d465a5aa95e7301e/ddtrace/ext/app_types.go#L31).
33//!
34//! If the default mapping is not fit for your use case, you may change some of them by providing [`FieldMappingFn`]s in pipeline.
35//!
36//! ## Performance
37//!
38//! For optimal performance, a batch exporter is recommended as the simple exporter will export
39//! each span synchronously on drop. The default batch exporter uses a dedicated thread for exprt,
40//! but you can enable the async batch exporter with  [`rt-tokio`], [`rt-tokio-current-thread`]
41//! or [`rt-async-std`] features and specify a runtime on the pipeline to have a batch exporter
42//! configured for you automatically.
43//!
44//! ```toml
45//! [dependencies]
46//! opentelemetry_sdk = { version = "*", features = ["rt-tokio"] }
47//! opentelemetry-datadog = "*"
48//! ```
49//!
50//! ```no_run
51//! # fn main() -> Result<(), opentelemetry_sdk::trace::TraceError> {
52//! let provider = opentelemetry_datadog::new_pipeline()
53//!     .install_batch()?;
54//! # Ok(())
55//! # }
56//! ```
57//!
58//! [`rt-tokio`]: https://tokio.rs
59//! [`rt-tokio-current-thread`]: https://tokio.rs
60//! [`rt-async-std`]: https://async.rs
61//!
62//! ## Bring your own http client
63//!
64//! Users can choose appropriate http clients to align with their runtime.
65//!
66//! Based on the feature enabled. The default http client will be different. If user doesn't specific
67//! features or enabled `reqwest-blocking-client` feature. The blocking reqwest http client will be used as
68//! default client. If `reqwest-client` feature is enabled. The async reqwest http client will be used. If
69//! `surf-client` feature is enabled. The surf http client will be used.
70//!
71//! Note that async http clients may need specific runtime otherwise it will panic. User should make
72//! sure the http client is running in appropriate runime.
73//!
74//! Users can always use their own http clients by implementing `HttpClient` trait.
75//!
76//! ## Kitchen Sink Full Configuration
77//!
78//! Example showing how to override all configuration options. See the
79//! [`DatadogPipelineBuilder`] docs for details of each option.
80//!
81//! [`DatadogPipelineBuilder`]: struct.DatadogPipelineBuilder.html
82//!
83//! ```no_run
84//! use opentelemetry::{global, KeyValue, trace::{Tracer, TracerProvider}, InstrumentationScope};
85//! use opentelemetry_sdk::{trace::{self, RandomIdGenerator, Sampler}, Resource};
86//! use opentelemetry_datadog::{new_pipeline, ApiVersion, Error};
87//! use opentelemetry_http::{HttpClient, HttpError};
88//! use opentelemetry_semantic_conventions as semcov;
89//! use async_trait::async_trait;
90//! use bytes::Bytes;
91//! use futures_util::io::AsyncReadExt as _;
92//! use http::{Request, Response, StatusCode};
93//! use std::convert::TryInto as _;
94//! use http_body_util::BodyExt;
95//!
96//! // `reqwest` and `surf` are supported through features, if you prefer an
97//! // alternate http client you can add support by implementing `HttpClient` as
98//! // shown here.
99//! #[derive(Debug)]
100//! struct HyperClient(hyper_util::client::legacy::Client<hyperlocal::UnixConnector, http_body_util::Full<hyper::body::Bytes>>);
101//!
102//! #[async_trait]
103//! impl HttpClient for HyperClient {
104//!     async fn send(&self, request: Request<Vec<u8>>) -> Result<Response<Bytes>, HttpError> {
105//!         let (parts, body) = request.into_parts();
106//!         let request = hyper::Request::from_parts(parts, body.into());
107//!         let mut response = self.0.request(request).await?;
108//!         let status = response.status();
109//!
110//!         let body = response.into_body().collect().await?;
111//!         Ok(Response::builder()
112//!             .status(status)
113//!             .body(body.to_bytes().into())?)
114//!     }
115//!     async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
116//!         let (parts, body) = request.into_parts();
117//!         //TODO - Implement a proper client
118//!          Ok(Response::builder()
119//!             .status(StatusCode::OK)
120//!             .body(Bytes::from("Dummy response"))
121//!             .unwrap())
122//!     }
123//! }
124//!
125//!     let mut config = trace::Config::default();
126//!     config.sampler = Box::new(Sampler::AlwaysOn);
127//!     config.id_generator = Box::new(RandomIdGenerator::default());
128//!
129//!     let provider = new_pipeline()
130//!         .with_service_name("my_app")
131//!         .with_api_version(ApiVersion::Version05)
132//!         .with_agent_endpoint("http://localhost:8126")
133//!         .with_trace_config(config)
134//!         .install_batch().unwrap();
135//!     global::set_tracer_provider(provider.clone());
136//!
137//!     let scope = InstrumentationScope::builder("opentelemetry-datadog")
138//!         .with_version(env!("CARGO_PKG_VERSION"))
139//!         .with_schema_url(semcov::SCHEMA_URL)
140//!         .with_attributes(None)
141//!         .build();
142//!     let tracer = provider.tracer_with_scope(scope);
143//!
144//!     tracer.in_span("doing_work", |cx| {
145//!         // Traced app logic here...
146//!     });
147//!
148//!     let _ = provider.shutdown(); // sending remaining spans before exit
149//!
150//! ```
151
152mod exporter;
153
154pub use exporter::{
155    new_pipeline, ApiVersion, DatadogExporter, DatadogPipelineBuilder, Error, FieldMappingFn,
156    ModelConfig,
157};
158pub use propagator::{DatadogPropagator, DatadogTraceState, DatadogTraceStateBuilder};
159
160mod propagator {
161    use opentelemetry::{
162        propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
163        trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
164        Context,
165    };
166    use std::sync::OnceLock;
167
168    const DATADOG_TRACE_ID_HEADER: &str = "x-datadog-trace-id";
169    const DATADOG_PARENT_ID_HEADER: &str = "x-datadog-parent-id";
170    const DATADOG_SAMPLING_PRIORITY_HEADER: &str = "x-datadog-sampling-priority";
171
172    const TRACE_FLAG_DEFERRED: TraceFlags = TraceFlags::new(0x02);
173    #[cfg(feature = "agent-sampling")]
174    const TRACE_STATE_PRIORITY_SAMPLING: &str = "psr";
175    const TRACE_STATE_MEASURE: &str = "m";
176    const TRACE_STATE_TRUE_VALUE: &str = "1";
177    const TRACE_STATE_FALSE_VALUE: &str = "0";
178
179    // TODO Replace this with LazyLock when MSRV is 1.80+
180    static TRACE_CONTEXT_HEADER_FIELDS: OnceLock<[String; 3]> = OnceLock::new();
181
182    fn trace_context_header_fields() -> &'static [String; 3] {
183        TRACE_CONTEXT_HEADER_FIELDS.get_or_init(|| {
184            [
185                DATADOG_TRACE_ID_HEADER.to_owned(),
186                DATADOG_PARENT_ID_HEADER.to_owned(),
187                DATADOG_SAMPLING_PRIORITY_HEADER.to_owned(),
188            ]
189        })
190    }
191
192    #[derive(Default)]
193    pub struct DatadogTraceStateBuilder {
194        #[cfg(feature = "agent-sampling")]
195        priority_sampling: bool,
196        measuring: bool,
197    }
198
199    fn boolean_to_trace_state_flag(value: bool) -> &'static str {
200        if value {
201            TRACE_STATE_TRUE_VALUE
202        } else {
203            TRACE_STATE_FALSE_VALUE
204        }
205    }
206
207    fn trace_flag_to_boolean(value: &str) -> bool {
208        value == TRACE_STATE_TRUE_VALUE
209    }
210
211    #[allow(clippy::needless_update)]
212    impl DatadogTraceStateBuilder {
213        #[cfg(feature = "agent-sampling")]
214        pub fn with_priority_sampling(self, enabled: bool) -> Self {
215            Self {
216                priority_sampling: enabled,
217                ..self
218            }
219        }
220
221        pub fn with_measuring(self, enabled: bool) -> Self {
222            Self {
223                measuring: enabled,
224                ..self
225            }
226        }
227
228        pub fn build(self) -> TraceState {
229            #[cfg(not(feature = "agent-sampling"))]
230            let values = [(
231                TRACE_STATE_MEASURE,
232                boolean_to_trace_state_flag(self.measuring),
233            )];
234            #[cfg(feature = "agent-sampling")]
235            let values = [
236                (
237                    TRACE_STATE_MEASURE,
238                    boolean_to_trace_state_flag(self.measuring),
239                ),
240                (
241                    TRACE_STATE_PRIORITY_SAMPLING,
242                    boolean_to_trace_state_flag(self.priority_sampling),
243                ),
244            ];
245
246            TraceState::from_key_value(values).unwrap_or_default()
247        }
248    }
249
250    pub trait DatadogTraceState {
251        fn with_measuring(&self, enabled: bool) -> TraceState;
252
253        fn measuring_enabled(&self) -> bool;
254
255        #[cfg(feature = "agent-sampling")]
256        fn with_priority_sampling(&self, enabled: bool) -> TraceState;
257
258        #[cfg(feature = "agent-sampling")]
259        fn priority_sampling_enabled(&self) -> bool;
260    }
261
262    impl DatadogTraceState for TraceState {
263        fn with_measuring(&self, enabled: bool) -> TraceState {
264            self.insert(TRACE_STATE_MEASURE, boolean_to_trace_state_flag(enabled))
265                .unwrap_or_else(|_err| self.clone())
266        }
267
268        fn measuring_enabled(&self) -> bool {
269            self.get(TRACE_STATE_MEASURE)
270                .map(trace_flag_to_boolean)
271                .unwrap_or_default()
272        }
273
274        #[cfg(feature = "agent-sampling")]
275        fn with_priority_sampling(&self, enabled: bool) -> TraceState {
276            self.insert(
277                TRACE_STATE_PRIORITY_SAMPLING,
278                boolean_to_trace_state_flag(enabled),
279            )
280            .unwrap_or_else(|_err| self.clone())
281        }
282
283        #[cfg(feature = "agent-sampling")]
284        fn priority_sampling_enabled(&self) -> bool {
285            self.get(TRACE_STATE_PRIORITY_SAMPLING)
286                .map(trace_flag_to_boolean)
287                .unwrap_or_default()
288        }
289    }
290
291    enum SamplingPriority {
292        UserReject = -1,
293        AutoReject = 0,
294        AutoKeep = 1,
295        UserKeep = 2,
296    }
297
298    #[derive(Debug)]
299    enum ExtractError {
300        TraceId,
301        SpanId,
302        SamplingPriority,
303    }
304
305    /// Extracts and injects `SpanContext`s into `Extractor`s or `Injector`s using Datadog's header format.
306    ///
307    /// The Datadog header format does not have an explicit spec, but can be divined from the client libraries,
308    /// such as [dd-trace-go]
309    ///
310    /// ## Example
311    ///
312    /// ```
313    /// use opentelemetry::global;
314    /// use opentelemetry_datadog::DatadogPropagator;
315    ///
316    /// global::set_text_map_propagator(DatadogPropagator::default());
317    /// ```
318    ///
319    /// [dd-trace-go]: https://github.com/DataDog/dd-trace-go/blob/v1.28.0/ddtrace/tracer/textmap.go#L293
320    #[derive(Clone, Debug, Default)]
321    pub struct DatadogPropagator {
322        _private: (),
323    }
324
325    #[cfg(not(feature = "agent-sampling"))]
326    fn create_trace_state_and_flags(trace_flags: TraceFlags) -> (TraceState, TraceFlags) {
327        (TraceState::default(), trace_flags)
328    }
329
330    #[cfg(feature = "agent-sampling")]
331    fn create_trace_state_and_flags(trace_flags: TraceFlags) -> (TraceState, TraceFlags) {
332        if trace_flags & TRACE_FLAG_DEFERRED == TRACE_FLAG_DEFERRED {
333            (TraceState::default(), trace_flags)
334        } else {
335            (
336                DatadogTraceStateBuilder::default()
337                    .with_priority_sampling(trace_flags.is_sampled())
338                    .build(),
339                TraceFlags::SAMPLED,
340            )
341        }
342    }
343
344    impl DatadogPropagator {
345        /// Creates a new `DatadogPropagator`.
346        pub fn new() -> Self {
347            DatadogPropagator::default()
348        }
349
350        fn extract_trace_id(&self, trace_id: &str) -> Result<TraceId, ExtractError> {
351            trace_id
352                .parse::<u64>()
353                .map(|id| TraceId::from(id as u128))
354                .map_err(|_| ExtractError::TraceId)
355        }
356
357        fn extract_span_id(&self, span_id: &str) -> Result<SpanId, ExtractError> {
358            span_id
359                .parse::<u64>()
360                .map(SpanId::from)
361                .map_err(|_| ExtractError::SpanId)
362        }
363
364        fn extract_sampling_priority(
365            &self,
366            sampling_priority: &str,
367        ) -> Result<SamplingPriority, ExtractError> {
368            let i = sampling_priority
369                .parse::<i32>()
370                .map_err(|_| ExtractError::SamplingPriority)?;
371
372            match i {
373                -1 => Ok(SamplingPriority::UserReject),
374                0 => Ok(SamplingPriority::AutoReject),
375                1 => Ok(SamplingPriority::AutoKeep),
376                2 => Ok(SamplingPriority::UserKeep),
377                _ => Err(ExtractError::SamplingPriority),
378            }
379        }
380
381        fn extract_span_context(
382            &self,
383            extractor: &dyn Extractor,
384        ) -> Result<SpanContext, ExtractError> {
385            let trace_id =
386                self.extract_trace_id(extractor.get(DATADOG_TRACE_ID_HEADER).unwrap_or(""))?;
387            // If we have a trace_id but can't get the parent span, we default it to invalid instead of completely erroring
388            // out so that the rest of the spans aren't completely lost
389            let span_id = self
390                .extract_span_id(extractor.get(DATADOG_PARENT_ID_HEADER).unwrap_or(""))
391                .unwrap_or(SpanId::INVALID);
392            let sampling_priority = self.extract_sampling_priority(
393                extractor
394                    .get(DATADOG_SAMPLING_PRIORITY_HEADER)
395                    .unwrap_or(""),
396            );
397            let sampled = match sampling_priority {
398                Ok(SamplingPriority::UserReject) | Ok(SamplingPriority::AutoReject) => {
399                    TraceFlags::default()
400                }
401                Ok(SamplingPriority::UserKeep) | Ok(SamplingPriority::AutoKeep) => {
402                    TraceFlags::SAMPLED
403                }
404                // Treat the sampling as DEFERRED instead of erroring on extracting the span context
405                Err(_) => TRACE_FLAG_DEFERRED,
406            };
407
408            let (trace_state, trace_flags) = create_trace_state_and_flags(sampled);
409
410            Ok(SpanContext::new(
411                trace_id,
412                span_id,
413                trace_flags,
414                true,
415                trace_state,
416            ))
417        }
418    }
419
420    #[cfg(not(feature = "agent-sampling"))]
421    fn get_sampling_priority(span_context: &SpanContext) -> SamplingPriority {
422        if span_context.is_sampled() {
423            SamplingPriority::AutoKeep
424        } else {
425            SamplingPriority::AutoReject
426        }
427    }
428
429    #[cfg(feature = "agent-sampling")]
430    fn get_sampling_priority(span_context: &SpanContext) -> SamplingPriority {
431        if span_context.trace_state().priority_sampling_enabled() {
432            SamplingPriority::AutoKeep
433        } else {
434            SamplingPriority::AutoReject
435        }
436    }
437
438    impl TextMapPropagator for DatadogPropagator {
439        fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
440            let span = cx.span();
441            let span_context = span.span_context();
442            if span_context.is_valid() {
443                injector.set(
444                    DATADOG_TRACE_ID_HEADER,
445                    (u128::from_be_bytes(span_context.trace_id().to_bytes()) as u64).to_string(),
446                );
447                injector.set(
448                    DATADOG_PARENT_ID_HEADER,
449                    u64::from_be_bytes(span_context.span_id().to_bytes()).to_string(),
450                );
451
452                if span_context.trace_flags() & TRACE_FLAG_DEFERRED != TRACE_FLAG_DEFERRED {
453                    let sampling_priority = get_sampling_priority(span_context);
454
455                    injector.set(
456                        DATADOG_SAMPLING_PRIORITY_HEADER,
457                        (sampling_priority as i32).to_string(),
458                    );
459                }
460            }
461        }
462
463        fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
464            self.extract_span_context(extractor)
465                .map(|sc| cx.with_remote_span_context(sc))
466                .unwrap_or_else(|_| cx.clone())
467        }
468
469        fn fields(&self) -> FieldIter<'_> {
470            FieldIter::new(trace_context_header_fields())
471        }
472    }
473
474    #[cfg(test)]
475    mod tests {
476        use super::*;
477        use opentelemetry::trace::TraceState;
478        use opentelemetry_sdk::testing::trace::TestSpan;
479        use std::collections::HashMap;
480
481        #[rustfmt::skip]
482        fn extract_test_data() -> Vec<(Vec<(&'static str, &'static str)>, SpanContext)> {
483            #[cfg(feature = "agent-sampling")]
484            return vec![
485                (vec![], SpanContext::empty_context()),
486                (vec![(DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::empty_context()),
487                (vec![(DATADOG_TRACE_ID_HEADER, "garbage")], SpanContext::empty_context()),
488                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "garbage")], SpanContext::new(TraceId::from_u128(1234), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
489                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
490                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, DatadogTraceStateBuilder::default().with_priority_sampling(false).build())),
491                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, DatadogTraceStateBuilder::default().with_priority_sampling(true).build())),
492            ];
493            #[cfg(not(feature = "agent-sampling"))]
494            return vec![
495                (vec![], SpanContext::empty_context()),
496                (vec![(DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::empty_context()),
497                (vec![(DATADOG_TRACE_ID_HEADER, "garbage")], SpanContext::empty_context()),
498                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "garbage")], SpanContext::new(TraceId::from_u128(1234), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
499                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
500                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::default(), true, TraceState::default())),
501                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, TraceState::default())),
502            ];
503        }
504
505        #[rustfmt::skip]
506        fn inject_test_data() -> Vec<(Vec<(&'static str, &'static str)>, SpanContext)> {
507            #[cfg(feature = "agent-sampling")]
508            return vec![
509                (vec![], SpanContext::empty_context()),
510                (vec![], SpanContext::new(TraceId::INVALID, SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
511                (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
512                (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TraceFlags::SAMPLED, true, TraceState::default())),
513                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
514                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, DatadogTraceStateBuilder::default().with_priority_sampling(false).build())),
515                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, DatadogTraceStateBuilder::default().with_priority_sampling(true).build())),
516            ];
517            #[cfg(not(feature = "agent-sampling"))]
518            return vec![
519                (vec![], SpanContext::empty_context()),
520                (vec![], SpanContext::new(TraceId::INVALID, SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
521                (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TRACE_FLAG_DEFERRED, true, TraceState::default())),
522                (vec![], SpanContext::new(TraceId::from_hex("1234").unwrap(), SpanId::INVALID, TraceFlags::SAMPLED, true, TraceState::default())),
523                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TRACE_FLAG_DEFERRED, true, TraceState::default())),
524                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "0")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::default(), true, TraceState::default())),
525                (vec![(DATADOG_TRACE_ID_HEADER, "1234"), (DATADOG_PARENT_ID_HEADER, "12"), (DATADOG_SAMPLING_PRIORITY_HEADER, "1")], SpanContext::new(TraceId::from_u128(1234), SpanId::from_u64(12), TraceFlags::SAMPLED, true, TraceState::default())),
526            ];
527        }
528
529        #[test]
530        fn test_extract() {
531            for (header_list, expected) in extract_test_data() {
532                let map: HashMap<String, String> = header_list
533                    .into_iter()
534                    .map(|(k, v)| (k.to_string(), v.to_string()))
535                    .collect();
536
537                let propagator = DatadogPropagator::default();
538                let context = propagator.extract(&map);
539                assert_eq!(context.span().span_context(), &expected);
540            }
541        }
542
543        #[test]
544        fn test_extract_empty() {
545            let map: HashMap<String, String> = HashMap::new();
546            let propagator = DatadogPropagator::default();
547            let context = propagator.extract(&map);
548            assert_eq!(context.span().span_context(), &SpanContext::empty_context())
549        }
550
551        #[test]
552        fn test_extract_with_empty_remote_context() {
553            let map: HashMap<String, String> = HashMap::new();
554            let propagator = DatadogPropagator::default();
555            let context = propagator.extract_with_context(&Context::new(), &map);
556            assert!(!context.has_active_span())
557        }
558
559        #[test]
560        fn test_inject() {
561            let propagator = DatadogPropagator::default();
562            for (header_values, span_context) in inject_test_data() {
563                let mut injector: HashMap<String, String> = HashMap::new();
564                propagator.inject_context(
565                    &Context::current_with_span(TestSpan(span_context)),
566                    &mut injector,
567                );
568
569                if !header_values.is_empty() {
570                    for (k, v) in header_values.into_iter() {
571                        let injected_value: Option<&String> = injector.get(k);
572                        assert_eq!(injected_value, Some(&v.to_string()));
573                        injector.remove(k);
574                    }
575                }
576                assert!(injector.is_empty());
577            }
578        }
579    }
580}