datadog_opentelemetry/
lib.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4//! # Datadog Opentelemetry
5//!
6//! This library powers [Distributed Tracing](https://docs.datadoghq.com/tracing/). It provides OpenTelemetry API and SDK compatibility with Datadog-specific features and optimizations.
7//!
8//!
9//! ## Usage
10//!
11//! The `datadog-opentelemetry` crate provides an easy to use override for the rust
12//! opentelemetry-sdk.
13//!
14//! ### Installation
15//!
16//! Add to you Cargo.toml
17//!
18//! ```toml
19//! datadog-opentelemetry = { version = "0.2.1" }
20//! ```
21//!
22//! ### Tracing
23//!
24//! To trace functions, you can either use the `opentelemetry` crate's [API](https://docs.rs/opentelemetry/0.31.0/opentelemetry/trace/index.html) or the `tracing` crate [API](https://docs.rs/tracing/0.1.41/tracing/) with the `tracing-opentelemetry` [bridge](https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/).
25//!
26//! ### Initialization
27//!
28//! The following examples will read datadog and opentelemetry configuration from environment
29//! variables and other available sources, initialize and set up the tracer provider and the
30//! distributed tracing propagators globally.
31//!
32//! #### Tracing API
33//!
34//! * Requires `tracing-subscriber` and `tracing`
35//!
36//! ```no_run
37//! use opentelemetry::trace::TracerProvider;
38//! use std::time::Duration;
39//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
40//!
41//! // This picks up env var configuration and other datadog configuration sources
42//! let tracer_provider = datadog_opentelemetry::tracing().init();
43//!
44//! tracing_subscriber::registry()
45//!     .with(
46//!         tracing_opentelemetry::layer()
47//!             .with_tracer(tracer_provider.tracer("my_application_name")),
48//!     )
49//!     .init();
50//!
51//! tracer_provider
52//!     .shutdown_with_timeout(Duration::from_secs(1))
53//!     .expect("tracer shutdown error");
54//! ```
55//!
56//! #### Opentelemetry API
57//!
58//! * requires `opentelemetry`
59//!
60//! ```no_run
61//! use std::time::Duration;
62//!
63//! // This picks up env var configuration and other datadog configuration sources
64//! let tracer_provider = datadog_opentelemetry::tracing().init();
65//!
66//! // Your code
67//! // Now use standard OpenTelemetry APIs
68//! use opentelemetry::global;
69//! use opentelemetry::trace::Tracer;
70//!
71//! let tracer = global::tracer("my-service");
72//! let span = tracer.start("my-operation");
73//! // ... do work ...
74//!
75//! // Shutdown the tracer to flush the remaining data
76//! tracer_provider
77//!     .shutdown_with_timeout(Duration::from_secs(1))
78//!     .expect("tracer shutdown error");
79//! ```
80//!
81//! ### Configuration
82//!
83//! Configuration can be passed either:
84//!
85//! * Programmatically
86//!
87//! ```rust
88//! use datadog_opentelemetry::configuration::Config;
89//! let config = datadog_opentelemetry::configuration::Config::builder()
90//!     .set_service("my_service".to_string())
91//!     .set_env("prod".to_string())
92//!     .build();
93//! let tracer_provider = datadog_opentelemetry::tracing()
94//!     .with_config(config)
95//!     // this also accepts options for the Opentelemetry SDK builder
96//!     .with_max_attributes_per_span(64)
97//!     .init();
98//! ```
99//!
100//! For advanced usage and configuration information, check out [`DatadogTracingBuilder`] and
101//! [`configuration::ConfigBuilder`]
102//!
103//! * Through env variables
104//!
105//! ```bash
106//! DD_SERVICE=my_service DD_ENV=prod cargo run
107//! ```
108//!
109//! Or to pass options to the OpenTelemetry SDK TracerProviderBuilder
110//! ```rust
111//! # #[derive(Debug)]
112//! # struct MySpanProcessor;
113//! #
114//! # impl opentelemetry_sdk::trace::SpanProcessor for MySpanProcessor {
115//! #     fn on_start(&self, span: &mut opentelemetry_sdk::trace::Span, cx: &opentelemetry::Context) {
116//! #     }
117//! #     fn on_end(&self, span: opentelemetry_sdk::trace::SpanData) {}
118//! #     fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult {
119//! #         Ok(())
120//! #     }
121//! #     fn shutdown_with_timeout(
122//! #         &self,
123//! #         timeout: std::time::Duration,
124//! #     ) -> opentelemetry_sdk::error::OTelSdkResult {
125//! #         Ok(())
126//! #     }
127//! #     fn set_resource(&mut self, _resource: &opentelemetry_sdk::Resource) {}
128//! # }
129//! #
130//! // Custom otel tracer sdk options
131//! datadog_opentelemetry::tracing()
132//!     .with_max_attributes_per_span(64)
133//!     // Custom span processor
134//!     .with_span_processor(MySpanProcessor)
135//!     .init();
136//! ```
137//!
138//! ## Support
139//!
140//! * MSRV: 1.84
141//! * [opentelemetry](https://docs.rs/opentelemetry/0.31.0/opentelemetry/) version: 0.31
142//! * [`tracing-opentelemetry`](https://docs.rs/tracing-opentelemetry/0.32.0/tracing_opentelemetry/)
143//!   version: 0.32
144
145// Public re-exports
146pub use core::configuration;
147pub use core::log;
148
149#[cfg(feature = "test-utils")]
150pub mod core;
151#[cfg(feature = "test-utils")]
152pub mod mappings;
153#[cfg(feature = "test-utils")]
154pub mod propagation;
155#[cfg(feature = "test-utils")]
156pub mod sampling;
157
158#[cfg(not(feature = "test-utils"))]
159pub(crate) mod core;
160#[cfg(not(feature = "test-utils"))]
161pub(crate) mod mappings;
162#[cfg(not(feature = "test-utils"))]
163pub(crate) mod propagation;
164#[cfg(not(feature = "test-utils"))]
165pub(crate) mod sampling;
166
167mod ddtrace_transform;
168mod sampler;
169mod span_exporter;
170mod span_processor;
171mod spans_metrics;
172mod text_map_propagator;
173mod trace_id;
174
175use std::sync::{Arc, RwLock};
176
177use opentelemetry::{Key, KeyValue, Value};
178use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
179use opentelemetry_semantic_conventions::resource::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME};
180
181use crate::{
182    core::configuration::{Config, RemoteConfigUpdate},
183    sampler::Sampler,
184    span_processor::{DatadogSpanProcessor, TraceRegistry},
185    text_map_propagator::DatadogPropagator,
186};
187
188pub struct DatadogTracingBuilder {
189    config: Option<Config>,
190    resource: Option<opentelemetry_sdk::Resource>,
191    tracer_provider: opentelemetry_sdk::trace::TracerProviderBuilder,
192}
193
194impl DatadogTracingBuilder {
195    /// Sets the datadog specific configuration
196    ///
197    /// Default: Config::builder().build()
198    pub fn with_config(mut self, config: Config) -> Self {
199        self.config = Some(config);
200        self
201    }
202
203    /// Sets the resource passed to the SDK. See [opentelemetry_sdk::Resource]
204    ///
205    /// Default: Config::builder().build()
206    pub fn with_resource(mut self, resource: opentelemetry_sdk::Resource) -> Self {
207        self.resource = Some(resource);
208        self
209    }
210
211    /// Initializes the Tracer Provider, and the Text Map Propagator and install
212    /// them globally
213    pub fn init(self) -> SdkTracerProvider {
214        let (tracer_provider, propagator) = self.init_local();
215
216        opentelemetry::global::set_text_map_propagator(propagator);
217        opentelemetry::global::set_tracer_provider(tracer_provider.clone());
218        tracer_provider
219    }
220
221    /// Initialize the Tracer Provider, and the Text Map Propagator without doing a global
222    /// installation
223    ///
224    /// You will need to set them up yourself, at a latter point if you want to use global tracing
225    /// methods and library integrations
226    ///
227    /// # Example
228    ///
229    /// ```rust
230    /// let (tracer_provider, propagator) = datadog_opentelemetry::tracing().init_local();
231    ///
232    /// opentelemetry::global::set_text_map_propagator(propagator);
233    /// opentelemetry::global::set_tracer_provider(tracer_provider.clone());
234    /// ```
235    pub fn init_local(self) -> (SdkTracerProvider, DatadogPropagator) {
236        let config = self.config.unwrap_or_else(|| Config::builder().build());
237        make_tracer(Arc::new(config), self.tracer_provider, self.resource)
238    }
239}
240
241impl DatadogTracingBuilder {
242    // Methods forwarded to the otel tracer provider builder
243
244    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_span_processor]
245    pub fn with_span_processor<T: opentelemetry_sdk::trace::SpanProcessor + 'static>(
246        mut self,
247        processor: T,
248    ) -> Self {
249        self.tracer_provider = self.tracer_provider.with_span_processor(processor);
250        self
251    }
252
253    /// Specify the number of events to be recorded per span.
254    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_max_events_per_span]
255    pub fn with_max_events_per_span(mut self, max_events: u32) -> Self {
256        self.tracer_provider = self.tracer_provider.with_max_events_per_span(max_events);
257        self
258    }
259
260    /// Specify the number of attributes to be recorded per span.
261    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_max_attributes_per_span]
262    pub fn with_max_attributes_per_span(mut self, max_attributes: u32) -> Self {
263        self.tracer_provider = self
264            .tracer_provider
265            .with_max_attributes_per_span(max_attributes);
266        self
267    }
268
269    /// Specify the number of events to be recorded per span.
270    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_max_links_per_span]
271    pub fn with_max_links_per_span(mut self, max_links: u32) -> Self {
272        self.tracer_provider = self.tracer_provider.with_max_links_per_span(max_links);
273        self
274    }
275
276    /// Specify the number of attributes one event can have.
277    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_max_attributes_per_event]
278    pub fn with_max_attributes_per_event(mut self, max_attributes: u32) -> Self {
279        self.tracer_provider = self
280            .tracer_provider
281            .with_max_attributes_per_event(max_attributes);
282        self
283    }
284
285    /// Specify the number of attributes one link can have.
286    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_max_attributes_per_link]
287    pub fn with_max_attributes_per_link(mut self, max_attributes: u32) -> Self {
288        self.tracer_provider = self
289            .tracer_provider
290            .with_max_attributes_per_link(max_attributes);
291        self
292    }
293
294    /// Specify all limit via the span_limits
295    /// See [opentelemetry_sdk::trace::TracerProviderBuilder::with_span_limits]
296    pub fn with_span_limits(mut self, span_limits: opentelemetry_sdk::trace::SpanLimits) -> Self {
297        self.tracer_provider = self.tracer_provider.with_span_limits(span_limits);
298        self
299    }
300}
301
302/// Initialize a new Datadog Tracing builder
303///
304/// # Usage
305///
306/// ```rust
307/// // Default configuration
308/// datadog_opentelemetry::tracing().init();
309/// ```
310///
311/// It is also possible to customize the datadog configuration passed to the tracer provider.
312///
313/// ```rust
314/// // Custom datadog configuration
315/// datadog_opentelemetry::tracing()
316///     .with_config(
317///         datadog_opentelemetry::configuration::Config::builder()
318///             .set_service("my_service".to_string())
319///             .set_env("my_env".to_string())
320///             .set_version("1.0.0".to_string())
321///             .build(),
322///     )
323///     .init();
324/// ```
325///
326/// Or to pass options to the OpenTelemetry SDK TracerProviderBuilder
327/// ```rust
328/// # #[derive(Debug)]
329/// # struct MySpanProcessor;
330/// #
331/// # impl opentelemetry_sdk::trace::SpanProcessor for MySpanProcessor {
332/// #     fn on_start(&self, span: &mut opentelemetry_sdk::trace::Span, cx: &opentelemetry::Context) {
333/// #     }
334/// #     fn on_end(&self, span: opentelemetry_sdk::trace::SpanData) {}
335/// #     fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult {
336/// #         Ok(())
337/// #     }
338/// #     fn shutdown_with_timeout(
339/// #         &self,
340/// #         timeout: std::time::Duration,
341/// #     ) -> opentelemetry_sdk::error::OTelSdkResult {
342/// #         Ok(())
343/// #     }
344/// #     fn set_resource(&mut self, _resource: &opentelemetry_sdk::Resource) {}
345/// # }
346/// #
347/// // Custom otel tracer sdk options
348/// datadog_opentelemetry::tracing()
349///     .with_max_attributes_per_span(64)
350///     // Custom span processor
351///     .with_span_processor(MySpanProcessor)
352///     .init();
353/// ```
354pub fn tracing() -> DatadogTracingBuilder {
355    DatadogTracingBuilder {
356        config: None,
357        tracer_provider: opentelemetry_sdk::trace::SdkTracerProvider::builder(),
358        resource: None,
359    }
360}
361
362/// Create an instance of the tracer provider
363fn make_tracer(
364    config: Arc<Config>,
365    mut tracer_provider_builder: opentelemetry_sdk::trace::TracerProviderBuilder,
366    resource: Option<Resource>,
367) -> (SdkTracerProvider, DatadogPropagator) {
368    let registry = TraceRegistry::new(config.clone());
369    let resource_slot = Arc::new(RwLock::new(Resource::builder_empty().build()));
370    // Sampler only needs config for initialization (reads initial sampling rules)
371    // Runtime updates come via config callback, so no need for shared config
372    let sampler = Sampler::new(config.clone(), resource_slot.clone(), registry.clone());
373
374    let agent_response_handler = sampler.on_agent_response();
375
376    let dd_resource = create_dd_resource(resource.unwrap_or(Resource::builder().build()), &config);
377    tracer_provider_builder = tracer_provider_builder.with_resource(dd_resource);
378    let propagator = DatadogPropagator::new(config.clone(), registry.clone());
379
380    if config.remote_config_enabled() {
381        let sampler_callback = sampler.on_rules_update();
382
383        config.set_sampling_rules_callback(move |update| match update {
384            RemoteConfigUpdate::SamplingRules(rules) => {
385                sampler_callback(rules);
386            }
387        });
388    };
389
390    let mut tracer_provider_builder = tracer_provider_builder
391        .with_sampler(sampler) // Use the sampler created above
392        .with_id_generator(trace_id::TraceidGenerator);
393    if config.enabled() {
394        let span_processor = DatadogSpanProcessor::new(
395            config.clone(),
396            registry.clone(),
397            resource_slot.clone(),
398            Some(agent_response_handler),
399        );
400        tracer_provider_builder = tracer_provider_builder.with_span_processor(span_processor);
401    }
402    let tracer_provider = tracer_provider_builder.build();
403
404    (tracer_provider, propagator)
405}
406
407fn merge_resource<I: IntoIterator<Item = (Key, Value)>>(
408    base: Option<Resource>,
409    additional: I,
410) -> Resource {
411    let mut builder = opentelemetry_sdk::Resource::builder_empty();
412    if let Some(base) = base {
413        if let Some(schema_url) = base.schema_url() {
414            builder = builder.with_schema_url(
415                base.iter()
416                    .map(|(k, v)| KeyValue::new(k.clone(), v.clone())),
417                schema_url.to_string(),
418            );
419        } else {
420            builder = builder.with_attributes(
421                base.iter()
422                    .map(|(k, v)| KeyValue::new(k.clone(), v.clone())),
423            );
424        }
425    }
426    builder = builder.with_attributes(additional.into_iter().map(|(k, v)| KeyValue::new(k, v)));
427    builder.build()
428}
429
430fn create_dd_resource(resource: Resource, cfg: &Config) -> Resource {
431    let otel_service_name: Option<Value> = resource.get(&Key::from_static_str(SERVICE_NAME));
432
433    // Collect attributes to add
434    let mut attributes = Vec::new();
435
436    // Handle service name
437    if otel_service_name.is_none() || otel_service_name.unwrap().as_str() == "unknown_service" {
438        // If the OpenTelemetry service name is not set or is "unknown_service",
439        // we override it with the Datadog service name.
440        attributes.push((
441            Key::from_static_str(SERVICE_NAME),
442            Value::from(cfg.service().to_string()),
443        ));
444    } else if !cfg.service_is_default() {
445        // If the service is configured, we override the OpenTelemetry service name
446        attributes.push((
447            Key::from_static_str(SERVICE_NAME),
448            Value::from(cfg.service().to_string()),
449        ));
450    }
451
452    // Handle environment - add it if configured and not already present
453    if let Some(env) = cfg.env() {
454        let otel_env: Option<Value> =
455            resource.get(&Key::from_static_str(DEPLOYMENT_ENVIRONMENT_NAME));
456        if otel_env.is_none() {
457            attributes.push((
458                Key::from_static_str(DEPLOYMENT_ENVIRONMENT_NAME),
459                Value::from(env.to_string()),
460            ));
461        }
462    }
463
464    if attributes.is_empty() {
465        // If no attributes to add, return the original resource
466        resource
467    } else {
468        merge_resource(Some(resource), attributes)
469    }
470}
471
472#[cfg(feature = "test-utils")]
473pub fn make_test_tracer(shared_config: Arc<Config>) -> (SdkTracerProvider, DatadogPropagator) {
474    make_tracer(
475        shared_config,
476        opentelemetry_sdk::trace::TracerProviderBuilder::default(),
477        None,
478    )
479}