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}