ts_opentelemetry_jaeger/
lib.rs

1//! Collects OpenTelemetry spans and reports them to a given Jaeger
2//! `agent` or `collector` endpoint, propagate the tracing context between the applications using [Jaeger propagation format].
3//!
4//! *Warning*: Note that the exporter component from this crate will be [deprecated][jaeger-deprecation]
5//! in the future. Users are advised to move to [opentelemetry_otlp][otlp-exporter] instead as [Jaeger][jaeger-otlp]
6//! supports accepting data in the OTLP protocol.
7//! See the [Jaeger Docs] for details about Jaeger and deployment information.
8//!
9//! *Compiler support: [requires `rustc` 1.60+][msrv]*
10//!
11//! [Jaeger Docs]: https://www.jaegertracing.io/docs/
12//! [jaeger-deprecation]: https://github.com/open-telemetry/opentelemetry-specification/pull/2858/files
13//! [jaeger-otlp]: https://www.jaegertracing.io/docs/1.38/apis/#opentelemetry-protocol-stable
14//! [otlp-exporter]: https://docs.rs/opentelemetry-otlp/latest/opentelemetry_otlp/
15//! [msrv]: #supported-rust-versions
16//! [jaeger propagation format]: https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format
17//!
18//! ## Quickstart
19//!
20//! First make sure you have a running version of the Jaeger instance
21//! you want to send data to:
22//!
23//! ```shell
24//! $ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest
25//! ```
26//!
27//! Then install a new jaeger pipeline with the recommended defaults to start
28//! exporting telemetry:
29//!
30//! ```no_run
31//! use ts_opentelemetry::trace::Tracer;
32//! use ts_opentelemetry::global;
33//!
34//! #[tokio::main]
35//! async fn main() -> Result<(), ts_opentelemetry::trace::TraceError> {
36//!     global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
37//!     let tracer = opentelemetry_jaeger::new_agent_pipeline().install_simple()?;
38//!
39//!     tracer.in_span("doing_work", |cx| {
40//!         // Traced app logic here...
41//!     });
42//!
43//!     global::shutdown_tracer_provider(); // export remaining spans
44//!
45//!     Ok(())
46//! }
47//! ```
48//!
49//! Or if you are running on an async runtime like Tokio and want to report spans in batches
50//! ```no_run
51//! use ts_opentelemetry::trace::Tracer;
52//! use ts_opentelemetry::global;
53//! use ts_opentelemetry::runtime::Tokio;
54//!
55//! fn main() -> Result<(), ts_opentelemetry::trace::TraceError> {
56//!     global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
57//!     let tracer = opentelemetry_jaeger::new_agent_pipeline().install_batch(Tokio)?;
58//!
59//!     tracer.in_span("doing_work", |cx| {
60//!         // Traced app logic here...
61//!     });
62//!
63//!     global::shutdown_tracer_provider(); // export remaining spans
64//!
65//!     Ok(())
66//! }
67//! ```
68//! ## Performance
69//!
70//! For optimal performance, a batch exporter is recommended as the simple exporter
71//! will export each span synchronously on drop. You can enable the `rt-tokio`,
72//! `rt-tokio-current-thread` or `rt-async-std` features and specify a runtime
73//! on the pipeline builder to have a batch exporter configured for you
74//! automatically.
75//!
76//! ```toml
77//! [dependencies]
78//! opentelemetry = { version = "*", features = ["rt-tokio"] }
79//! opentelemetry-jaeger = { version = "*", features = ["rt-tokio"] }
80//! ```
81//!
82//! ```no_run
83//! # fn main() -> Result<(), ts_opentelemetry::trace::TraceError> {
84//! let tracer = opentelemetry_jaeger::new_agent_pipeline()
85//!     .install_batch(ts_opentelemetry::runtime::Tokio)?;
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! [`tokio`]: https://tokio.rs
91//! [`async-std`]: https://async.rs
92//!
93//! ## Jaeger Exporter From Environment Variables
94//!
95//! The jaeger pipeline builder can be configured dynamically via environment
96//! variables. All variables are optional, a full list of accepted options can
97//! be found in the [jaeger variables spec].
98//!
99//! [jaeger variables spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md#jaeger-exporter
100//!
101//! ## Jaeger Collector Example
102//!
103//! If you want to skip the agent and submit spans directly to a Jaeger collector,
104//! you can enable the optional `collector_client` feature for this crate. This
105//! example expects a Jaeger collector running on `http://localhost:14268`.
106//!
107//! ```toml
108//! [dependencies]
109//! opentelemetry-jaeger = { version = "..", features = ["collector_client", "isahc_collector_client"] }
110//! ```
111//!
112//! Then you can use the [`with_endpoint`] method to specify the endpoint:
113//!
114//! [`with_endpoint`]: exporter::config::collector::CollectorPipeline::with_endpoint
115//!
116//! ```ignore
117//! // Note that this requires the `collector_client` feature.
118//! // We enabled the `isahc_collector_client` feature for a default isahc http client.
119//! // You can also provide your own implementation via .with_http_client() method.
120//! use ts_opentelemetry::trace::{Tracer, TraceError};
121//!
122//! fn main() -> Result<(), TraceError> {
123//!     let tracer = opentelemetry_jaeger::new_collector_pipeline()
124//!         .with_endpoint("http://localhost:14268/api/traces")
125//!         // optionally set username and password for authentication of the exporter.
126//!         .with_username("username")
127//!         .with_password("s3cr3t")
128//!         .with_isahc()
129//!         //.with_http_client(<your client>) provide custom http client implementation
130//!         .install_batch(ts_opentelemetry::runtime::Tokio)?;
131//!
132//!     tracer.in_span("doing_work", |cx| {
133//!         // Traced app logic here...
134//!     });
135//!
136//!     Ok(())
137//! }
138//! ```
139//! ## Resource, tags and service name
140//! In order to export the spans in different format. opentelemetry uses its own
141//! model internally. Most of the jaeger spans' concept can be found in this model.
142//! The full list of this mapping can be found in [OpenTelemetry to Jaeger Transformation].
143//!
144//! The **process tags** in jaeger spans will be mapped as resource in opentelemetry. You can
145//! set it through `OTEL_RESOURCE_ATTRIBUTES` environment variable or using [`with_trace_config`].
146//!
147//! Note that to avoid copying data multiple times. Jaeger exporter will uses resource stored in [`Exporter`].
148//!
149//! The **tags** in jaeger spans will be mapped as attributes in opentelemetry spans. You can
150//! set it through [`set_attribute`] method.
151//!
152//! Each jaeger span requires a **service name**. This will be mapped as a resource with `service.name` key.
153//! You can set it using one of the following methods from highest priority to lowest priority.
154//! 1. [`with_service_name`].
155//! 2. include a `service.name` key value pairs when configure resource using [`with_trace_config`].
156//! 3. set the service name as `OTEL_SERVCE_NAME` environment variable.
157//! 4. set the `service.name` attributes in `OTEL_RESOURCE_ATTRIBUTES`.
158//! 5. if the service name is not provided by the above method. `unknown_service` will be used.
159//!
160//! Based on the service name, we update/append the `service.name` process tags in jaeger spans.
161//!
162//! [`with_service_name`]: crate::exporter::config::agent::AgentPipeline::with_service_name
163//! [`with_trace_config`]: crate::exporter::config::agent::AgentPipeline::with_trace_config
164//! [`set_attribute`]: ts_opentelemetry::trace::Span::set_attribute
165//! [OpenTelemetry to Jaeger Transformation]:https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md
166//!
167//! ## Kitchen Sink Full Configuration
168//!
169//! Example showing how to override all configuration options. See the
170//! [`CollectorPipeline`] and [`AgentPipeline`] docs for details of each option.
171//!
172//! [`CollectorPipeline`]: config::collector::CollectorPipeline
173//! [`AgentPipeline`]: config::agent::AgentPipeline
174//!
175//! ### Export to agents
176//! ```no_run
177//! use ts_opentelemetry::{sdk::{trace::{self, RandomIdGenerator, Sampler}, Resource}, global, KeyValue, trace::{Tracer, TraceError}};
178//!
179//! fn main() -> Result<(), TraceError> {
180//!     global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
181//!     let tracer = opentelemetry_jaeger::new_agent_pipeline()
182//!         .with_endpoint("localhost:6831")
183//!         .with_service_name("my_app")
184//!         .with_max_packet_size(9_216)
185//!         .with_auto_split_batch(true)
186//!         .with_instrumentation_library_tags(false)
187//!         .with_trace_config(
188//!             trace::config()
189//!                 .with_sampler(Sampler::AlwaysOn)
190//!                 .with_id_generator(RandomIdGenerator::default())
191//!                 .with_max_events_per_span(64)
192//!                 .with_max_attributes_per_span(16)
193//!                  // resources will translated to tags in jaeger spans
194//!                 .with_resource(Resource::new(vec![KeyValue::new("key", "value"),
195//!                           KeyValue::new("process_key", "process_value")])),
196//!         )
197//!         .install_batch(ts_opentelemetry::runtime::Tokio)?;
198//!
199//!     tracer.in_span("doing_work", |cx| {
200//!         // Traced app logic here...
201//!     });
202//!
203//!     // export remaining spans. It's optional if you can accept spans loss for the last batch.
204//!     global::shutdown_tracer_provider();
205//!
206//!     Ok(())
207//! }
208//! ```
209//!
210//! ### Export to collectors
211//! Note that this example requires `collecotr_client` and `isahc_collector_client` feature.
212//! ```ignore
213//! use ts_opentelemetry::{sdk::{trace::{self, RandomIdGenerator, Sampler}, Resource}, global, KeyValue, trace::{Tracer, TraceError}};
214//!
215//! fn main() -> Result<(), TraceError> {
216//!     global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
217//!     let tracer = opentelemetry_jaeger::new_collector_pipeline()
218//!         .with_endpoint("http://localhost:14250/api/trace") // set collector endpoint
219//!         .with_service_name("my_app") // the name of the application
220//!         .with_trace_config(
221//!             trace::config()
222//!                 .with_sampler(Sampler::AlwaysOn)
223//!                 .with_id_generator(RandomIdGenerator::default())
224//!                 .with_max_events_per_span(64)
225//!                 .with_max_attributes_per_span(16)
226//!                 .with_max_events_per_span(16)
227//!                 // resources will translated to tags in jaeger spans
228//!                 .with_resource(Resource::new(vec![KeyValue::new("key", "value"),
229//!                           KeyValue::new("process_key", "process_value")])),
230//!         )
231//!         // we config a surf http client with 2 seconds timeout
232//!         // and have basic authentication header with username=username, password=s3cr3t
233//!         .with_isahc() // requires `isahc_collector_client` feature
234//!         .with_username("username")
235//!         .with_password("s3cr3t")
236//!         .with_timeout(std::time::Duration::from_secs(2))
237//!         .install_batch(ts_opentelemetry::runtime::Tokio)?;
238//!
239//!     tracer.in_span("doing_work", |cx| {
240//!         // Traced app logic here...
241//!     });
242//!
243//!     // export remaining spans. It's optional if you can accept spans loss for the last batch.
244//!     global::shutdown_tracer_provider();
245//!
246//!     Ok(())
247//! }
248//! ```
249//!
250//! # Crate Feature Flags
251//!
252//! The following crate feature flags are available:
253//!
254//! * `collector_client`: Export span data directly to a Jaeger collector. User MUST provide the http client.
255//!
256//! * `hyper_collector_client`: Export span data with Jaeger collector backed by a hyper default http client.
257//!
258//! * `surf_collector_client`: Export span data with Jaeger collector backed by a surf default http client.
259//!
260//! * `reqwest_collector_client`: Export span data with Jaeger collector backed by a reqwest http client.
261//!
262//! * `reqwest_blocking_collector_client`: Export span data with Jaeger collector backed by a reqwest blocking http client.
263//!
264//! * `isahc_collector_client`: Export span data with Jaeger collector backed by a isahc http client.
265//!
266//! * `wasm_collector_client`: Enable collector in wasm.
267//!
268//! Support for recording and exporting telemetry asynchronously can be added
269//! via the following flags, it extends the [`opentelemetry`] feature:
270//!
271//! * `rt-tokio`: Enable sending UDP packets to Jaeger agent asynchronously when the tokio
272//!   [`Multi-Threaded Scheduler`] is used.
273//!
274//! * `rt-tokio-current-thread`: Enable sending UDP packets to Jaeger agent asynchronously when the
275//!   tokio [`Current-Thread Scheduler`] is used.
276//!
277//! * `rt-async-std`: Enable sending UDP packets to Jaeger agent asynchronously when the
278//!   [`async-std`] runtime is used.
279//!
280//! [`Multi-Threaded Scheduler`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#multi-thread-scheduler
281//! [`Current-Thread Scheduler`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#current-thread-scheduler
282//! [`async-std`]: https://async.rs
283//! [`opentelemetry`]: https://crates.io/crates/opentelemetry
284//!
285//! # Supported Rust Versions
286//!
287//! OpenTelemetry is built against the latest stable release. The minimum
288//! supported version is 1.60. The current OpenTelemetry version is not
289//! guaranteed to build on Rust versions earlier than the minimum supported
290//! version.
291//!
292//! The current stable Rust compiler and the three most recent minor versions
293//! before it will always be supported. For example, if the current stable
294//! compiler version is 1.60, the minimum supported version will not be
295//! increased past 1.46, three minor versions prior. Increasing the minimum
296//! supported compiler version is not considered a semver breaking change as
297//! long as doing so complies with this policy.
298#![warn(
299    future_incompatible,
300    missing_debug_implementations,
301    missing_docs,
302    nonstandard_style,
303    rust_2018_idioms,
304    unreachable_pub,
305    unused
306)]
307#![cfg_attr(
308    docsrs,
309    feature(doc_cfg, doc_auto_cfg),
310    deny(rustdoc::broken_intra_doc_links)
311)]
312#![doc(
313    html_logo_url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo.svg"
314)]
315#![cfg_attr(test, deny(warnings))]
316
317pub use exporter::config;
318#[cfg(feature = "collector_client")]
319pub use exporter::config::collector::new_collector_pipeline;
320#[cfg(feature = "wasm_collector_client")]
321pub use exporter::config::collector::new_wasm_collector_pipeline;
322pub use exporter::{
323    config::agent::new_agent_pipeline, runtime::JaegerTraceRuntime, Error, Exporter, Process,
324};
325pub use propagator::Propagator;
326
327mod exporter;
328
329#[cfg(feature = "integration_test")]
330#[doc(hidden)]
331pub mod testing;
332
333mod propagator {
334    use ts_opentelemetry::{
335        global::{self, Error},
336        propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
337        trace::{
338            SpanContext, SpanId, TraceContextExt, TraceError, TraceFlags, TraceId, TraceState,
339        },
340        Context,
341    };
342    use std::borrow::Cow;
343    use std::str::FromStr;
344
345    const JAEGER_HEADER: &str = "uber-trace-id";
346    const JAEGER_BAGGAGE_PREFIX: &str = "uberctx-";
347    const DEPRECATED_PARENT_SPAN: &str = "0";
348
349    const TRACE_FLAG_DEBUG: TraceFlags = TraceFlags::new(0x04);
350
351    /// The Jaeger propagator propagates span contexts in [Jaeger propagation format].
352    ///
353    /// Cross-cutting concerns send their state to the next process using `Propagator`s,
354    /// which are defined as objects used to read and write context data to and from messages
355    /// exchanged by the applications. Each concern creates a set of `Propagator`s for every
356    /// supported `Propagator` type.
357    ///
358    /// Note that jaeger header can be set in http header or encoded as url.
359    ///
360    /// ## Examples
361    /// ```
362    /// # use ts_opentelemetry::{global, trace::{Tracer, TraceContextExt}, Context};
363    /// # use opentelemetry_jaeger::Propagator as JaegerPropagator;
364    /// # fn send_request() {
365    /// // setup jaeger propagator
366    /// global::set_text_map_propagator(JaegerPropagator::default());
367    /// // You also can init propagator with custom header name
368    /// // global::set_text_map_propagator(JaegerPropagator::with_custom_header("my-custom-header"));
369    ///
370    /// // before sending requests to downstream services.
371    /// let mut headers = std::collections::HashMap::new(); // replace by http header of the outgoing request
372    /// let caller_span = global::tracer("caller").start("say hello");
373    /// let cx = Context::current_with_span(caller_span);
374    /// global::get_text_map_propagator(|propagator| {
375    ///     propagator.inject_context(&cx, &mut headers); // propagator serialize the tracing context
376    /// });
377    /// // Send the request..
378    /// # }
379    ///
380    ///
381    /// # fn receive_request() {
382    /// // Receive the request sent above on the other service...
383    /// // setup jaeger propagator
384    /// global::set_text_map_propagator(JaegerPropagator::new());
385    /// // You also can init propagator with custom header name
386    /// // global::set_text_map_propagator(JaegerPropagator::with_custom_header("my-custom-header"));
387    ///
388    /// let headers = std::collections::HashMap::new(); // replace this with http header map from incoming requests.
389    /// let parent_context = global::get_text_map_propagator(|propagator| {
390    ///      propagator.extract(&headers)
391    /// });
392    ///
393    /// // this span's parent span will be caller_span in send_request functions.
394    /// let receiver_span = global::tracer("receiver").start_with_context("hello", &parent_context);
395    /// # }
396    /// ```
397    ///
398    ///  [jaeger propagation format]: https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format
399    #[derive(Clone, Debug)]
400    pub struct Propagator {
401        baggage_prefix: &'static str,
402        header_name: &'static str,
403        fields: [String; 1],
404    }
405
406    // Implement default using Propagator::new() to not break compatibility with previous versions
407    impl Default for Propagator {
408        fn default() -> Self {
409            Propagator::new()
410        }
411    }
412
413    impl Propagator {
414        /// Create a Jaeger propagator
415        pub fn new() -> Self {
416            Self::with_custom_header_and_baggage(JAEGER_HEADER, JAEGER_BAGGAGE_PREFIX)
417        }
418
419        /// Create a Jaeger propagator with custom header name
420        pub fn with_custom_header(custom_header_name: &'static str) -> Self {
421            Self::with_custom_header_and_baggage(custom_header_name, JAEGER_BAGGAGE_PREFIX)
422        }
423
424        /// Create a Jaeger propagator with custom header name and baggage prefix
425        ///
426        /// NOTE: it's implicitly fallback to the default header names when the ane of provided custom_* is empty
427        /// Default header-name is `uber-trace-id` and baggage-prefix is `uberctx-`
428        /// The format of serialized context and baggage's stays unchanged and not depending
429        /// on provided header name and prefix.
430        pub fn with_custom_header_and_baggage(
431            custom_header_name: &'static str,
432            custom_baggage_prefix: &'static str,
433        ) -> Self {
434            let custom_header_name = if custom_header_name.trim().is_empty() {
435                JAEGER_HEADER
436            } else {
437                custom_header_name
438            };
439
440            let custom_baggage_prefix = if custom_baggage_prefix.trim().is_empty() {
441                JAEGER_BAGGAGE_PREFIX
442            } else {
443                custom_baggage_prefix
444            };
445
446            Propagator {
447                baggage_prefix: custom_baggage_prefix.trim(),
448                header_name: custom_header_name.trim(),
449                fields: [custom_header_name.to_owned()],
450            }
451        }
452
453        /// Extract span context from header value
454        fn extract_span_context(&self, extractor: &dyn Extractor) -> Result<SpanContext, ()> {
455            let mut header_value = Cow::from(extractor.get(self.header_name).unwrap_or(""));
456            // if there is no :, it means header_value could be encoded as url, try decode first
457            if !header_value.contains(':') {
458                header_value = Cow::from(header_value.replace("%3A", ":"));
459            }
460
461            let parts = header_value.split_terminator(':').collect::<Vec<&str>>();
462            if parts.len() != 4 {
463                return Err(());
464            }
465
466            // extract trace id
467            let trace_id = self.extract_trace_id(parts[0])?;
468            let span_id = self.extract_span_id(parts[1])?;
469            // Ignore parent span id since it's deprecated.
470            let flags = self.extract_trace_flags(parts[3])?;
471            let state = self.extract_trace_state(extractor)?;
472
473            Ok(SpanContext::new(trace_id, span_id, flags, true, state))
474        }
475
476        /// Extract trace id from the header.
477        fn extract_trace_id(&self, trace_id: &str) -> Result<TraceId, ()> {
478            if trace_id.len() > 32 {
479                return Err(());
480            }
481
482            TraceId::from_hex(trace_id).map_err(|_| ())
483        }
484
485        /// Extract span id from the header.
486        fn extract_span_id(&self, span_id: &str) -> Result<SpanId, ()> {
487            match span_id.len() {
488                // exact 16
489                16 => SpanId::from_hex(span_id).map_err(|_| ()),
490                // more than 16 is invalid
491                17.. => Err(()),
492                // less than 16 will result padding on left
493                _ => {
494                    let padded = format!("{span_id:0>16}");
495                    SpanId::from_hex(&padded).map_err(|_| ())
496                }
497            }
498        }
499
500        /// Extract flag from the header
501        ///
502        /// First bit control whether to sample
503        /// Second bit control whether it's a debug trace
504        /// Third bit is not used.
505        /// Forth bit is firehose flag, which is not supported in OT now.
506        fn extract_trace_flags(&self, flag: &str) -> Result<TraceFlags, ()> {
507            if flag.len() > 2 {
508                return Err(());
509            }
510            let flag = u8::from_str(flag).map_err(|_| ())?;
511            if flag & 0x01 == 0x01 {
512                if flag & 0x02 == 0x02 {
513                    Ok(TraceFlags::SAMPLED | TRACE_FLAG_DEBUG)
514                } else {
515                    Ok(TraceFlags::SAMPLED)
516                }
517            } else {
518                // Debug flag should only be set when sampled flag is set.
519                // So if debug flag is set alone. We will just use not sampled flag
520                Ok(TraceFlags::default())
521            }
522        }
523
524        fn extract_trace_state(&self, extractor: &dyn Extractor) -> Result<TraceState, ()> {
525            let baggage_keys = extractor
526                .keys()
527                .into_iter()
528                .filter(|key| key.starts_with(self.baggage_prefix))
529                .filter_map(|key| {
530                    extractor
531                        .get(key)
532                        .map(|value| (key.to_string(), value.to_string()))
533                });
534
535            match TraceState::from_key_value(baggage_keys) {
536                Ok(trace_state) => Ok(trace_state),
537                Err(trace_state_err) => {
538                    global::handle_error(Error::Trace(TraceError::Other(Box::new(
539                        trace_state_err,
540                    ))));
541                    Err(()) //todo: assign an error type instead of using ()
542                }
543            }
544        }
545    }
546
547    impl TextMapPropagator for Propagator {
548        fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
549            let span = cx.span();
550            let span_context = span.span_context();
551            if span_context.is_valid() {
552                let flag: u8 = if span_context.is_sampled() {
553                    if span_context.trace_flags() & TRACE_FLAG_DEBUG == TRACE_FLAG_DEBUG {
554                        0x03
555                    } else {
556                        0x01
557                    }
558                } else {
559                    0x00
560                };
561                let header_value = format!(
562                    "{:032x}:{:016x}:{:01}:{:01x}",
563                    span_context.trace_id(),
564                    span_context.span_id(),
565                    DEPRECATED_PARENT_SPAN,
566                    flag,
567                );
568                injector.set(self.header_name, header_value);
569            }
570        }
571
572        fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
573            self.extract_span_context(extractor)
574                .map(|sc| cx.with_remote_span_context(sc))
575                .unwrap_or_else(|_| cx.clone())
576        }
577
578        fn fields(&self) -> FieldIter<'_> {
579            FieldIter::new(self.fields.as_ref())
580        }
581    }
582
583    #[cfg(test)]
584    mod tests {
585        use super::*;
586        use ts_opentelemetry::{
587            propagation::{Injector, TextMapPropagator},
588            testing::trace::TestSpan,
589            trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
590            Context,
591        };
592        use std::collections::HashMap;
593
594        const LONG_TRACE_ID_STR: &str = "000000000000004d0000000000000016";
595        const SHORT_TRACE_ID_STR: &str = "4d0000000000000016";
596        const TRACE_ID: u128 = 0x0000_0000_0000_004d_0000_0000_0000_0016;
597        const SPAN_ID_STR: &str = "0000000000017c29";
598        const SHORT_SPAN_ID_STR: &str = "17c29";
599        const SPAN_ID: u64 = 0x0000_0000_0001_7c29;
600
601        fn get_extract_data() -> Vec<(&'static str, &'static str, u8, SpanContext)> {
602            vec![
603                (
604                    LONG_TRACE_ID_STR,
605                    SPAN_ID_STR,
606                    1,
607                    SpanContext::new(
608                        TraceId::from_u128(TRACE_ID),
609                        SpanId::from_u64(SPAN_ID),
610                        TraceFlags::SAMPLED,
611                        true,
612                        TraceState::default(),
613                    ),
614                ),
615                (
616                    SHORT_TRACE_ID_STR,
617                    SPAN_ID_STR,
618                    1,
619                    SpanContext::new(
620                        TraceId::from_u128(TRACE_ID),
621                        SpanId::from_u64(SPAN_ID),
622                        TraceFlags::SAMPLED,
623                        true,
624                        TraceState::default(),
625                    ),
626                ),
627                (
628                    SHORT_TRACE_ID_STR,
629                    SHORT_SPAN_ID_STR,
630                    1,
631                    SpanContext::new(
632                        TraceId::from_u128(TRACE_ID),
633                        SpanId::from_u64(SPAN_ID),
634                        TraceFlags::SAMPLED,
635                        true,
636                        TraceState::default(),
637                    ),
638                ),
639                (
640                    LONG_TRACE_ID_STR,
641                    SPAN_ID_STR,
642                    3,
643                    SpanContext::new(
644                        TraceId::from_u128(TRACE_ID),
645                        SpanId::from_u64(SPAN_ID),
646                        TRACE_FLAG_DEBUG | TraceFlags::SAMPLED,
647                        true,
648                        TraceState::default(),
649                    ),
650                ),
651                (
652                    LONG_TRACE_ID_STR,
653                    SPAN_ID_STR,
654                    0,
655                    SpanContext::new(
656                        TraceId::from_u128(TRACE_ID),
657                        SpanId::from_u64(SPAN_ID),
658                        TraceFlags::default(),
659                        true,
660                        TraceState::default(),
661                    ),
662                ),
663                (
664                    "invalidtractid",
665                    SPAN_ID_STR,
666                    0,
667                    SpanContext::empty_context(),
668                ),
669                (
670                    LONG_TRACE_ID_STR,
671                    "invalidspanID",
672                    0,
673                    SpanContext::empty_context(),
674                ),
675                (
676                    LONG_TRACE_ID_STR,
677                    SPAN_ID_STR,
678                    120,
679                    SpanContext::empty_context(),
680                ),
681            ]
682        }
683
684        fn get_inject_data() -> Vec<(SpanContext, String)> {
685            vec![
686                (
687                    SpanContext::new(
688                        TraceId::from_u128(TRACE_ID),
689                        SpanId::from_u64(SPAN_ID),
690                        TraceFlags::SAMPLED,
691                        true,
692                        TraceState::default(),
693                    ),
694                    format!("{}:{}:0:1", LONG_TRACE_ID_STR, SPAN_ID_STR),
695                ),
696                (
697                    SpanContext::new(
698                        TraceId::from_u128(TRACE_ID),
699                        SpanId::from_u64(SPAN_ID),
700                        TraceFlags::default(),
701                        true,
702                        TraceState::default(),
703                    ),
704                    format!("{}:{}:0:0", LONG_TRACE_ID_STR, SPAN_ID_STR),
705                ),
706                (
707                    SpanContext::new(
708                        TraceId::from_u128(TRACE_ID),
709                        SpanId::from_u64(SPAN_ID),
710                        TRACE_FLAG_DEBUG | TraceFlags::SAMPLED,
711                        true,
712                        TraceState::default(),
713                    ),
714                    format!("{}:{}:0:3", LONG_TRACE_ID_STR, SPAN_ID_STR),
715                ),
716            ]
717        }
718
719        /// Try to extract the context using the created Propagator with custom header name
720        /// from the Extractor under the `context_key` key.
721        fn _test_extract_with_header(construct_header: &'static str, context_key: &'static str) {
722            let propagator = Propagator::with_custom_header(construct_header);
723            for (trace_id, span_id, flag, expected) in get_extract_data() {
724                let mut map: HashMap<String, String> = HashMap::new();
725                map.set(context_key, format!("{}:{}:0:{}", trace_id, span_id, flag));
726                let context = propagator.extract(&map);
727                assert_eq!(context.span().span_context(), &expected);
728            }
729        }
730
731        /// Try to inject the context using the created Propagator with custom header name
732        /// and expect the serialized context existence under `expect_header` key.
733        fn _test_inject_with_header(construct_header: &'static str, expect_header: &'static str) {
734            let propagator = Propagator::with_custom_header(construct_header);
735            for (span_context, header_value) in get_inject_data() {
736                let mut injector = HashMap::new();
737                propagator.inject_context(
738                    &Context::current_with_span(TestSpan(span_context)),
739                    &mut injector,
740                );
741                assert_eq!(injector.get(expect_header), Some(&header_value));
742            }
743        }
744
745        #[test]
746        fn test_extract_empty() {
747            let map: HashMap<String, String> = HashMap::new();
748            let propagator = Propagator::new();
749            let context = propagator.extract(&map);
750            assert_eq!(context.span().span_context(), &SpanContext::empty_context())
751        }
752
753        #[test]
754        fn test_inject_extract_with_default() {
755            let propagator = Propagator::default();
756            for (span_context, header_value) in get_inject_data() {
757                let mut injector = HashMap::new();
758                propagator.inject_context(
759                    &Context::current_with_span(TestSpan(span_context)),
760                    &mut injector,
761                );
762                assert_eq!(injector.get(JAEGER_HEADER), Some(&header_value));
763            }
764            for (trace_id, span_id, flag, expected) in get_extract_data() {
765                let mut map: HashMap<String, String> = HashMap::new();
766                map.set(
767                    JAEGER_HEADER,
768                    format!("{}:{}:0:{}", trace_id, span_id, flag),
769                );
770                let context = propagator.extract(&map);
771                assert_eq!(context.span().span_context(), &expected);
772            }
773        }
774
775        #[test]
776        fn test_extract_too_many_parts() {
777            let mut map: HashMap<String, String> = HashMap::new();
778            map.set(
779                JAEGER_HEADER,
780                format!("{}:{}:0:1:aa", LONG_TRACE_ID_STR, SPAN_ID_STR),
781            );
782            let propagator = Propagator::new();
783            let context = propagator.extract(&map);
784            assert_eq!(context.span().span_context(), &SpanContext::empty_context());
785        }
786
787        #[test]
788        fn test_extract_invalid_flag() {
789            let mut map: HashMap<String, String> = HashMap::new();
790            map.set(
791                JAEGER_HEADER,
792                format!("{}:{}:0:aa", LONG_TRACE_ID_STR, SPAN_ID_STR),
793            );
794            let propagator = Propagator::new();
795            let context = propagator.extract(&map);
796            assert_eq!(context.span().span_context(), &SpanContext::empty_context());
797        }
798
799        #[test]
800        fn test_extract_from_url() {
801            let mut map: HashMap<String, String> = HashMap::new();
802            map.set(
803                JAEGER_HEADER,
804                format!("{}%3A{}%3A0%3A1", LONG_TRACE_ID_STR, SPAN_ID_STR),
805            );
806            let propagator = Propagator::new();
807            let context = propagator.extract(&map);
808            assert_eq!(
809                context.span().span_context(),
810                &SpanContext::new(
811                    TraceId::from_u128(TRACE_ID),
812                    SpanId::from_u64(SPAN_ID),
813                    TraceFlags::SAMPLED,
814                    true,
815                    TraceState::default(),
816                )
817            );
818        }
819
820        #[test]
821        fn test_extract() {
822            _test_extract_with_header(JAEGER_HEADER, JAEGER_HEADER)
823        }
824
825        #[test]
826        fn test_inject() {
827            _test_inject_with_header(JAEGER_HEADER, JAEGER_HEADER)
828        }
829
830        #[test]
831        fn test_extract_with_invalid_header() {
832            for construct in &["", "   "] {
833                _test_extract_with_header(construct, JAEGER_HEADER)
834            }
835        }
836
837        #[test]
838        fn test_extract_with_valid_header() {
839            for construct in &["custom-header", "custom-header   ", "   custom-header   "] {
840                _test_extract_with_header(construct, "custom-header")
841            }
842        }
843
844        #[test]
845        fn test_inject_with_invalid_header() {
846            for construct in &["", "   "] {
847                _test_inject_with_header(construct, JAEGER_HEADER)
848            }
849        }
850
851        #[test]
852        fn test_inject_with_valid_header() {
853            for construct in &["custom-header", "custom-header   ", "   custom-header   "] {
854                _test_inject_with_header(construct, "custom-header")
855            }
856        }
857    }
858}