datadog_opentelemetry/core/configuration/
configuration.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use libdd_telemetry::data::Configuration;
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet, VecDeque};
7use std::ops::Deref;
8use std::sync::{Arc, Mutex};
9use std::{borrow::Cow, fmt::Display, str::FromStr, sync::OnceLock};
10
11use rustc_version_runtime::version;
12
13use crate::core::configuration::sources::{
14    CompositeConfigSourceResult, CompositeSource, ConfigKey, ConfigSourceOrigin,
15};
16use crate::core::configuration::supported_configurations::SupportedConfigurations;
17use crate::core::log::LevelFilter;
18use crate::core::telemetry;
19use crate::{dd_error, dd_warn};
20
21/// Different types of remote configuration updates that can trigger callbacks
22#[derive(Debug, Clone)]
23pub enum RemoteConfigUpdate {
24    /// Sampling rules were updated from remote configuration
25    SamplingRules(Vec<SamplingRuleConfig>),
26    // Future remote config update types should be added here as new variants.
27    // E.g.
28    // - FeatureFlags(HashMap<String, bool>)
29}
30
31/// Type alias for remote configuration callback functions
32/// This reduces type complexity and improves readability
33type RemoteConfigCallback = Box<dyn Fn(&RemoteConfigUpdate) + Send + Sync>;
34
35/// Struct-based callback system for remote configuration updates
36pub struct RemoteConfigCallbacks {
37    pub sampling_rules_update: Option<RemoteConfigCallback>,
38    // Future callback types can be added here as new fields
39    // e.g. pub feature_flags_update: Option<RemoteConfigCallback>,
40}
41
42impl std::fmt::Debug for RemoteConfigCallbacks {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("RemoteConfigCallbacks")
45            .field(
46                "sampling_rules_update",
47                &self.sampling_rules_update.as_ref().map(|_| "<callback>"),
48            )
49            .finish()
50    }
51}
52
53impl RemoteConfigCallbacks {
54    pub fn new() -> Self {
55        Self {
56            sampling_rules_update: None,
57        }
58    }
59
60    pub fn set_sampling_rules_callback<F>(&mut self, callback: F)
61    where
62        F: Fn(&RemoteConfigUpdate) + Send + Sync + 'static,
63    {
64        self.sampling_rules_update = Some(Box::new(callback));
65    }
66
67    /// Calls all relevant callbacks for the given update type
68    /// Provides a unified interface for future callback types
69    pub fn notify_update(&self, update: &RemoteConfigUpdate) {
70        match update {
71            RemoteConfigUpdate::SamplingRules(_) => {
72                if let Some(ref callback) = self.sampling_rules_update {
73                    callback(update);
74                }
75            } // Future update types can be handled here
76        }
77    }
78}
79
80impl Default for RemoteConfigCallbacks {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86/// Configuration for a single sampling rule
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
88pub struct SamplingRuleConfig {
89    /// The sample rate to apply (0.0-1.0)
90    pub sample_rate: f64,
91
92    /// Optional service name pattern to match
93    #[serde(default)]
94    pub service: Option<String>,
95
96    /// Optional span name pattern to match
97    #[serde(default)]
98    pub name: Option<String>,
99
100    /// Optional resource name pattern to match
101    #[serde(default)]
102    pub resource: Option<String>,
103
104    /// Tags that must match (key-value pairs)
105    #[serde(default)]
106    pub tags: HashMap<String, String>,
107
108    /// Where this rule comes from (customer, dynamic, default)
109    // TODO(paullgdc): this value should not be definable by customers
110    #[serde(default = "default_provenance")]
111    pub provenance: String,
112}
113
114impl Display for SamplingRuleConfig {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", serde_json::json!(self))
117    }
118}
119
120fn default_provenance() -> String {
121    "default".to_string()
122}
123
124pub const TRACER_VERSION: &str = env!("CARGO_PKG_VERSION");
125
126const DATADOG_TAGS_MAX_LENGTH: usize = 512;
127const RC_DEFAULT_POLL_INTERVAL: f64 = 5.0; // 5 seconds is the highest interval allowed by the spec
128
129#[derive(Debug, Default, Clone, PartialEq)]
130struct ParsedSamplingRules {
131    rules: Vec<SamplingRuleConfig>,
132}
133
134impl Deref for ParsedSamplingRules {
135    type Target = [SamplingRuleConfig];
136
137    fn deref(&self) -> &Self::Target {
138        &self.rules
139    }
140}
141
142impl From<ParsedSamplingRules> for Vec<SamplingRuleConfig> {
143    fn from(parsed: ParsedSamplingRules) -> Self {
144        parsed.rules
145    }
146}
147
148impl FromStr for ParsedSamplingRules {
149    type Err = serde_json::Error;
150
151    fn from_str(s: &str) -> Result<Self, Self::Err> {
152        if s.trim().is_empty() {
153            return Ok(ParsedSamplingRules::default());
154        }
155        // DD_TRACE_SAMPLING_RULES is expected to be a JSON array of SamplingRuleConfig objects.
156        let rules_vec: Vec<SamplingRuleConfig> = serde_json::from_str(s)?;
157        Ok(ParsedSamplingRules { rules: rules_vec })
158    }
159}
160
161impl Display for ParsedSamplingRules {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(
164            f,
165            "{}",
166            serde_json::to_string(&self.rules).unwrap_or_default()
167        )
168    }
169}
170
171enum ConfigItemRef<'a, T> {
172    Ref(&'a T),
173    ArcRef(arc_swap::Guard<Option<Arc<T>>>),
174}
175
176impl<T: Deref> Deref for ConfigItemRef<'_, T> {
177    type Target = T::Target;
178
179    fn deref(&self) -> &Self::Target {
180        match self {
181            ConfigItemRef::Ref(t) => t,
182            ConfigItemRef::ArcRef(guard) => guard.as_ref().unwrap(),
183        }
184    }
185}
186
187impl<T: ConfigurationValueProvider> ConfigurationValueProvider for ConfigItemRef<'_, T> {
188    fn get_configuration_value(&self) -> String {
189        match self {
190            ConfigItemRef::Ref(t) => t.get_configuration_value(),
191            ConfigItemRef::ArcRef(guard) => guard.as_ref().unwrap().get_configuration_value(),
192        }
193    }
194}
195
196/// A trait for providing configuration data for telemetry reporting.
197///
198/// This trait standardizes how configuration items expose their current state
199/// as `ddtelemetry::data::Configuration` payloads for telemetry collection.
200/// It enables the configuration system to report configuration values, their
201/// origins, and associated metadata to Datadog.
202pub trait ConfigurationProvider {
203    /// Returns a telemetry configuration object representing the current state of this
204    /// configuration item.
205    fn get_configuration(&self) -> Configuration;
206}
207
208/// A trait for converting configuration values to their string representation for telemetry.
209///
210/// This trait is used to serialize configuration values into strings that can be sent
211/// as part of telemetry data to Datadog. It provides a standardized way to convert
212/// various configuration types (primitives, enums, collections, etc.) into a string
213/// format suitable for the `ddtelemetry::data::payloads::Configuration` payload.
214///
215/// # Auto-Implementation
216///
217/// The trait is automatically implemented for common types using the `impl_config_value_provider!`
218/// macro:
219/// - Basic types: `bool`, `u32`, `i32`, `f64`, `Cow<'static, str>`, etc.
220/// - Option wrappers: `Option<String>`, etc.
221/// - Custom types: `ServiceName`, `LevelFilter`, `ParsedSamplingRules`, etc.
222///
223/// # Usage in Configuration System
224///
225/// This trait is primarily used by `ConfigItem<T>` and `ConfigItemWithOverride<T>`
226/// to serialize their current values for telemetry reporting, regardless of the value's source
227/// (default, environment variable, programmatic setting, or remote configuration).
228trait ConfigurationValueProvider {
229    /// Returns the string representation of this configuration value for telemetry reporting.
230    ///
231    /// This method should produce a concise, human-readable string that represents
232    /// the current value in a format suitable for debugging and telemetry analysis.
233    fn get_configuration_value(&self) -> String;
234}
235
236/// A trait for updating configuration values while tracking their origin source.
237///
238/// This trait provides a standardized interface for setting configuration values on
239/// configuration items while preserving information about where the value came from
240/// (environment variables, programmatic code, remote configuration, etc.). This source
241/// tracking is essential for implementing proper configuration precedence rules and
242/// for telemetry reporting.
243trait ValueSourceUpdater<T> {
244    fn name(&self) -> SupportedConfigurations;
245    /// Updates the configuration value while recording its source origin.
246    fn set_value_source(&mut self, value: T, source: ConfigSourceOrigin);
247}
248
249/// Configuration item that tracks the value of a setting and where it came from
250/// This allows us to manage configuration precedence
251#[derive(Debug)]
252struct ConfigItem<T: ConfigurationValueProvider> {
253    name: SupportedConfigurations,
254    default_value: T,
255    env_value: Option<T>,
256    code_value: Option<T>,
257    config_id: Option<String>,
258}
259
260impl<T: Clone + ConfigurationValueProvider> Clone for ConfigItem<T> {
261    fn clone(&self) -> Self {
262        Self {
263            name: self.name,
264            default_value: self.default_value.clone(),
265            env_value: self.env_value.clone(),
266            code_value: self.code_value.clone(),
267            config_id: self.config_id.clone(),
268        }
269    }
270}
271
272impl<T: Clone + ConfigurationValueProvider> ConfigItem<T> {
273    /// Creates a new ConfigItem with a default value
274    fn new(name: SupportedConfigurations, default: T) -> Self {
275        Self {
276            name,
277            default_value: default,
278            env_value: None,
279            code_value: None,
280            config_id: None,
281        }
282    }
283
284    /// Sets the code value (convenience method)
285    fn set_code(&mut self, value: T) {
286        self.code_value = Some(value);
287    }
288
289    /// Gets the current value based on priority:
290    /// code > env_var > default
291    fn value(&self) -> &T {
292        self.code_value
293            .as_ref()
294            .or(self.env_value.as_ref())
295            .unwrap_or(&self.default_value)
296    }
297
298    /// Gets the source of the current value
299    #[allow(dead_code)] // Used in tests and will be used for remote configuration
300    fn source(&self) -> ConfigSourceOrigin {
301        if self.code_value.is_some() {
302            ConfigSourceOrigin::Code
303        } else if self.env_value.is_some() {
304            ConfigSourceOrigin::EnvVar
305        } else {
306            ConfigSourceOrigin::Default
307        }
308    }
309}
310
311impl<T: Clone + ConfigurationValueProvider> ConfigurationProvider for ConfigItem<T> {
312    /// Gets a Configuration object used as telemetry payload
313    fn get_configuration(&self) -> Configuration {
314        Configuration {
315            name: self.name.as_str().to_string(),
316            value: self.value().get_configuration_value(),
317            origin: self.source().into(),
318            config_id: self.config_id.clone(),
319        }
320    }
321}
322
323impl<T: ConfigurationValueProvider> ValueSourceUpdater<T> for ConfigItem<T> {
324    fn name(&self) -> SupportedConfigurations {
325        self.name
326    }
327
328    /// Sets a value from a specific source
329    fn set_value_source(&mut self, value: T, source: ConfigSourceOrigin) {
330        match source {
331            ConfigSourceOrigin::Code => self.code_value = Some(value),
332            ConfigSourceOrigin::EnvVar => self.env_value = Some(value),
333            ConfigSourceOrigin::RemoteConfig => {
334                dd_warn!("Cannot set a value from RC");
335            }
336            ConfigSourceOrigin::Default => {
337                dd_warn!("Cannot set default value after initialization");
338            }
339        }
340    }
341}
342
343/// Configuration item that tracks the value of a setting and where it came from
344/// And allows to update the corresponding value with a ConfigSourceOrigin
345#[derive(Debug)]
346struct ConfigItemWithOverride<T: ConfigurationValueProvider + Deref> {
347    config_item: ConfigItem<T>,
348    override_value: arc_swap::ArcSwapOption<T>,
349    override_origin: ConfigSourceOrigin,
350    config_id: arc_swap::ArcSwapOption<String>,
351}
352
353impl<T: Clone + ConfigurationValueProvider + Deref> Clone for ConfigItemWithOverride<T> {
354    fn clone(&self) -> Self {
355        Self {
356            config_item: self.config_item.clone(),
357            override_value: arc_swap::ArcSwapOption::new(self.override_value.load_full()),
358            override_origin: self.override_origin,
359            config_id: arc_swap::ArcSwapOption::new(self.config_id.load_full()),
360        }
361    }
362}
363
364impl<T: ConfigurationValueProvider + Clone + Deref> ConfigItemWithOverride<T> {
365    fn new_code(name: SupportedConfigurations, default: T) -> Self {
366        Self {
367            config_item: ConfigItem::new(name, default),
368            override_value: arc_swap::ArcSwapOption::const_empty(),
369            override_origin: ConfigSourceOrigin::Code,
370            config_id: arc_swap::ArcSwapOption::const_empty(),
371        }
372    }
373
374    fn new_rc(name: SupportedConfigurations, default: T) -> Self {
375        Self {
376            config_item: ConfigItem::new(name, default),
377            override_value: arc_swap::ArcSwapOption::const_empty(),
378            override_origin: ConfigSourceOrigin::RemoteConfig,
379            config_id: arc_swap::ArcSwapOption::const_empty(),
380        }
381    }
382
383    fn source(&self) -> ConfigSourceOrigin {
384        if self.override_value.load().is_some() {
385            self.override_origin
386        } else {
387            self.config_item.source()
388        }
389    }
390
391    /// Replaces override value only if origin matches source_type
392    fn set_override_value(&self, value: T, source: ConfigSourceOrigin) {
393        if source == self.override_origin {
394            self.override_value.store(Some(Arc::new(value)));
395        }
396    }
397
398    fn set_config_id(&self, config_id: Option<String>) {
399        match config_id {
400            Some(id) => self.config_id.store(Some(Arc::new(id))),
401            None => self.config_id.store(None),
402        }
403    }
404
405    /// Unsets the override value
406    fn unset_override_value(&self) {
407        self.override_value.store(None);
408    }
409
410    /// Sets Code value only if source_type is Code
411    fn set_code(&mut self, value: T) {
412        self.set_value_source(value, ConfigSourceOrigin::Code);
413    }
414
415    /// Gets the current value based on priority:
416    /// remote_config > code > env_var > default
417    fn value(&self) -> ConfigItemRef<'_, T> {
418        let override_value = self.override_value.load();
419        if override_value.is_some() {
420            ConfigItemRef::ArcRef(override_value)
421        } else {
422            ConfigItemRef::Ref(self.config_item.value())
423        }
424    }
425}
426
427impl<T: Clone + ConfigurationValueProvider + Deref> ConfigurationProvider
428    for ConfigItemWithOverride<T>
429{
430    /// Gets a Configuration object used as telemetry payload
431    fn get_configuration(&self) -> Configuration {
432        let config_id = self.config_id.load().as_ref().map(|id| (**id).clone());
433        Configuration {
434            name: self.config_item.name.as_str().to_string(),
435            value: self.value().get_configuration_value(),
436            origin: self.source().into(),
437            config_id,
438        }
439    }
440}
441
442impl<T: Clone + ConfigurationValueProvider + Deref> ValueSourceUpdater<T>
443    for ConfigItemWithOverride<T>
444{
445    fn name(&self) -> SupportedConfigurations {
446        self.config_item.name()
447    }
448
449    /// Sets a value from a specific source
450    fn set_value_source(&mut self, value: T, source: ConfigSourceOrigin) {
451        if source == self.override_origin {
452            self.set_override_value(value, source);
453        } else {
454            self.config_item.set_value_source(value, source);
455        }
456    }
457}
458
459struct ConfigItemSourceUpdater<'a> {
460    sources: &'a CompositeSource,
461}
462
463impl ConfigItemSourceUpdater<'_> {
464    fn apply_result<ParsedConfig, RawConfig, ConfigItemType, F>(
465        &self,
466        mut item: ConfigItemType,
467        result: CompositeConfigSourceResult<RawConfig>,
468        transform: F,
469    ) -> ConfigItemType
470    where
471        ParsedConfig: Clone + ConfigurationValueProvider,
472        ConfigItemType: ValueSourceUpdater<ParsedConfig>,
473        F: FnOnce(RawConfig) -> ParsedConfig,
474    {
475        if !result.errors.is_empty() {
476            dd_error!(
477                "Configuration: Error parsing property {} - {:?}",
478                item.name().as_str(),
479                result.errors
480            );
481        }
482
483        if let Some(ConfigKey { value, origin }) = result.value {
484            item.set_value_source(transform(value), origin);
485        }
486        item
487    }
488
489    /// Updates a ConfigItem from sources with parsed value (no transformation)
490    fn update_parsed<ParsedConfig, ConfigItemType>(&self, default: ConfigItemType) -> ConfigItemType
491    where
492        ParsedConfig: Clone + FromStr + ConfigurationValueProvider,
493        ParsedConfig::Err: std::fmt::Display,
494        ConfigItemType: ValueSourceUpdater<ParsedConfig>,
495    {
496        let result = self.sources.get_parse::<ParsedConfig>(default.name());
497        self.apply_result(default, result, |value| value)
498    }
499
500    /// Updates a ConfigItem from sources string with transformation
501    pub fn update_string<ParsedConfig, ConfigItemType, F>(
502        &self,
503        default: ConfigItemType,
504        transform: F,
505    ) -> ConfigItemType
506    where
507        ParsedConfig: Clone + ConfigurationValueProvider,
508        ConfigItemType: ValueSourceUpdater<ParsedConfig>,
509        F: FnOnce(String) -> ParsedConfig,
510    {
511        let result = self.sources.get(default.name());
512        self.apply_result(default, result, transform)
513    }
514
515    /// Updates a ConfigItem from sources with parsed value and transformation
516    pub fn update_parsed_with_transform<ParsedConfig, RawConfig, ConfigItemType, F>(
517        &self,
518        default: ConfigItemType,
519        transform: F,
520    ) -> ConfigItemType
521    where
522        ParsedConfig: Clone + ConfigurationValueProvider,
523        RawConfig: FromStr,
524        RawConfig::Err: std::fmt::Display,
525        ConfigItemType: ValueSourceUpdater<ParsedConfig>,
526        F: FnOnce(RawConfig) -> ParsedConfig,
527    {
528        let result = self.sources.get_parse::<RawConfig>(default.name());
529        self.apply_result(default, result, transform)
530    }
531}
532
533/// Macro to implement ConfigurationValueProvider trait for types that implement Display
534macro_rules! impl_config_value_provider {
535  // Handle Option<T> specially
536  (option: $($type:ty),* $(,)?) => {
537      $(
538          impl ConfigurationValueProvider for Option<$type> {
539              fn get_configuration_value(&self) -> String {
540                  match self {
541                      Some(value) => value.to_string(),
542                      None => String::new(),
543                  }
544              }
545          }
546      )*
547  };
548
549  // Handle regular types
550  (simple: $($type:ty),* $(,)?) => {
551      $(
552          impl ConfigurationValueProvider for $type {
553              fn get_configuration_value(&self) -> String {
554                  self.to_string()
555              }
556          }
557      )*
558  };
559}
560
561type SamplingRulesConfigItem = ConfigItemWithOverride<ParsedSamplingRules>;
562
563/// Manages extra services discovered at runtime
564/// This is used to track services beyond the main service for remote configuration
565#[derive(Debug, Clone)]
566struct ExtraServicesTracker {
567    /// Services that have been discovered
568    extra_services: Arc<Mutex<HashSet<String>>>,
569    /// Services that have already been sent to the agent
570    extra_services_sent: Arc<Mutex<HashSet<String>>>,
571    /// Queue of new services to process
572    extra_services_queue: Arc<Mutex<Option<VecDeque<String>>>>,
573}
574
575impl ExtraServicesTracker {
576    fn new() -> Self {
577        Self {
578            extra_services: Arc::new(Mutex::new(HashSet::new())),
579            extra_services_sent: Arc::new(Mutex::new(HashSet::new())),
580            extra_services_queue: Arc::new(Mutex::new(Some(VecDeque::new()))),
581        }
582    }
583
584    fn add_extra_service(&self, service_name: &str, main_service: &str) {
585        if service_name == main_service {
586            return;
587        }
588
589        let mut sent = match self.extra_services_sent.lock() {
590            Ok(s) => s,
591            Err(_) => return,
592        };
593
594        if sent.contains(service_name) {
595            return;
596        }
597
598        let mut queue = match self.extra_services_queue.lock() {
599            Ok(q) => q,
600            Err(_) => return,
601        };
602
603        // Add to queue and mark as sent
604        if let Some(ref mut q) = *queue {
605            q.push_back(service_name.to_string());
606        }
607        sent.insert(service_name.to_string());
608    }
609
610    /// Get all extra services, updating from the queue
611    fn get_extra_services(&self) -> Vec<String> {
612        let mut queue = match self.extra_services_queue.lock() {
613            Ok(q) => q,
614            Err(_) => return Vec::new(),
615        };
616
617        let mut services = match self.extra_services.lock() {
618            Ok(s) => s,
619            Err(_) => return Vec::new(),
620        };
621
622        // Drain the queue into extra_services
623        if let Some(ref mut q) = *queue {
624            while let Some(service) = q.pop_front() {
625                services.insert(service);
626
627                // Limit to 64 services
628                if services.len() > 64 {
629                    // Remove one arbitrary service (HashSet doesn't guarantee order)
630                    if let Some(to_remove) = services.iter().next().cloned() {
631                        dd_warn!("ExtraServicesTracker:RemoteConfig: Exceeded 64 service limit, removing service: {}", to_remove);
632                        services.remove(&to_remove);
633                    }
634                }
635            }
636        }
637
638        services.iter().cloned().collect()
639    }
640}
641
642#[derive(Clone, Copy, Debug, PartialEq, Eq)]
643pub enum TracePropagationStyle {
644    Datadog,
645    TraceContext,
646    None,
647}
648
649impl TracePropagationStyle {
650    fn from_tags(tags: Option<Vec<String>>) -> Option<Vec<TracePropagationStyle>> {
651        match tags {
652            Some(tags) if !tags.is_empty() => Some(
653                tags.iter()
654                    .filter_map(|value| match TracePropagationStyle::from_str(value) {
655                        Ok(style) => Some(style),
656                        Err(err) => {
657                            dd_warn!("Error parsing: {err}");
658                            None
659                        }
660                    })
661                    .collect::<Vec<TracePropagationStyle>>(),
662            ),
663            Some(_) => None,
664            None => None,
665        }
666    }
667}
668
669impl FromStr for TracePropagationStyle {
670    type Err = String;
671
672    fn from_str(s: &str) -> Result<Self, Self::Err> {
673        match s.trim().to_lowercase().as_str() {
674            "datadog" => Ok(TracePropagationStyle::Datadog),
675            "tracecontext" => Ok(TracePropagationStyle::TraceContext),
676            "none" => Ok(TracePropagationStyle::None),
677            _ => Err(format!("Unknown trace propagation style: '{s}'")),
678        }
679    }
680}
681
682impl Display for TracePropagationStyle {
683    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684        let style = match self {
685            TracePropagationStyle::Datadog => "datadog",
686            TracePropagationStyle::TraceContext => "tracecontext",
687            TracePropagationStyle::None => "none",
688        };
689        write!(f, "{style}")
690    }
691}
692
693#[derive(Debug, Clone)]
694enum ServiceName {
695    Default,
696    Configured(String),
697}
698
699impl ServiceName {
700    fn is_default(&self) -> bool {
701        matches!(self, ServiceName::Default)
702    }
703
704    fn as_str(&self) -> &str {
705        match self {
706            ServiceName::Default => "unnamed-rust-service",
707            ServiceName::Configured(name) => name,
708        }
709    }
710}
711
712impl std::ops::Deref for ServiceName {
713    type Target = str;
714
715    fn deref(&self) -> &Self::Target {
716        self.as_str()
717    }
718}
719
720impl Display for ServiceName {
721    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722        write!(f, "{}", self.as_str())
723    }
724}
725
726impl ConfigurationValueProvider for Vec<(String, String)> {
727    fn get_configuration_value(&self) -> String {
728        self.iter()
729            .map(|(key, value)| format!("{key}:{value}"))
730            .collect::<Vec<_>>()
731            .join(",")
732    }
733}
734
735impl ConfigurationValueProvider for Option<Vec<TracePropagationStyle>> {
736    fn get_configuration_value(&self) -> String {
737        match &self {
738            Some(styles) => styles
739                .iter()
740                .map(|style| style.to_string())
741                .collect::<Vec<_>>()
742                .join(","),
743            None => "".to_string(),
744        }
745    }
746}
747
748impl_config_value_provider!(simple: Cow<'static, str>, bool, u32, usize, i32, f64, ServiceName, LevelFilter, ParsedSamplingRules);
749impl_config_value_provider!(option: String);
750
751#[derive(Clone)]
752#[non_exhaustive]
753/// Configuration for the Datadog Tracer
754///
755/// # Usage
756/// ```
757/// use datadog_opentelemetry::configuration::Config;
758///
759///
760/// let config = Config::builder() // This pulls configuration from the environment and other sources
761///     .set_service("my-service".to_string()) // Override service name
762///     .set_version("1.0.0".to_string()) // Override version
763/// .build();
764/// ```
765pub struct Config {
766    // # Global
767    runtime_id: &'static str,
768
769    // # Tracer
770    tracer_version: &'static str,
771    language_version: String,
772    language: &'static str,
773
774    // # Service tagging
775    service: ConfigItemWithOverride<ServiceName>,
776    env: ConfigItem<Option<String>>,
777    version: ConfigItem<Option<String>>,
778
779    // # Agent
780    /// A list of default tags to be added to every span
781    /// If DD_ENV or DD_VERSION is used, it overrides any env or version tag defined in DD_TAGS
782    global_tags: ConfigItem<Vec<(String, String)>>,
783    /// host of the trace agent
784    agent_host: ConfigItem<Cow<'static, str>>,
785    /// port of the trace agent
786    trace_agent_port: ConfigItem<u32>,
787    /// url of the trace agent
788    trace_agent_url: ConfigItem<Cow<'static, str>>,
789    /// host of the dogstatsd agent
790    dogstatsd_agent_host: ConfigItem<Cow<'static, str>>,
791    /// port of the dogstatsd agent
792    dogstatsd_agent_port: ConfigItem<u32>,
793    /// url of the dogstatsd agent
794    dogstatsd_agent_url: ConfigItem<Cow<'static, str>>,
795
796    // # Sampling
797    ///  A list of sampling rules. Each rule is matched against the root span of a trace
798    /// If a rule matches, the trace is sampled with the associated sample rate.
799    trace_sampling_rules: SamplingRulesConfigItem,
800
801    /// Maximum number of spans to sample per second
802    /// Only applied if trace_sampling_rules are matched
803    trace_rate_limit: ConfigItem<i32>,
804
805    /// Disables the library if this is false
806    enabled: ConfigItem<bool>,
807    /// The log level filter for the tracer
808    log_level_filter: ConfigItem<LevelFilter>,
809
810    /// Whether to enable stats computation for the tracer
811    /// Results in dropped spans not being sent to the agent
812    trace_stats_computation_enabled: ConfigItem<bool>,
813
814    /// Configurations for testing. Not exposed to customer
815    #[cfg(feature = "test-utils")]
816    wait_agent_info_ready: bool,
817
818    // # Telemetry configuration
819    /// Disables telemetry if false
820    telemetry_enabled: ConfigItem<bool>,
821    /// Disables telemetry log collection if false.
822    telemetry_log_collection_enabled: ConfigItem<bool>,
823    /// Interval by which telemetry events are flushed (seconds)
824    telemetry_heartbeat_interval: ConfigItem<f64>,
825
826    /// Partial flush
827    trace_partial_flush_enabled: ConfigItem<bool>,
828    trace_partial_flush_min_spans: ConfigItem<usize>,
829
830    /// Trace propagation configuration
831    trace_propagation_style: ConfigItem<Option<Vec<TracePropagationStyle>>>,
832    trace_propagation_style_extract: ConfigItem<Option<Vec<TracePropagationStyle>>>,
833    trace_propagation_style_inject: ConfigItem<Option<Vec<TracePropagationStyle>>>,
834    trace_propagation_extract_first: ConfigItem<bool>,
835
836    /// Whether remote configuration is enabled
837    remote_config_enabled: ConfigItem<bool>,
838
839    /// Interval by with remote configuration is polled (seconds)
840    /// 5 seconds is the highest interval allowed by the spec
841    remote_config_poll_interval: ConfigItem<f64>,
842
843    /// Tracks extra services discovered at runtime
844    /// Used for remote configuration to report all services
845    extra_services_tracker: ExtraServicesTracker,
846
847    /// General callbacks to be called when configuration is updated from remote configuration
848    /// Allows components like the DatadogSampler to be updated without circular imports
849    remote_config_callbacks: Arc<Mutex<RemoteConfigCallbacks>>,
850
851    /// Max length of x-datadog-tags header. It only accepts values between 0 and 512.
852    /// The default value is 512 and x-datadog-tags header is not injected if value is 0.
853    datadog_tags_max_length: ConfigItem<usize>,
854}
855
856impl Config {
857    fn from_sources(sources: &CompositeSource) -> Self {
858        let default = default_config();
859
860        /// Wrapper to parse "," separated string to vector
861        struct DdTags(Vec<String>);
862
863        impl FromStr for DdTags {
864            type Err = &'static str;
865
866            fn from_str(s: &str) -> Result<Self, Self::Err> {
867                Ok(DdTags(
868                    s.split(',').map(|s| s.to_string()).collect::<Vec<String>>(),
869                ))
870            }
871        }
872
873        /// Wrapper to parse "," separated key:value tags to vector<(key, value)>
874        /// discarding tags without ":" delimiter
875        struct DdKeyValueTags(Vec<(String, String)>);
876
877        impl FromStr for DdKeyValueTags {
878            type Err = &'static str;
879
880            fn from_str(s: &str) -> Result<Self, Self::Err> {
881                Ok(DdKeyValueTags(
882                    s.split(',')
883                        .filter_map(|s| {
884                            s.split_once(':')
885                                .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
886                        })
887                        .collect(),
888                ))
889            }
890        }
891
892        let parsed_sampling_rules_config = sources
893            .get_parse::<ParsedSamplingRules>(SupportedConfigurations::DD_TRACE_SAMPLING_RULES);
894
895        let mut sampling_rules_item = ConfigItemWithOverride::new_rc(
896            parsed_sampling_rules_config.name,
897            ParsedSamplingRules::default(), // default is empty rules
898        );
899
900        // Set env value if it was parsed from environment
901        if let Some(rules) = parsed_sampling_rules_config.value {
902            sampling_rules_item.set_value_source(rules.value, rules.origin);
903        }
904
905        let cisu = ConfigItemSourceUpdater { sources };
906
907        Self {
908            runtime_id: default.runtime_id,
909            tracer_version: default.tracer_version,
910            language_version: default.language_version,
911            language: default.language,
912            service: cisu.update_string(default.service, ServiceName::Configured),
913            env: cisu.update_string(default.env, Some),
914            version: cisu.update_string(default.version, Some),
915            // TODO(paullgdc): tags should be merged, not replaced
916            global_tags: cisu
917                .update_parsed_with_transform(default.global_tags, |DdKeyValueTags(tags)| tags),
918            agent_host: cisu.update_string(default.agent_host, Cow::Owned),
919            trace_agent_port: cisu.update_parsed(default.trace_agent_port),
920            trace_agent_url: cisu.update_string(default.trace_agent_url, Cow::Owned),
921            dogstatsd_agent_host: cisu.update_string(default.dogstatsd_agent_host, Cow::Owned),
922            dogstatsd_agent_port: cisu.update_parsed(default.dogstatsd_agent_port),
923            dogstatsd_agent_url: cisu.update_string(default.dogstatsd_agent_url, Cow::Owned),
924
925            trace_partial_flush_enabled: cisu.update_parsed(default.trace_partial_flush_enabled),
926            trace_partial_flush_min_spans: cisu
927                .update_parsed(default.trace_partial_flush_min_spans),
928
929            // Use the initialized ConfigItem
930            trace_sampling_rules: sampling_rules_item,
931            trace_rate_limit: cisu.update_parsed(default.trace_rate_limit),
932
933            enabled: cisu.update_parsed(default.enabled),
934            log_level_filter: cisu.update_parsed(default.log_level_filter),
935            trace_stats_computation_enabled: cisu
936                .update_parsed(default.trace_stats_computation_enabled),
937            telemetry_enabled: cisu.update_parsed(default.telemetry_enabled),
938            telemetry_log_collection_enabled: cisu
939                .update_parsed(default.telemetry_log_collection_enabled),
940            telemetry_heartbeat_interval: cisu.update_parsed_with_transform(
941                default.telemetry_heartbeat_interval,
942                |interval: f64| interval.abs(),
943            ),
944            trace_propagation_style: cisu
945                .update_parsed_with_transform(default.trace_propagation_style, |DdTags(tags)| {
946                    TracePropagationStyle::from_tags(Some(tags))
947                }),
948            trace_propagation_style_extract: cisu.update_parsed_with_transform(
949                default.trace_propagation_style_extract,
950                |DdTags(tags)| TracePropagationStyle::from_tags(Some(tags)),
951            ),
952            trace_propagation_style_inject: cisu.update_parsed_with_transform(
953                default.trace_propagation_style_inject,
954                |DdTags(tags)| TracePropagationStyle::from_tags(Some(tags)),
955            ),
956            trace_propagation_extract_first: cisu
957                .update_parsed(default.trace_propagation_extract_first),
958            #[cfg(feature = "test-utils")]
959            wait_agent_info_ready: default.wait_agent_info_ready,
960            extra_services_tracker: ExtraServicesTracker::new(),
961            remote_config_enabled: cisu.update_parsed(default.remote_config_enabled),
962            remote_config_poll_interval: cisu.update_parsed_with_transform(
963                default.remote_config_poll_interval,
964                |interval: f64| interval.abs().min(RC_DEFAULT_POLL_INTERVAL),
965            ),
966            remote_config_callbacks: Arc::new(Mutex::new(RemoteConfigCallbacks::new())),
967            datadog_tags_max_length: cisu
968                .update_parsed_with_transform(default.datadog_tags_max_length, |max: usize| {
969                    max.min(DATADOG_TAGS_MAX_LENGTH)
970                }),
971        }
972    }
973
974    fn builder_with_sources(sources: &CompositeSource) -> ConfigBuilder {
975        ConfigBuilder {
976            config: Config::from_sources(sources),
977        }
978    }
979
980    /// Creates a new builder to set overrides detected configuration
981    pub fn builder() -> ConfigBuilder {
982        Self::builder_with_sources(&CompositeSource::default_sources())
983    }
984
985    pub(crate) fn get_telemetry_configuration(&self) -> Vec<&dyn ConfigurationProvider> {
986        vec![
987            &self.service,
988            &self.env,
989            &self.version,
990            &self.global_tags,
991            &self.agent_host,
992            &self.trace_agent_port,
993            &self.trace_agent_url,
994            &self.dogstatsd_agent_host,
995            &self.dogstatsd_agent_port,
996            &self.dogstatsd_agent_url,
997            &self.trace_sampling_rules,
998            &self.trace_rate_limit,
999            &self.enabled,
1000            &self.log_level_filter,
1001            &self.trace_stats_computation_enabled,
1002            &self.telemetry_enabled,
1003            &self.telemetry_log_collection_enabled,
1004            &self.telemetry_heartbeat_interval,
1005            &self.trace_partial_flush_enabled,
1006            &self.trace_partial_flush_min_spans,
1007            &self.trace_propagation_style,
1008            &self.trace_propagation_style_extract,
1009            &self.trace_propagation_style_inject,
1010            &self.trace_propagation_extract_first,
1011            &self.remote_config_enabled,
1012            &self.remote_config_poll_interval,
1013            &self.datadog_tags_max_length,
1014        ]
1015    }
1016
1017    pub fn runtime_id(&self) -> &str {
1018        self.runtime_id
1019    }
1020
1021    pub fn tracer_version(&self) -> &str {
1022        self.tracer_version
1023    }
1024
1025    pub fn language(&self) -> &str {
1026        self.language
1027    }
1028
1029    pub fn language_version(&self) -> &str {
1030        self.language_version.as_str()
1031    }
1032
1033    pub fn service(&self) -> impl Deref<Target = str> + use<'_> {
1034        self.service.value()
1035    }
1036
1037    pub fn service_is_default(&self) -> bool {
1038        match self.service.value() {
1039            ConfigItemRef::Ref(t) => t.is_default(),
1040            ConfigItemRef::ArcRef(guard) => guard.as_ref().unwrap().is_default(),
1041        }
1042    }
1043
1044    pub fn env(&self) -> Option<&str> {
1045        self.env.value().as_deref()
1046    }
1047
1048    pub fn version(&self) -> Option<&str> {
1049        self.version.value().as_deref()
1050    }
1051
1052    pub fn global_tags(&self) -> impl Iterator<Item = (&str, &str)> {
1053        self.global_tags
1054            .value()
1055            .iter()
1056            .map(|tag| (tag.0.as_str(), tag.1.as_str()))
1057    }
1058
1059    pub fn trace_agent_url(&self) -> &Cow<'static, str> {
1060        self.trace_agent_url.value()
1061    }
1062
1063    pub fn dogstatsd_agent_host(&self) -> &Cow<'static, str> {
1064        self.dogstatsd_agent_host.value()
1065    }
1066
1067    pub fn dogstatsd_agent_port(&self) -> &u32 {
1068        self.dogstatsd_agent_port.value()
1069    }
1070
1071    pub fn dogstatsd_agent_url(&self) -> &Cow<'static, str> {
1072        self.dogstatsd_agent_url.value()
1073    }
1074
1075    pub fn trace_sampling_rules(&self) -> impl Deref<Target = [SamplingRuleConfig]> + use<'_> {
1076        self.trace_sampling_rules.value()
1077    }
1078
1079    pub fn trace_rate_limit(&self) -> i32 {
1080        *self.trace_rate_limit.value()
1081    }
1082
1083    pub fn enabled(&self) -> bool {
1084        *self.enabled.value()
1085    }
1086
1087    pub fn log_level_filter(&self) -> &LevelFilter {
1088        self.log_level_filter.value()
1089    }
1090
1091    pub fn trace_stats_computation_enabled(&self) -> bool {
1092        *self.trace_stats_computation_enabled.value()
1093    }
1094
1095    #[cfg(feature = "test-utils")]
1096    pub fn __internal_wait_agent_info_ready(&self) -> bool {
1097        self.wait_agent_info_ready
1098    }
1099
1100    /// Static runtime id if the process
1101    fn process_runtime_id() -> &'static str {
1102        // TODO(paullgdc): Regenerate on fork? Would we even support forks?
1103        static RUNTIME_ID: OnceLock<String> = OnceLock::new();
1104        RUNTIME_ID.get_or_init(|| uuid::Uuid::new_v4().to_string())
1105    }
1106
1107    pub fn telemetry_enabled(&self) -> bool {
1108        *self.telemetry_enabled.value()
1109    }
1110
1111    pub fn telemetry_log_collection_enabled(&self) -> bool {
1112        *self.telemetry_log_collection_enabled.value()
1113    }
1114
1115    pub fn telemetry_heartbeat_interval(&self) -> f64 {
1116        *self.telemetry_heartbeat_interval.value()
1117    }
1118
1119    pub fn trace_partial_flush_enabled(&self) -> bool {
1120        *self.trace_partial_flush_enabled.value()
1121    }
1122
1123    pub fn trace_partial_flush_min_spans(&self) -> usize {
1124        *self.trace_partial_flush_min_spans.value()
1125    }
1126
1127    pub fn trace_propagation_style(&self) -> Option<&[TracePropagationStyle]> {
1128        self.trace_propagation_style.value().as_deref()
1129    }
1130
1131    pub fn trace_propagation_style_extract(&self) -> Option<&[TracePropagationStyle]> {
1132        self.trace_propagation_style_extract.value().as_deref()
1133    }
1134
1135    pub fn trace_propagation_style_inject(&self) -> Option<&[TracePropagationStyle]> {
1136        self.trace_propagation_style_inject.value().as_deref()
1137    }
1138
1139    pub fn trace_propagation_extract_first(&self) -> bool {
1140        *self.trace_propagation_extract_first.value()
1141    }
1142
1143    pub(crate) fn update_sampling_rules_from_remote(
1144        &self,
1145        rules_json: &str,
1146        config_id: Option<String>,
1147    ) -> Result<(), String> {
1148        // Parse the JSON into SamplingRuleConfig objects
1149        let rules: Vec<SamplingRuleConfig> = serde_json::from_str(rules_json)
1150            .map_err(|e| format!("Failed to parse sampling rules JSON: {e}"))?;
1151
1152        // If remote config sends empty rules, clear remote config to fall back to local rules
1153        if rules.is_empty() {
1154            self.clear_remote_sampling_rules(config_id);
1155        } else {
1156            self.trace_sampling_rules.set_override_value(
1157                ParsedSamplingRules { rules },
1158                ConfigSourceOrigin::RemoteConfig,
1159            );
1160            self.trace_sampling_rules.set_config_id(config_id);
1161
1162            // Notify callbacks about the sampling rules update
1163            self.remote_config_callbacks.lock().unwrap().notify_update(
1164                &RemoteConfigUpdate::SamplingRules(self.trace_sampling_rules().to_vec()),
1165            );
1166
1167            telemetry::notify_configuration_update(&self.trace_sampling_rules);
1168        }
1169
1170        Ok(())
1171    }
1172
1173    pub(crate) fn update_service_name(&self, service_name: Option<String>) {
1174        if let Some(service_name) = service_name {
1175            self.service.set_override_value(
1176                ServiceName::Configured(service_name),
1177                ConfigSourceOrigin::Code,
1178            );
1179        }
1180    }
1181
1182    pub(crate) fn clear_remote_sampling_rules(&self, config_id: Option<String>) {
1183        self.trace_sampling_rules.unset_override_value();
1184        self.trace_sampling_rules.set_config_id(config_id);
1185
1186        self.remote_config_callbacks.lock().unwrap().notify_update(
1187            &RemoteConfigUpdate::SamplingRules(self.trace_sampling_rules().to_vec()),
1188        );
1189
1190        telemetry::notify_configuration_update(&self.trace_sampling_rules);
1191    }
1192
1193    /// Add a callback to be called when sampling rules are updated via remote configuration
1194    /// This allows components like DatadogSampler to be updated without circular imports
1195    ///
1196    /// # Arguments
1197    /// * `callback` - The function to call when sampling rules are updated (receives
1198    ///   RemoteConfigUpdate enum)
1199    pub(crate) fn set_sampling_rules_callback<F>(&self, callback: F)
1200    where
1201        F: Fn(&RemoteConfigUpdate) + Send + Sync + 'static,
1202    {
1203        self.remote_config_callbacks
1204            .lock()
1205            .unwrap()
1206            .set_sampling_rules_callback(callback);
1207    }
1208
1209    /// Add an extra service discovered at runtime
1210    /// This is used for remote configuration
1211    pub(crate) fn add_extra_service(&self, service_name: &str) {
1212        if !self.remote_config_enabled() {
1213            return;
1214        }
1215        self.extra_services_tracker
1216            .add_extra_service(service_name, &self.service());
1217    }
1218
1219    /// Get all extra services discovered at runtime
1220    pub(crate) fn get_extra_services(&self) -> Vec<String> {
1221        if !self.remote_config_enabled() {
1222            return Vec::new();
1223        }
1224        self.extra_services_tracker.get_extra_services()
1225    }
1226
1227    /// Check if remote configuration is enabled
1228    pub fn remote_config_enabled(&self) -> bool {
1229        *self.remote_config_enabled.value()
1230    }
1231
1232    /// Get RC poll interval (seconds)
1233    pub fn remote_config_poll_interval(&self) -> f64 {
1234        *self.remote_config_poll_interval.value()
1235    }
1236
1237    /// Return tags max length
1238    pub fn datadog_tags_max_length(&self) -> usize {
1239        *self.datadog_tags_max_length.value()
1240    }
1241}
1242
1243impl std::fmt::Debug for Config {
1244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1245        f.debug_struct("Config")
1246            .field("runtime_id", &self.runtime_id)
1247            .field("tracer_version", &self.tracer_version)
1248            .field("language_version", &self.language_version)
1249            .field("service", &self.service)
1250            .field("env", &self.env)
1251            .field("version", &self.version)
1252            .field("global_tags", &self.global_tags)
1253            .field("trace_agent_url", &self.trace_agent_url)
1254            .field("dogstatsd_agent_url", &self.dogstatsd_agent_url)
1255            .field("trace_sampling_rules", &self.trace_sampling_rules)
1256            .field("trace_rate_limit", &self.trace_rate_limit)
1257            .field("enabled", &self.enabled)
1258            .field("log_level_filter", &self.log_level_filter)
1259            .field(
1260                "trace_stats_computation_enabled",
1261                &self.trace_stats_computation_enabled,
1262            )
1263            .field("trace_propagation_style", &self.trace_propagation_style)
1264            .field(
1265                "trace_propagation_style_extract",
1266                &self.trace_propagation_style_extract,
1267            )
1268            .field(
1269                "trace_propagation_style_inject",
1270                &self.trace_propagation_style_inject,
1271            )
1272            .field(
1273                "trace_propagation_extract_first",
1274                &self.trace_propagation_extract_first,
1275            )
1276            .field("extra_services_tracker", &self.extra_services_tracker)
1277            .field("remote_config_enabled", &self.remote_config_enabled)
1278            .field(
1279                "remote_config_poll_interval",
1280                &self.remote_config_poll_interval,
1281            )
1282            .field("remote_config_callbacks", &self.remote_config_callbacks)
1283            .finish()
1284    }
1285}
1286
1287fn default_config() -> Config {
1288    Config {
1289        runtime_id: Config::process_runtime_id(),
1290        env: ConfigItem::new(SupportedConfigurations::DD_ENV, None),
1291        // TODO(paullgdc): Default service naming detection, probably from arg0
1292        service: ConfigItemWithOverride::new_code(
1293            SupportedConfigurations::DD_SERVICE,
1294            ServiceName::Default,
1295        ),
1296        version: ConfigItem::new(SupportedConfigurations::DD_VERSION, None),
1297        global_tags: ConfigItem::new(SupportedConfigurations::DD_TAGS, Vec::new()),
1298
1299        agent_host: ConfigItem::new(
1300            SupportedConfigurations::DD_AGENT_HOST,
1301            Cow::Borrowed("localhost"),
1302        ),
1303        trace_agent_port: ConfigItem::new(SupportedConfigurations::DD_TRACE_AGENT_PORT, 8126),
1304        trace_agent_url: ConfigItem::new(
1305            SupportedConfigurations::DD_TRACE_AGENT_URL,
1306            Cow::Borrowed(""),
1307        ),
1308        dogstatsd_agent_host: ConfigItem::new(
1309            SupportedConfigurations::DD_DOGSTATSD_HOST,
1310            Cow::Borrowed("localhost"),
1311        ),
1312        dogstatsd_agent_port: ConfigItem::new(SupportedConfigurations::DD_DOGSTATSD_PORT, 8125),
1313        dogstatsd_agent_url: ConfigItem::new(
1314            SupportedConfigurations::DD_DOGSTATSD_URL,
1315            Cow::Borrowed(""),
1316        ),
1317        trace_sampling_rules: ConfigItemWithOverride::new_rc(
1318            SupportedConfigurations::DD_TRACE_SAMPLING_RULES,
1319            ParsedSamplingRules::default(), // Empty rules by default
1320        ),
1321        trace_rate_limit: ConfigItem::new(SupportedConfigurations::DD_TRACE_RATE_LIMIT, 100),
1322        enabled: ConfigItem::new(SupportedConfigurations::DD_TRACE_ENABLED, true),
1323        log_level_filter: ConfigItem::new(
1324            SupportedConfigurations::DD_LOG_LEVEL,
1325            LevelFilter::default(),
1326        ),
1327        tracer_version: TRACER_VERSION,
1328        language: "rust",
1329        language_version: version().to_string(),
1330        trace_stats_computation_enabled: ConfigItem::new(
1331            SupportedConfigurations::DD_TRACE_STATS_COMPUTATION_ENABLED,
1332            true,
1333        ),
1334        #[cfg(feature = "test-utils")]
1335        wait_agent_info_ready: false,
1336
1337        telemetry_enabled: ConfigItem::new(
1338            SupportedConfigurations::DD_INSTRUMENTATION_TELEMETRY_ENABLED,
1339            true,
1340        ),
1341        telemetry_log_collection_enabled: ConfigItem::new(
1342            SupportedConfigurations::DD_TELEMETRY_LOG_COLLECTION_ENABLED,
1343            true,
1344        ),
1345        telemetry_heartbeat_interval: ConfigItem::new(
1346            SupportedConfigurations::DD_TELEMETRY_HEARTBEAT_INTERVAL,
1347            60.0,
1348        ),
1349        trace_partial_flush_enabled: ConfigItem::new(
1350            SupportedConfigurations::DD_TRACE_PARTIAL_FLUSH_ENABLED,
1351            false,
1352        ),
1353        trace_partial_flush_min_spans: ConfigItem::new(
1354            SupportedConfigurations::DD_TRACE_PARTIAL_FLUSH_MIN_SPANS,
1355            300,
1356        ),
1357        trace_propagation_style: ConfigItem::new(
1358            SupportedConfigurations::DD_TRACE_PROPAGATION_STYLE,
1359            Some(vec![
1360                TracePropagationStyle::Datadog,
1361                TracePropagationStyle::TraceContext,
1362            ]),
1363        ),
1364        trace_propagation_style_extract: ConfigItem::new(
1365            SupportedConfigurations::DD_TRACE_PROPAGATION_STYLE_EXTRACT,
1366            None,
1367        ),
1368        trace_propagation_style_inject: ConfigItem::new(
1369            SupportedConfigurations::DD_TRACE_PROPAGATION_STYLE_INJECT,
1370            None,
1371        ),
1372        trace_propagation_extract_first: ConfigItem::new(
1373            SupportedConfigurations::DD_TRACE_PROPAGATION_EXTRACT_FIRST,
1374            false,
1375        ),
1376        extra_services_tracker: ExtraServicesTracker::new(),
1377        remote_config_enabled: ConfigItem::new(
1378            SupportedConfigurations::DD_REMOTE_CONFIGURATION_ENABLED,
1379            true,
1380        ),
1381        remote_config_poll_interval: ConfigItem::new(
1382            SupportedConfigurations::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS,
1383            RC_DEFAULT_POLL_INTERVAL,
1384        ),
1385        remote_config_callbacks: Arc::new(Mutex::new(RemoteConfigCallbacks::new())),
1386        datadog_tags_max_length: ConfigItem::new(
1387            SupportedConfigurations::DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
1388            DATADOG_TAGS_MAX_LENGTH,
1389        ),
1390    }
1391}
1392
1393pub struct ConfigBuilder {
1394    config: Config,
1395}
1396
1397impl ConfigBuilder {
1398    /// Finalizes the builder and returns the configuration
1399    pub fn build(&self) -> Config {
1400        crate::core::log::set_max_level(*self.config.log_level_filter.value());
1401        let mut config = self.config.clone();
1402
1403        // resolve trace_agent_url
1404        if config.trace_agent_url.value().is_empty() {
1405            let host = &config.agent_host.value();
1406            let port = *config.trace_agent_port.value();
1407            config
1408                .trace_agent_url
1409                .set_code(Cow::Owned(format!("http://{host}:{port}")));
1410        }
1411
1412        // resolve dogstatsd_agent_url
1413        if config.dogstatsd_agent_url.value().is_empty() {
1414            let host = &config.dogstatsd_agent_host.value();
1415            let port = *config.dogstatsd_agent_port.value();
1416            config
1417                .dogstatsd_agent_url
1418                .set_code(Cow::Owned(format!("http://{host}:{port}")));
1419        }
1420
1421        config
1422    }
1423
1424    /// Sets the service name for your application
1425    ///
1426    /// **Default**: `unnamed-rust-service`
1427    ///
1428    /// Env variable: `DD_SERVICE`
1429    pub fn set_service(&mut self, service: String) -> &mut Self {
1430        self.config
1431            .service
1432            .set_code(ServiceName::Configured(service));
1433        self
1434    }
1435
1436    /// Set the application's environment, for example: `prod`, `staging`.
1437    ///
1438    /// **Default**: `(none)`
1439    ///
1440    /// Env variable: `DD_ENV`
1441    pub fn set_env(&mut self, env: String) -> &mut Self {
1442        self.config.env.set_code(Some(env));
1443        self
1444    }
1445
1446    /// Set the application's version, for example: `1.2.3` or `6c44da20`.
1447    ///
1448    /// **Default**: `(none)`
1449    ///
1450    /// Env variable: `DD_VERSION`
1451    pub fn set_version(&mut self, version: String) -> &mut Self {
1452        self.config.version.set_code(Some(version));
1453        self
1454    }
1455
1456    /// A list of default tags to be added to every span, in `(key, value)` format. Example:
1457    /// `[(layer, api), (team, intake)]`.
1458    ///
1459    /// **Default**: `(none)`
1460    ///
1461    /// Env variable: `DD_TAGS`
1462    pub fn set_global_tags(&mut self, tags: Vec<(String, String)>) -> &mut Self {
1463        self.config.global_tags.set_code(tags);
1464        self
1465    }
1466
1467    /// Add a tag to be added to every span, in `(key, value)` format.
1468    /// Example: `(layer, api)`.
1469    pub fn add_global_tag(&mut self, tag: (String, String)) -> &mut Self {
1470        let mut current_tags = self.config.global_tags.value().clone();
1471        current_tags.push(tag);
1472        self.config.global_tags.set_code(current_tags);
1473        self
1474    }
1475
1476    /// Enable or disable telemetry data collection and sending.
1477    ///
1478    /// **Default**: `true`
1479    ///
1480    /// Env variable: `DD_INSTRUMENTATION_TELEMETRY_ENABLED`
1481    pub fn set_telemetry_enabled(&mut self, enabled: bool) -> &mut Self {
1482        self.config.telemetry_enabled.set_code(enabled);
1483        self
1484    }
1485
1486    /// Enable or disable log collection for telemetry.
1487    ///
1488    /// **Default**: `true`
1489    ///
1490    /// Env variable: `DD_TELEMETRY_LOG_COLLECTION_ENABLED`
1491    pub fn set_telemetry_log_collection_enabled(&mut self, enabled: bool) -> &mut Self {
1492        self.config
1493            .telemetry_log_collection_enabled
1494            .set_code(enabled);
1495        self
1496    }
1497
1498    /// Interval in seconds for sending telemetry heartbeat messages.
1499    ///
1500    ///  **Default**: `60.0`
1501    ///
1502    /// Env variable: `DD_TELEMETRY_HEARTBEAT_INTERVAL`
1503    pub fn set_telemetry_heartbeat_interval(&mut self, seconds: f64) -> &mut Self {
1504        self.config
1505            .telemetry_heartbeat_interval
1506            .set_code(seconds.abs());
1507        self
1508    }
1509
1510    /// Sets the hostname of the Datadog Agent.
1511    ///
1512    ///  **Default**: `localhost`
1513    ///
1514    /// Env variable: `DD_AGENT_HOST`
1515    pub fn set_agent_host(&mut self, host: String) -> &mut Self {
1516        self.config
1517            .agent_host
1518            .set_code(Cow::Owned(host.to_string()));
1519        self
1520    }
1521
1522    /// Sets the port of the Datadog Agent for trace collection.
1523    ///
1524    ///  **Default**: `8126`
1525    ///
1526    /// Env variable: `DD_TRACE_AGENT_PORT`
1527    pub fn set_trace_agent_port(&mut self, port: u32) -> &mut Self {
1528        self.config.trace_agent_port.set_code(port);
1529        self
1530    }
1531
1532    /// Sets the URL of the Datadog Agent. This takes precedence over `DD_AGENT_HOST` and
1533    /// `DD_TRACE_AGENT_PORT`.
1534    ///
1535    ///  **Default**: `http://localhost:8126`
1536    ///
1537    /// Env variable: `DD_TRACE_AGENT_URL`
1538    pub fn set_trace_agent_url(&mut self, url: String) -> &mut Self {
1539        self.config
1540            .trace_agent_url
1541            .set_code(Cow::Owned(url.to_string()));
1542        self
1543    }
1544
1545    /// Sets the hostname for DogStatsD metric collection.
1546    ///
1547    /// **Default**: `localhost`
1548    ///
1549    /// Env variable: `DD_DOGSTATSD_HOST`
1550    pub fn set_dogstatsd_agent_host(&mut self, host: String) -> &mut Self {
1551        self.config
1552            .dogstatsd_agent_host
1553            .set_code(Cow::Owned(host.to_string()));
1554        self
1555    }
1556
1557    /// Sets the port for DogStatsD metric collection.
1558    ///
1559    /// **Default**: `8125`
1560    ///
1561    /// Env variable: `DD_DOGSTATSD_PORT`
1562    pub fn set_dogstatsd_agent_port(&mut self, port: u32) -> &mut Self {
1563        self.config.dogstatsd_agent_port.set_code(port);
1564        self
1565    }
1566
1567    /// Enable partial flushing of traces.
1568    ///
1569    /// **Default**: `false`
1570    ///
1571    /// Env variable: `DD_TRACE_PARTIAL_FLUSH_ENABLED`
1572    pub fn set_trace_partial_flush_enabled(&mut self, enabled: bool) -> &mut Self {
1573        self.config.trace_partial_flush_enabled.set_code(enabled);
1574        self
1575    }
1576
1577    /// Minimum number of spans in a trace before partial flush is triggered.
1578    ///
1579    /// **Default**: `300`
1580    ///
1581    /// Env variable: `DD_TRACE_PARTIAL_FLUSH_MIN_SPANS`
1582    pub fn set_trace_partial_flush_min_spans(&mut self, min_spans: usize) -> &mut Self {
1583        self.config
1584            .trace_partial_flush_min_spans
1585            .set_code(min_spans);
1586        self
1587    }
1588
1589    /// A JSON array of objects to apply for trace sampling. Each rule must have a `sample_rate`
1590    /// between 0.0 and 1.0 (inclusive).
1591    ///
1592    /// **Default**: `[]`
1593    ///
1594    /// Env variable: `DD_TRACE_SAMPLING_RULES`
1595    pub fn set_trace_sampling_rules(&mut self, rules: Vec<SamplingRuleConfig>) -> &mut Self {
1596        self.config
1597            .trace_sampling_rules
1598            .set_code(ParsedSamplingRules { rules });
1599        self
1600    }
1601
1602    /// Maximum number of traces to sample per second.
1603    /// Only applied if trace_sampling_rules are matched
1604    ///
1605    /// **Default**: `100`
1606    ///
1607    /// Env variable: `DD_TRACE_RATE_LIMIT`
1608    pub fn set_trace_rate_limit(&mut self, rate_limit: i32) -> &mut Self {
1609        self.config.trace_rate_limit.set_code(rate_limit);
1610        self
1611    }
1612
1613    /// A list of propagation styles to use for both extraction and injection. Supported values are
1614    /// `datadog` and `tracecontext`.
1615    ///
1616    /// **Default**: `[Datadog, TraceContext]`
1617    ///
1618    /// Env variable: `DD_TRACE_PROPAGATION_STYLE`
1619    pub fn set_trace_propagation_style(&mut self, styles: Vec<TracePropagationStyle>) -> &mut Self {
1620        self.config.trace_propagation_style.set_code(Some(styles));
1621        self
1622    }
1623
1624    /// A list of propagation styles to use for extraction. When set, this overrides
1625    /// `DD_TRACE_PROPAGATION_STYLE` for extraction.
1626    ///
1627    /// **Default**: `(none)`
1628    ///
1629    /// Env variable: `DD_TRACE_PROPAGATION_STYLE_EXTRACT`
1630    pub fn set_trace_propagation_style_extract(
1631        &mut self,
1632        styles: Vec<TracePropagationStyle>,
1633    ) -> &mut Self {
1634        self.config
1635            .trace_propagation_style_extract
1636            .set_code(Some(styles));
1637        self
1638    }
1639
1640    /// A list of propagation styles to use for injection. When set, this overrides
1641    /// `DD_TRACE_PROPAGATION_STYLE` for injection.
1642    ///
1643    /// **Default**: `(none)`
1644    ///
1645    /// Env variable: `DD_TRACE_PROPAGATION_STYLE_INJECT`
1646    pub fn set_trace_propagation_style_inject(
1647        &mut self,
1648        styles: Vec<TracePropagationStyle>,
1649    ) -> &mut Self {
1650        self.config
1651            .trace_propagation_style_inject
1652            .set_code(Some(styles));
1653        self
1654    }
1655
1656    /// When set to `true`, stops extracting after the first successful trace context extraction.
1657    ///
1658    /// **Default**: `false`
1659    ///
1660    /// Env variable: `DD_TRACE_PROPAGATION_EXTRACT_FIRST`
1661    pub fn set_trace_propagation_extract_first(&mut self, first: bool) -> &mut Self {
1662        self.config.trace_propagation_extract_first.set_code(first);
1663        self
1664    }
1665
1666    /// Set to `false` to disable tracing.
1667    ///
1668    /// **Default**: `true`
1669    ///
1670    /// Env variable: `DD_TRACE_ENABLED`
1671    pub fn set_enabled(&mut self, enabled: bool) -> &mut Self {
1672        self.config.enabled.set_code(enabled);
1673        self
1674    }
1675
1676    /// Sets the internal log level for the tracer.
1677    ///
1678    /// **Default**: `Error`
1679    ///
1680    /// Env variable: `DD_LOG_LEVEL`
1681    pub fn set_log_level_filter(&mut self, filter: LevelFilter) -> &mut Self {
1682        self.config.log_level_filter.set_code(filter);
1683        self
1684    }
1685
1686    /// Enable computation of trace statistics.
1687    ///
1688    /// **Default**: `true`
1689    ///
1690    /// Env variable: `DD_TRACE_STATS_COMPUTATION_ENABLED`
1691    pub fn set_trace_stats_computation_enabled(
1692        &mut self,
1693        trace_stats_computation_enabled: bool,
1694    ) -> &mut Self {
1695        self.config
1696            .trace_stats_computation_enabled
1697            .set_code(trace_stats_computation_enabled);
1698        self
1699    }
1700
1701    /// Enable or disable remote configuration.
1702    ///
1703    /// **Default**: `true`
1704    ///
1705    /// Env variable: `DD_REMOTE_CONFIGURATION_ENABLED`
1706    pub fn set_remote_config_enabled(&mut self, enabled: bool) -> &mut Self {
1707        self.config.remote_config_enabled.set_code(enabled);
1708        self
1709    }
1710
1711    /// Interval in seconds for polling remote configuration updates.
1712    ///
1713    /// **Default**: `5.0`
1714    ///
1715    /// Env variable: `DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS`
1716    pub fn set_remote_config_poll_interval(&mut self, seconds: f64) -> &mut Self {
1717        self.config
1718            .remote_config_poll_interval
1719            .set_code(seconds.abs().min(RC_DEFAULT_POLL_INTERVAL));
1720        self
1721    }
1722
1723    /// Maximum length of the `x-datadog-tags` header in bytes.
1724    ///
1725    /// **Default**: `512`
1726    ///
1727    /// Env variable: `DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH`
1728    pub fn set_datadog_tags_max_length(&mut self, length: usize) -> &mut Self {
1729        self.config
1730            .datadog_tags_max_length
1731            .set_code(length.min(DATADOG_TAGS_MAX_LENGTH));
1732        self
1733    }
1734
1735    #[cfg(feature = "test-utils")]
1736    pub fn set_datadog_tags_max_length_with_no_limit(&mut self, length: usize) -> &mut Self {
1737        self.config.datadog_tags_max_length.set_code(length);
1738        self
1739    }
1740
1741    #[cfg(feature = "test-utils")]
1742    pub fn __internal_set_wait_agent_info_ready(
1743        &mut self,
1744        wait_agent_info_ready: bool,
1745    ) -> &mut Self {
1746        self.config.wait_agent_info_ready = wait_agent_info_ready;
1747        self
1748    }
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753    use libdd_telemetry::data::ConfigurationOrigin;
1754
1755    use super::Config;
1756    use super::*;
1757    use crate::core::configuration::sources::{CompositeSource, ConfigSourceOrigin, HashMapSource};
1758
1759    #[test]
1760    fn test_config_from_source() {
1761        let mut sources = CompositeSource::new();
1762        sources.add_source(HashMapSource::from_iter(
1763            [
1764                ("DD_SERVICE", "test-service"),
1765                ("DD_ENV", "test-env"),
1766                ("DD_TRACE_SAMPLING_RULES", 
1767                 r#"[{"sample_rate":0.5,"service":"web-api","name":null,"resource":null,"tags":{},"provenance":"customer"}]"#),
1768                ("DD_TRACE_RATE_LIMIT", "123"),
1769                ("DD_TRACE_ENABLED", "true"),
1770                ("DD_LOG_LEVEL", "DEBUG"),
1771            ],
1772            ConfigSourceOrigin::EnvVar,
1773        ));
1774        let config = Config::builder_with_sources(&sources).build();
1775
1776        assert_eq!(&*config.service(), "test-service");
1777        assert_eq!(config.env(), Some("test-env"));
1778        assert_eq!(config.trace_rate_limit(), 123);
1779        let rules = config.trace_sampling_rules();
1780        assert_eq!(rules.len(), 1, "Should have one rule");
1781        assert_eq!(
1782            &rules[0],
1783            &SamplingRuleConfig {
1784                sample_rate: 0.5,
1785                service: Some("web-api".to_string()),
1786                provenance: "customer".to_string(),
1787                ..SamplingRuleConfig::default()
1788            }
1789        );
1790
1791        assert!(config.enabled());
1792        assert_eq!(*config.log_level_filter(), super::LevelFilter::Debug);
1793    }
1794
1795    #[test]
1796    fn test_sampling_rules() {
1797        let mut sources = CompositeSource::new();
1798        sources.add_source(HashMapSource::from_iter(
1799            [(
1800                "DD_TRACE_SAMPLING_RULES",
1801                r#"[{"sample_rate":0.5,"service":"test-service","provenance":"customer"}]"#,
1802            )],
1803            ConfigSourceOrigin::EnvVar,
1804        ));
1805        let config = Config::builder_with_sources(&sources).build();
1806
1807        assert_eq!(config.trace_sampling_rules().len(), 1);
1808        assert_eq!(
1809            &config.trace_sampling_rules()[0],
1810            &SamplingRuleConfig {
1811                sample_rate: 0.5,
1812                service: Some("test-service".to_string()),
1813                provenance: "customer".to_string(),
1814                ..SamplingRuleConfig::default()
1815            }
1816        );
1817    }
1818
1819    #[test]
1820    fn test_config_from_source_manual_override() {
1821        let mut sources = CompositeSource::new();
1822        sources.add_source(HashMapSource::from_iter(
1823            [
1824                ("DD_SERVICE", "test-service"),
1825                ("DD_TRACE_RATE_LIMIT", "50"),
1826            ],
1827            ConfigSourceOrigin::EnvVar,
1828        ));
1829        let config = Config::builder_with_sources(&sources)
1830            .set_trace_sampling_rules(vec![SamplingRuleConfig {
1831                sample_rate: 0.8,
1832                service: Some("manual-service".to_string()),
1833                name: None,
1834                resource: None,
1835                tags: HashMap::new(),
1836                provenance: "manual".to_string(),
1837            }])
1838            .set_trace_rate_limit(200)
1839            .set_service("manual-service".to_string())
1840            .set_env("manual-env".to_string())
1841            .set_log_level_filter(super::LevelFilter::Warn)
1842            .build();
1843
1844        assert_eq!(config.trace_rate_limit(), 200);
1845        let rules = config.trace_sampling_rules();
1846        assert_eq!(rules.len(), 1);
1847        assert_eq!(
1848            &config.trace_sampling_rules()[0],
1849            &SamplingRuleConfig {
1850                sample_rate: 0.8,
1851                service: Some("manual-service".to_string()),
1852                provenance: "manual".to_string(),
1853                ..SamplingRuleConfig::default()
1854            }
1855        );
1856
1857        assert!(config.enabled());
1858        assert_eq!(*config.log_level_filter(), super::LevelFilter::Warn);
1859    }
1860
1861    #[test]
1862    fn test_propagation_config_from_source() {
1863        let mut sources = CompositeSource::new();
1864        sources.add_source(HashMapSource::from_iter(
1865            [
1866                ("DD_TRACE_PROPAGATION_STYLE", ""),
1867                (
1868                    "DD_TRACE_PROPAGATION_STYLE_EXTRACT",
1869                    "datadog,  tracecontext, invalid",
1870                ),
1871                ("DD_TRACE_PROPAGATION_STYLE_INJECT", "tracecontext"),
1872                ("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"),
1873            ],
1874            ConfigSourceOrigin::EnvVar,
1875        ));
1876        let config = Config::builder_with_sources(&sources).build();
1877
1878        assert_eq!(config.trace_propagation_style(), Some(vec![]).as_deref());
1879        assert_eq!(
1880            config.trace_propagation_style_extract(),
1881            Some(vec![
1882                TracePropagationStyle::Datadog,
1883                TracePropagationStyle::TraceContext
1884            ])
1885            .as_deref()
1886        );
1887        assert_eq!(
1888            config.trace_propagation_style_inject(),
1889            Some(vec![TracePropagationStyle::TraceContext]).as_deref()
1890        );
1891        assert!(config.trace_propagation_extract_first())
1892    }
1893
1894    #[test]
1895    fn test_propagation_config_from_source_override() {
1896        let mut sources = CompositeSource::new();
1897        sources.add_source(HashMapSource::from_iter(
1898            [
1899                ("DD_TRACE_PROPAGATION_STYLE", ""),
1900                (
1901                    "DD_TRACE_PROPAGATION_STYLE_EXTRACT",
1902                    "datadog,  tracecontext",
1903                ),
1904                ("DD_TRACE_PROPAGATION_STYLE_INJECT", "tracecontext"),
1905                ("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"),
1906            ],
1907            ConfigSourceOrigin::EnvVar,
1908        ));
1909        let config = Config::builder_with_sources(&sources)
1910            .set_trace_propagation_style(vec![
1911                TracePropagationStyle::TraceContext,
1912                TracePropagationStyle::Datadog,
1913            ])
1914            .set_trace_propagation_style_extract(vec![TracePropagationStyle::TraceContext])
1915            .set_trace_propagation_style_inject(vec![TracePropagationStyle::Datadog])
1916            .set_trace_propagation_extract_first(false)
1917            .build();
1918
1919        assert_eq!(
1920            config.trace_propagation_style(),
1921            Some(vec![
1922                TracePropagationStyle::TraceContext,
1923                TracePropagationStyle::Datadog
1924            ])
1925            .as_deref()
1926        );
1927        assert_eq!(
1928            config.trace_propagation_style_extract(),
1929            Some(vec![TracePropagationStyle::TraceContext]).as_deref()
1930        );
1931        assert_eq!(
1932            config.trace_propagation_style_inject(),
1933            Some(vec![TracePropagationStyle::Datadog]).as_deref()
1934        );
1935        assert!(!config.trace_propagation_extract_first());
1936    }
1937
1938    #[test]
1939    fn test_propagation_config_incorrect_extract() {
1940        let mut sources = CompositeSource::new();
1941        sources.add_source(HashMapSource::from_iter(
1942            [
1943                ("DD_TRACE_PROPAGATION_STYLE", "datadog,  tracecontext"),
1944                ("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "incorrect,"),
1945                ("DD_TRACE_PROPAGATION_STYLE_INJECT", "tracecontext"),
1946                ("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"),
1947            ],
1948            ConfigSourceOrigin::EnvVar,
1949        ));
1950        let config = Config::builder_with_sources(&sources).build();
1951
1952        assert_eq!(
1953            config.trace_propagation_style(),
1954            Some(vec![
1955                TracePropagationStyle::Datadog,
1956                TracePropagationStyle::TraceContext,
1957            ])
1958            .as_deref()
1959        );
1960        assert_eq!(
1961            config.trace_propagation_style_extract(),
1962            Some(vec![]).as_deref()
1963        );
1964        assert_eq!(
1965            config.trace_propagation_style_inject(),
1966            Some(vec![TracePropagationStyle::TraceContext]).as_deref()
1967        );
1968        assert!(config.trace_propagation_extract_first());
1969    }
1970    #[test]
1971    fn test_propagation_config_empty_extract() {
1972        let mut sources = CompositeSource::new();
1973        sources.add_source(HashMapSource::from_iter(
1974            [
1975                ("DD_TRACE_PROPAGATION_STYLE", ""),
1976                ("DD_TRACE_PROPAGATION_STYLE_EXTRACT", ""),
1977                ("DD_TRACE_PROPAGATION_STYLE_INJECT", "tracecontext"),
1978                ("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"),
1979            ],
1980            ConfigSourceOrigin::EnvVar,
1981        ));
1982        let config = Config::builder_with_sources(&sources).build();
1983
1984        assert_eq!(config.trace_propagation_style(), Some(vec![]).as_deref());
1985        assert_eq!(
1986            config.trace_propagation_style_extract(),
1987            Some(vec![]).as_deref()
1988        );
1989        assert_eq!(
1990            config.trace_propagation_style_inject(),
1991            Some(vec![TracePropagationStyle::TraceContext]).as_deref()
1992        );
1993        assert!(config.trace_propagation_extract_first());
1994    }
1995
1996    #[test]
1997    fn test_propagation_config_not_present_extract() {
1998        let mut sources = CompositeSource::new();
1999        sources.add_source(HashMapSource::from_iter(
2000            [
2001                ("DD_TRACE_PROPAGATION_STYLE_INJECT", "tracecontext"),
2002                ("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"),
2003            ],
2004            ConfigSourceOrigin::EnvVar,
2005        ));
2006        let config = Config::builder_with_sources(&sources).build();
2007
2008        assert_eq!(
2009            config.trace_propagation_style(),
2010            Some(vec![
2011                TracePropagationStyle::Datadog,
2012                TracePropagationStyle::TraceContext,
2013            ])
2014            .as_deref()
2015        );
2016        assert_eq!(config.trace_propagation_style_extract(), None);
2017        assert_eq!(
2018            config.trace_propagation_style_inject(),
2019            Some(vec![TracePropagationStyle::TraceContext]).as_deref()
2020        );
2021        assert!(config.trace_propagation_extract_first());
2022    }
2023
2024    #[test]
2025    fn test_stats_computation_enabled_config() {
2026        let mut sources = CompositeSource::new();
2027        sources.add_source(HashMapSource::from_iter(
2028            [("DD_TRACE_STATS_COMPUTATION_ENABLED", "false")],
2029            ConfigSourceOrigin::EnvVar,
2030        ));
2031        let config = Config::builder_with_sources(&sources).build();
2032        assert!(!config.trace_stats_computation_enabled());
2033
2034        let mut sources = CompositeSource::new();
2035        sources.add_source(HashMapSource::from_iter(
2036            [("DD_TRACE_STATS_COMPUTATION_ENABLED", "true")],
2037            ConfigSourceOrigin::EnvVar,
2038        ));
2039        let config = Config::builder_with_sources(&sources).build();
2040        assert!(config.trace_stats_computation_enabled());
2041
2042        let mut sources = CompositeSource::new();
2043        sources.add_source(HashMapSource::from_iter(
2044            [("DD_TRACE_STATS_COMPUTATION_ENABLED", "a")],
2045            ConfigSourceOrigin::EnvVar,
2046        ));
2047        let config = Config::builder_with_sources(&sources).build();
2048        assert!(config.trace_stats_computation_enabled());
2049
2050        let config = Config::builder()
2051            .set_trace_stats_computation_enabled(false)
2052            .build();
2053
2054        assert!(!config.trace_stats_computation_enabled());
2055    }
2056
2057    #[test]
2058    fn test_extra_services_tracking() {
2059        let config = Config::builder()
2060            .set_service("main-service".to_string())
2061            .build();
2062
2063        // Initially empty
2064        assert_eq!(config.get_extra_services().len(), 0);
2065
2066        // Add some extra services
2067        config.add_extra_service("service-1");
2068        config.add_extra_service("service-2");
2069        config.add_extra_service("service-3");
2070
2071        // Should not add the main service
2072        config.add_extra_service("main-service");
2073
2074        // Should not add duplicates
2075        config.add_extra_service("service-1");
2076
2077        let services = config.get_extra_services();
2078        assert_eq!(services.len(), 3);
2079        assert!(services.contains(&"service-1".to_string()));
2080        assert!(services.contains(&"service-2".to_string()));
2081        assert!(services.contains(&"service-3".to_string()));
2082        assert!(!services.contains(&"main-service".to_string()));
2083    }
2084
2085    #[test]
2086    fn test_extra_services_disabled_when_remote_config_disabled() {
2087        // Use environment variable to disable remote config
2088        let mut sources = CompositeSource::new();
2089        sources.add_source(HashMapSource::from_iter(
2090            [("DD_REMOTE_CONFIGURATION_ENABLED", "false")],
2091            ConfigSourceOrigin::EnvVar,
2092        ));
2093        let config = Config::builder_with_sources(&sources)
2094            .set_service("main-service".to_string())
2095            .build();
2096
2097        // Add services when remote config is disabled
2098        config.add_extra_service("service-1");
2099        config.add_extra_service("service-2");
2100
2101        // Should return empty since remote config is disabled
2102        let services = config.get_extra_services();
2103        assert_eq!(services.len(), 0);
2104    }
2105
2106    #[test]
2107    fn test_extra_services_limit() {
2108        let config = Config::builder()
2109            .set_service("main-service".to_string())
2110            .build();
2111
2112        // Add more than 64 services
2113        for i in 0..70 {
2114            config.add_extra_service(&format!("service-{i}"));
2115        }
2116
2117        // Should be limited to 64
2118        let services = config.get_extra_services();
2119        assert_eq!(services.len(), 64);
2120    }
2121
2122    #[test]
2123    fn test_remote_config_enabled_from_env() {
2124        // Test with explicit true
2125        let mut sources = CompositeSource::new();
2126        sources.add_source(HashMapSource::from_iter(
2127            [("DD_REMOTE_CONFIGURATION_ENABLED", "true")],
2128            ConfigSourceOrigin::EnvVar,
2129        ));
2130        let config = Config::builder_with_sources(&sources).build();
2131        assert!(config.remote_config_enabled());
2132
2133        // Test with explicit false
2134        let mut sources = CompositeSource::new();
2135        sources.add_source(HashMapSource::from_iter(
2136            [("DD_REMOTE_CONFIGURATION_ENABLED", "false")],
2137            ConfigSourceOrigin::EnvVar,
2138        ));
2139        let config = Config::builder_with_sources(&sources).build();
2140        assert!(!config.remote_config_enabled());
2141
2142        // Test with invalid value (should default to true)
2143        let mut sources = CompositeSource::new();
2144        sources.add_source(HashMapSource::from_iter(
2145            [("DD_REMOTE_CONFIGURATION_ENABLED", "invalid")],
2146            ConfigSourceOrigin::EnvVar,
2147        ));
2148        let config = Config::builder_with_sources(&sources).build();
2149        assert!(config.remote_config_enabled());
2150
2151        // Test without env var (should use default)
2152        let config = Config::builder().build();
2153        assert!(config.remote_config_enabled()); // Default is true based on user's change
2154    }
2155
2156    #[test]
2157    fn test_sampling_rules_update_callbacks() {
2158        let config = Config::builder().build();
2159
2160        // Track callback invocations
2161        let callback_called = Arc::new(Mutex::new(false));
2162        let callback_rules = Arc::new(Mutex::new(Vec::<SamplingRuleConfig>::new()));
2163
2164        let callback_called_clone = callback_called.clone();
2165        let callback_rules_clone = callback_rules.clone();
2166
2167        config.set_sampling_rules_callback(move |update| {
2168            *callback_called_clone.lock().unwrap() = true;
2169            // Store the rules - for now we only have SamplingRules variant
2170            let RemoteConfigUpdate::SamplingRules(rules) = update;
2171            *callback_rules_clone.lock().unwrap() = rules.clone();
2172        });
2173
2174        // Initially callback should not be called
2175        assert!(!*callback_called.lock().unwrap());
2176        assert!(callback_rules.lock().unwrap().is_empty());
2177
2178        // Update rules from remote config
2179        let new_rules = vec![SamplingRuleConfig {
2180            sample_rate: 0.5,
2181            service: Some("test-service".to_string()),
2182            provenance: "remote".to_string(),
2183            ..SamplingRuleConfig::default()
2184        }];
2185
2186        let rules_json = serde_json::to_string(&new_rules).unwrap();
2187        config
2188            .update_sampling_rules_from_remote(&rules_json, None)
2189            .unwrap();
2190
2191        // Callback should be called with the new rules
2192        assert!(*callback_called.lock().unwrap());
2193        assert_eq!(*callback_rules.lock().unwrap(), new_rules);
2194
2195        // Test clearing rules
2196        *callback_called.lock().unwrap() = false;
2197        callback_rules.lock().unwrap().clear();
2198
2199        config.clear_remote_sampling_rules(None);
2200
2201        // Callback should be called with fallback rules (empty in this case since no env/code rules
2202        // set)
2203        assert!(*callback_called.lock().unwrap());
2204        assert!(callback_rules.lock().unwrap().is_empty());
2205    }
2206
2207    #[test]
2208    fn test_config_item_priority() {
2209        // Test that ConfigItem respects priority: remote_config > code > env_var > default
2210        let mut config_item = ConfigItemWithOverride::new_rc(
2211            SupportedConfigurations::DD_TRACE_SAMPLING_RULES,
2212            ParsedSamplingRules::default(),
2213        );
2214
2215        // Default value
2216        assert_eq!(config_item.source(), ConfigSourceOrigin::Default);
2217        assert_eq!(config_item.value().len(), 0);
2218
2219        // Env overrides default
2220        config_item.set_value_source(
2221            ParsedSamplingRules {
2222                rules: vec![SamplingRuleConfig {
2223                    sample_rate: 0.3,
2224                    ..SamplingRuleConfig::default()
2225                }],
2226            },
2227            ConfigSourceOrigin::EnvVar,
2228        );
2229        assert_eq!(config_item.source(), ConfigSourceOrigin::EnvVar);
2230        assert_eq!(config_item.value()[0].sample_rate, 0.3);
2231
2232        // Code overrides env
2233        config_item.set_code(ParsedSamplingRules {
2234            rules: vec![SamplingRuleConfig {
2235                sample_rate: 0.5,
2236                ..SamplingRuleConfig::default()
2237            }],
2238        });
2239        assert_eq!(config_item.source(), ConfigSourceOrigin::Code);
2240        assert_eq!(config_item.value()[0].sample_rate, 0.5);
2241
2242        // Remote config overrides all
2243        config_item.set_value_source(
2244            ParsedSamplingRules {
2245                rules: vec![SamplingRuleConfig {
2246                    sample_rate: 0.8,
2247                    ..SamplingRuleConfig::default()
2248                }],
2249            },
2250            ConfigSourceOrigin::RemoteConfig,
2251        );
2252        assert_eq!(config_item.source(), ConfigSourceOrigin::RemoteConfig);
2253        assert_eq!(config_item.value()[0].sample_rate, 0.8);
2254
2255        // Unset RC falls back to code
2256        config_item.unset_override_value();
2257        assert_eq!(config_item.source(), ConfigSourceOrigin::Code);
2258        assert_eq!(config_item.value()[0].sample_rate, 0.5);
2259    }
2260
2261    #[test]
2262    fn test_sampling_rules_with_config_item() {
2263        // Test integration: env var is parsed, then overridden by code
2264        let mut sources = CompositeSource::new();
2265        sources.add_source(HashMapSource::from_iter(
2266            [(
2267                "DD_TRACE_SAMPLING_RULES",
2268                r#"[{"sample_rate":0.25,"service":"env-service"}]"#,
2269            )],
2270            ConfigSourceOrigin::EnvVar,
2271        ));
2272
2273        // First, env var should be used
2274        let config = Config::builder_with_sources(&sources).build();
2275        assert_eq!(config.trace_sampling_rules().len(), 1);
2276        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.25);
2277
2278        // Code override should take precedence
2279        let config = Config::builder_with_sources(&sources)
2280            .set_trace_sampling_rules(vec![SamplingRuleConfig {
2281                sample_rate: 0.75,
2282                service: Some("code-service".to_string()),
2283                ..SamplingRuleConfig::default()
2284            }])
2285            .build();
2286        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.75);
2287        assert_eq!(
2288            config.trace_sampling_rules()[0].service.as_ref().unwrap(),
2289            "code-service"
2290        );
2291    }
2292
2293    #[test]
2294    fn test_empty_remote_rules_fallback_behavior() {
2295        let mut config = Config::builder().build();
2296
2297        // 1. Set up local rules via environment variable simulation
2298        let local_rules = ParsedSamplingRules {
2299            rules: vec![SamplingRuleConfig {
2300                sample_rate: 0.3,
2301                service: Some("local-service".to_string()),
2302                provenance: "local".to_string(),
2303                ..SamplingRuleConfig::default()
2304            }],
2305        };
2306        config
2307            .trace_sampling_rules
2308            .set_value_source(local_rules.clone(), ConfigSourceOrigin::EnvVar);
2309
2310        // Verify local rules are active
2311        assert_eq!(config.trace_sampling_rules().len(), 1);
2312        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.3);
2313        assert_eq!(
2314            config.trace_sampling_rules.source(),
2315            ConfigSourceOrigin::EnvVar
2316        );
2317
2318        // 2. Remote config sends non-empty rules
2319        let remote_rules_json =
2320            r#"[{"sample_rate": 0.8, "service": "remote-service", "provenance": "remote"}]"#;
2321        config
2322            .update_sampling_rules_from_remote(remote_rules_json, None)
2323            .unwrap();
2324
2325        // Verify remote rules override local rules
2326        assert_eq!(config.trace_sampling_rules().len(), 1);
2327        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.8);
2328        assert_eq!(
2329            config.trace_sampling_rules.source(),
2330            ConfigSourceOrigin::RemoteConfig
2331        );
2332
2333        // 3. Remote config sends empty array []
2334        let empty_remote_rules_json = "[]";
2335        config
2336            .update_sampling_rules_from_remote(empty_remote_rules_json, None)
2337            .unwrap();
2338
2339        // Empty remote rules automatically fall back to local rules
2340        assert_eq!(config.trace_sampling_rules().len(), 1); // Falls back to local rules
2341        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.3); // Local rule values
2342        assert_eq!(
2343            config.trace_sampling_rules.source(),
2344            ConfigSourceOrigin::EnvVar
2345        ); // Back to env source!
2346
2347        // 4. Verify explicit clearing still works (for completeness)
2348        // Since we're already on local rules, clear should keep us on local rules
2349        config.clear_remote_sampling_rules(None);
2350
2351        // Should remain on local rules
2352        assert_eq!(config.trace_sampling_rules().len(), 1);
2353        assert_eq!(config.trace_sampling_rules()[0].sample_rate, 0.3);
2354        assert_eq!(
2355            config.trace_sampling_rules.source(),
2356            ConfigSourceOrigin::EnvVar
2357        );
2358    }
2359
2360    #[test]
2361    fn test_update_sampling_rules_from_remote_config_id() {
2362        let config = Config::builder().build();
2363
2364        let new_rules = vec![SamplingRuleConfig {
2365            sample_rate: 0.5,
2366            service: Some("test-service".to_string()),
2367            provenance: "remote".to_string(),
2368            ..SamplingRuleConfig::default()
2369        }];
2370
2371        let rules_json = serde_json::to_string(&new_rules).unwrap();
2372        config
2373            .update_sampling_rules_from_remote(&rules_json, Some("config_id_1".to_string()))
2374            .unwrap();
2375
2376        assert_eq!(
2377            config.trace_sampling_rules.get_configuration().config_id,
2378            Some("config_id_1".to_string())
2379        );
2380
2381        config
2382            .update_sampling_rules_from_remote(&rules_json, Some("config_id_2".to_string()))
2383            .unwrap();
2384        assert_eq!(
2385            config.trace_sampling_rules.get_configuration().config_id,
2386            Some("config_id_2".to_string())
2387        );
2388
2389        config
2390            .update_sampling_rules_from_remote("[]", None)
2391            .unwrap();
2392        assert_eq!(
2393            config.trace_sampling_rules.get_configuration().config_id,
2394            None
2395        );
2396    }
2397
2398    #[test]
2399    fn test_telemetry_config_from_sources() {
2400        let mut sources = CompositeSource::new();
2401        sources.add_source(HashMapSource::from_iter(
2402            [
2403                ("DD_INSTRUMENTATION_TELEMETRY_ENABLED", "false"),
2404                ("DD_TELEMETRY_LOG_COLLECTION_ENABLED", "false"),
2405                ("DD_TELEMETRY_HEARTBEAT_INTERVAL", "42"),
2406            ],
2407            ConfigSourceOrigin::EnvVar,
2408        ));
2409        let config = Config::builder_with_sources(&sources).build();
2410
2411        assert!(!config.telemetry_enabled());
2412        assert!(!config.telemetry_log_collection_enabled());
2413        assert_eq!(config.telemetry_heartbeat_interval(), 42.0);
2414    }
2415
2416    #[test]
2417    fn test_telemetry_config() {
2418        let mut sources = CompositeSource::new();
2419        sources.add_source(HashMapSource::from_iter(
2420            [
2421                ("DD_INSTRUMENTATION_TELEMETRY_ENABLED", "false"),
2422                ("DD_TELEMETRY_LOG_COLLECTION_ENABLED", "false"),
2423                ("DD_TELEMETRY_HEARTBEAT_INTERVAL", "42"),
2424            ],
2425            ConfigSourceOrigin::EnvVar,
2426        ));
2427        let mut builder = Config::builder_with_sources(&sources);
2428
2429        builder
2430            .set_telemetry_enabled(true)
2431            .set_telemetry_log_collection_enabled(true)
2432            .set_telemetry_heartbeat_interval(0.1);
2433
2434        let config = builder.build();
2435
2436        assert!(config.telemetry_enabled());
2437        assert!(config.telemetry_log_collection_enabled());
2438        assert_eq!(config.telemetry_heartbeat_interval(), 0.1);
2439    }
2440
2441    #[test]
2442    fn test_dd_tags() {
2443        let mut sources = CompositeSource::new();
2444        sources.add_source(HashMapSource::from_iter(
2445            [("DD_TAGS", "key1   :value1          ,   key2:,key3")],
2446            ConfigSourceOrigin::EnvVar,
2447        ));
2448        let config = Config::builder_with_sources(&sources).build();
2449
2450        let tags: Vec<(&str, &str)> = config.global_tags().collect();
2451
2452        assert_eq!(tags.len(), 2);
2453        assert_eq!(tags, vec![("key1", "value1"), ("key2", "")]);
2454    }
2455
2456    #[test]
2457    fn test_dd_agent_url_default() {
2458        let config = Config::builder().build();
2459
2460        assert_eq!(config.trace_agent_url(), "http://localhost:8126");
2461    }
2462
2463    #[test]
2464    fn test_dd_agent_url_from_host_and_port() {
2465        let mut sources = CompositeSource::new();
2466        sources.add_source(HashMapSource::from_iter(
2467            [
2468                ("DD_AGENT_HOST", "agent-host"),
2469                ("DD_TRACE_AGENT_PORT", "4242"),
2470            ],
2471            ConfigSourceOrigin::EnvVar,
2472        ));
2473        let config = Config::builder_with_sources(&sources).build();
2474
2475        assert_eq!(config.trace_agent_url(), "http://agent-host:4242");
2476    }
2477
2478    #[test]
2479    fn test_dd_agent_url_from_url() {
2480        let mut sources = CompositeSource::new();
2481        sources.add_source(HashMapSource::from_iter(
2482            [
2483                ("DD_TRACE_AGENT_URL", "https://test-host"),
2484                ("DD_AGENT_HOST", "agent-host"),
2485                ("DD_TRACE_AGENT_PORT", "4242"),
2486            ],
2487            ConfigSourceOrigin::EnvVar,
2488        ));
2489        let config = Config::builder_with_sources(&sources).build();
2490
2491        assert_eq!(config.trace_agent_url(), "https://test-host");
2492    }
2493
2494    #[test]
2495    fn test_dd_agent_url_from_url_empty() {
2496        let mut sources = CompositeSource::new();
2497        sources.add_source(HashMapSource::from_iter(
2498            [
2499                ("DD_TRACE_AGENT_URL", ""),
2500                ("DD_AGENT_HOST", "agent-host"),
2501                ("DD_TRACE_AGENT_PORT", "4242"),
2502            ],
2503            ConfigSourceOrigin::EnvVar,
2504        ));
2505        let config = Config::builder_with_sources(&sources).build();
2506
2507        assert_eq!(config.trace_agent_url(), "http://agent-host:4242");
2508    }
2509
2510    #[test]
2511    fn test_dd_agent_url_from_host_and_port_using_builder() {
2512        let config = Config::builder()
2513            .set_agent_host("agent-host".into())
2514            .set_trace_agent_port(4242)
2515            .build();
2516
2517        assert_eq!(config.trace_agent_url(), "http://agent-host:4242");
2518    }
2519
2520    #[test]
2521    fn test_dd_agent_url_from_url_using_builder() {
2522        let config = Config::builder()
2523            .set_agent_host("agent-host".into())
2524            .set_trace_agent_port(4242)
2525            .set_trace_agent_url("https://test-host".into())
2526            .build();
2527
2528        assert_eq!(config.trace_agent_url(), "https://test-host");
2529    }
2530
2531    #[test]
2532    fn test_dogstatsd_agent_url_default() {
2533        let config = Config::builder().build();
2534
2535        assert_eq!(config.dogstatsd_agent_url(), "http://localhost:8125");
2536    }
2537
2538    #[test]
2539    fn test_dogstatsd_agent_url_from_host_and_port() {
2540        let mut sources = CompositeSource::new();
2541        sources.add_source(HashMapSource::from_iter(
2542            [
2543                ("DD_DOGSTATSD_HOST", "dogstatsd-host"),
2544                ("DD_DOGSTATSD_PORT", "4242"),
2545            ],
2546            ConfigSourceOrigin::EnvVar,
2547        ));
2548        let config = Config::builder_with_sources(&sources).build();
2549
2550        assert_eq!(config.dogstatsd_agent_url(), "http://dogstatsd-host:4242");
2551    }
2552
2553    #[test]
2554    fn test_dogstatsd_agent_url_from_url_using_builder() {
2555        let config = Config::builder()
2556            .set_dogstatsd_agent_host("dogstatsd-host".into())
2557            .set_dogstatsd_agent_port(4242)
2558            .build();
2559
2560        assert_eq!(config.dogstatsd_agent_url(), "http://dogstatsd-host:4242");
2561    }
2562
2563    #[test]
2564    fn test_config_source_updater() {
2565        let mut sources = CompositeSource::new();
2566        sources.add_source(HashMapSource::from_iter(
2567            [("DD_ENV", "test-env")],
2568            ConfigSourceOrigin::EnvVar,
2569        ));
2570        sources.add_source(HashMapSource::from_iter(
2571            [("DD_ENABLED", "false")],
2572            ConfigSourceOrigin::RemoteConfig,
2573        ));
2574        sources.add_source(HashMapSource::from_iter(
2575            [("DD_TAGS", "v1,v2")],
2576            ConfigSourceOrigin::Code,
2577        ));
2578        let default = default_config();
2579
2580        let cisu = ConfigItemSourceUpdater { sources: &sources };
2581
2582        assert_eq!(default.env(), None);
2583        assert!(default.enabled());
2584        assert_eq!(default.global_tags().collect::<Vec<_>>(), vec![]);
2585
2586        let env = cisu.update_string(default.env, Some);
2587        assert_eq!(env.default_value, None);
2588        assert_eq!(env.env_value, Some(Some("test-env".to_string())));
2589        assert_eq!(env.code_value, None);
2590
2591        let enabled = cisu.update_parsed(default.enabled);
2592        assert!(enabled.default_value);
2593        assert_eq!(enabled.env_value, None);
2594        assert_eq!(enabled.code_value, None);
2595
2596        struct Tags(Vec<(String, String)>);
2597
2598        impl FromStr for Tags {
2599            type Err = &'static str;
2600
2601            fn from_str(s: &str) -> Result<Self, Self::Err> {
2602                Ok(Tags(
2603                    s.split(',')
2604                        .enumerate()
2605                        .map(|(index, s)| (index.to_string(), s.to_string()))
2606                        .collect(),
2607                ))
2608            }
2609        }
2610
2611        let tags = cisu.update_parsed_with_transform(default.global_tags, |Tags(tags)| tags);
2612        assert_eq!(tags.default_value, vec![]);
2613        assert_eq!(tags.env_value, None);
2614        assert_eq!(
2615            tags.code_value,
2616            Some(vec![
2617                ("0".to_string(), "v1".to_string()),
2618                ("1".to_string(), "v2".to_string())
2619            ])
2620        );
2621    }
2622
2623    #[test]
2624    fn test_get_configuration_config_item_rc() {
2625        let mut sources = CompositeSource::new();
2626        sources.add_source(HashMapSource::from_iter(
2627            [
2628                ("DD_TRACE_SAMPLING_RULES", 
2629                 r#"[{"sample_rate":0.5,"service":"web-api","name":null,"resource":null,"tags":{},"provenance":"customer"}]"#),
2630            ],
2631            ConfigSourceOrigin::EnvVar,
2632        ));
2633        let config = Config::builder_with_sources(&sources).build();
2634
2635        let expected = ParsedSamplingRules::from_str(
2636            r#"[{"sample_rate":0.5,"service":"web-api","name":null,"resource":null,"tags":{},"provenance":"customer"}]"#
2637        ).unwrap();
2638
2639        let configuration = &config.trace_sampling_rules.get_configuration();
2640        assert_eq!(configuration.origin, ConfigurationOrigin::EnvVar);
2641
2642        // Converting configuration value to json helps with comparison as serialized properties may
2643        // differ from their original order
2644        assert_eq!(
2645            ParsedSamplingRules::from_str(&configuration.value).unwrap(),
2646            expected.clone()
2647        );
2648
2649        // Update ConfigItemRc via RC
2650        let expected_rc = ParsedSamplingRules::from_str(r#"[{"sample_rate":1,"service":"web-api","name":null,"resource":null,"tags":{},"provenance":"customer"}]"#).unwrap();
2651        config
2652            .trace_sampling_rules
2653            .set_override_value(expected_rc.clone(), ConfigSourceOrigin::RemoteConfig);
2654
2655        let configuration_after_rc = &config.trace_sampling_rules.get_configuration();
2656        assert_eq!(
2657            configuration_after_rc.origin,
2658            ConfigurationOrigin::RemoteConfig
2659        );
2660        assert_eq!(
2661            ParsedSamplingRules::from_str(&configuration_after_rc.value).unwrap(),
2662            expected_rc
2663        );
2664
2665        // Reset ConfigItemRc RC previous value
2666        config.trace_sampling_rules.unset_override_value();
2667
2668        let configuration = &config.trace_sampling_rules.get_configuration();
2669        assert_eq!(configuration.origin, ConfigurationOrigin::EnvVar);
2670        assert_eq!(
2671            ParsedSamplingRules::from_str(&configuration.value).unwrap(),
2672            expected
2673        );
2674    }
2675
2676    #[test]
2677    fn test_datadog_tags_max_length() {
2678        let config = Config::builder().set_datadog_tags_max_length(4242).build();
2679
2680        assert_eq!(config.datadog_tags_max_length(), DATADOG_TAGS_MAX_LENGTH);
2681
2682        let mut sources = CompositeSource::new();
2683        sources.add_source(HashMapSource::from_iter(
2684            [("DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH", "4242")],
2685            ConfigSourceOrigin::EnvVar,
2686        ));
2687        let config = Config::builder_with_sources(&sources).build();
2688        assert_eq!(config.datadog_tags_max_length(), DATADOG_TAGS_MAX_LENGTH);
2689
2690        let mut sources = CompositeSource::new();
2691        sources.add_source(HashMapSource::from_iter(
2692            [("DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH", "42")],
2693            ConfigSourceOrigin::EnvVar,
2694        ));
2695        let config = Config::builder_with_sources(&sources).build();
2696        assert_eq!(config.datadog_tags_max_length(), 42);
2697    }
2698
2699    #[test]
2700    fn test_remote_config_poll_interval() {
2701        let config = Config::builder()
2702            .set_remote_config_poll_interval(42.0)
2703            .build();
2704
2705        assert_eq!(config.remote_config_poll_interval(), 5.0);
2706
2707        let config = Config::builder()
2708            .set_remote_config_poll_interval(-0.2)
2709            .build();
2710
2711        assert_eq!(config.remote_config_poll_interval(), 0.2);
2712    }
2713}