From 98b278e0d2fc01ff502bb4aeb404b916255e7d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:48:23 -0400 Subject: [PATCH 01/10] add `trace_propagation_style.rs` --- .../src/config/trace_propagation_style.rs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 bottlecap/src/config/trace_propagation_style.rs diff --git a/bottlecap/src/config/trace_propagation_style.rs b/bottlecap/src/config/trace_propagation_style.rs new file mode 100644 index 000000000..6ebc9dc74 --- /dev/null +++ b/bottlecap/src/config/trace_propagation_style.rs @@ -0,0 +1,58 @@ +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Deserializer}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TracePropagationStyle { + Datadog, + B3Multi, + B3, + TraceContext, + None, +} + +impl FromStr for TracePropagationStyle { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "datadog" => Ok(TracePropagationStyle::Datadog), + "b3multi" => Ok(TracePropagationStyle::B3Multi), + "b3" => Ok(TracePropagationStyle::B3), + "tracecontext" => Ok(TracePropagationStyle::TraceContext), + "none" => Ok(TracePropagationStyle::None), + _ => Err(format!("Unknown trace propagation style: {s}")), + } + } +} + +impl Display for TracePropagationStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let style = match self { + TracePropagationStyle::Datadog => "datadog", + TracePropagationStyle::B3Multi => "b3multi", + TracePropagationStyle::B3 => "b3", + TracePropagationStyle::TraceContext => "tracecontext", + TracePropagationStyle::None => "none", + }; + write!(f, "{style}") + } +} + +#[allow(clippy::module_name_repetitions)] +pub fn deserialize_trace_propagation_style<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: String = String::deserialize(deserializer)?; + + s.split(',') + .map(|style| { + TracePropagationStyle::from_str(style.trim()).map_err(|e| { + serde::de::Error::custom(format!("Failed to deserialize propagation style: {e}")) + }) + }) + .collect() +} From 4eb30b9e715a8dfb6762ead280b7aeb86bb66f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:49:28 -0400 Subject: [PATCH 02/10] add Trace Propagation to `config.rs` also updated unit tests, as we have custom behavior, we should check only the fields we care about in the tests --- bottlecap/src/config/mod.rs | 148 ++++++++++++++---------------------- 1 file changed, 57 insertions(+), 91 deletions(-) diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index 8942c26a7..458cefe0c 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -1,12 +1,15 @@ pub mod flush_strategy; pub mod log_level; pub mod processing_rule; +pub mod trace_propagation_style; use std::path::Path; +use std::vec; use figment::providers::{Format, Yaml}; use figment::{providers::Env, Figment}; use serde::Deserialize; +use trace_propagation_style::{deserialize_trace_propagation_style, TracePropagationStyle}; use crate::config::flush_strategy::FlushStrategy; use crate::config::log_level::{deserialize_log_level, LogLevel}; @@ -62,6 +65,13 @@ pub struct Config { pub serverless_flush_strategy: FlushStrategy, pub enhanced_metrics: bool, pub https_proxy: Option, + // Trace Propagation + #[serde(deserialize_with = "deserialize_trace_propagation_style")] + pub trace_propagation_style: Vec, + #[serde(deserialize_with = "deserialize_trace_propagation_style")] + pub trace_propagation_style_extract: Vec, + pub trace_propagation_extract_first: bool, + pub trace_propagation_http_baggage_enabled: bool, } impl Default for Config { @@ -85,6 +95,14 @@ impl Default for Config { enhanced_metrics: true, // Failover https_proxy: None, + // Trace Propagation + trace_propagation_style: vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext, + ], + trace_propagation_style_extract: vec![], + trace_propagation_extract_first: false, + trace_propagation_http_baggage_enabled: false, } } } @@ -180,6 +198,15 @@ pub fn get_config(config_directory: &Path) -> Result { } } + // Trace Propagation + // + // If not set by the user, set defaults + if config.trace_propagation_style_extract.is_empty() { + config + .trace_propagation_style_extract + .clone_from(&config.trace_propagation_style); + } + Ok(config) } @@ -242,13 +269,7 @@ pub mod tests { )?; jail.set_env("DD_SITE", "datad0g.com"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datad0g.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.site, "datad0g.com"); Ok(()) }); } @@ -264,13 +285,7 @@ pub mod tests { ", )?; let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.site, "datadoghq.com"); Ok(()) }); } @@ -282,13 +297,7 @@ pub mod tests { jail.set_env("DD_SITE", "datadoghq.eu"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datadoghq.eu".to_string(), - ..Config::default() - } - ); + assert_eq!(config.site, "datadoghq.eu"); Ok(()) }); } @@ -300,14 +309,7 @@ pub mod tests { jail.set_env("DD_LOG_LEVEL", "TRACE"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - log_level: LogLevel::Trace, - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.log_level, LogLevel::Trace); Ok(()) }); } @@ -322,6 +324,10 @@ pub mod tests { config, Config { site: "datadoghq.com".to_string(), + trace_propagation_style_extract: vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext + ], ..Config::default() } ); @@ -336,14 +342,7 @@ pub mod tests { jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "end"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - serverless_flush_strategy: FlushStrategy::End, - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.serverless_flush_strategy, FlushStrategy::End); Ok(()) }); } @@ -355,16 +354,9 @@ pub mod tests { jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "periodically,100000"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - serverless_flush_strategy: FlushStrategy::Periodically(PeriodicStrategy { - interval: 100_000 - }), - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.serverless_flush_strategy, FlushStrategy::Periodically(PeriodicStrategy { + interval: 100_000 + })); Ok(()) }); } @@ -376,13 +368,7 @@ pub mod tests { jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "invalid_strategy"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.serverless_flush_strategy, FlushStrategy::Default); Ok(()) }); } @@ -397,13 +383,7 @@ pub mod tests { ); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + assert_eq!(config.serverless_flush_strategy, FlushStrategy::Default); Ok(()) }); } @@ -430,17 +410,13 @@ pub mod tests { jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); assert_eq!( - config, - Config { - logs_config_processing_rules: Some(vec![ProcessingRule { - kind: processing_rule::Kind::ExcludeAtMatch, - name: "exclude".to_string(), - pattern: "exclude".to_string(), - replace_placeholder: None - }]), - site: "datadoghq.com".to_string(), - ..Config::default() - } + config.logs_config_processing_rules, + Some(vec![ProcessingRule { + kind: processing_rule::Kind::ExcludeAtMatch, + name: "exclude".to_string(), + pattern: "exclude".to_string(), + replace_placeholder: None + }]) ); Ok(()) }); @@ -464,17 +440,13 @@ pub mod tests { )?; let config = get_config(Path::new("")).expect("should parse config"); assert_eq!( - config, - Config { - logs_config_processing_rules: Some(vec![ProcessingRule { - kind: processing_rule::Kind::ExcludeAtMatch, - name: "exclude".to_string(), - pattern: "exclude".to_string(), - replace_placeholder: None - }]), - site: "datadoghq.com".to_string(), - ..Config::default() - } + config.logs_config_processing_rules, + Some(vec![ProcessingRule { + kind: processing_rule::Kind::ExcludeAtMatch, + name: "exclude".to_string(), + pattern: "exclude".to_string(), + replace_placeholder: None + }]), ); Ok(()) }); @@ -489,14 +461,8 @@ pub mod tests { r#"[{"name":"resource.name","pattern":"(.*)/(foo[:%].+)","repl":"$1/{foo}"}]"#, ); jail.set_env("DD_EXTENSION_VERSION", "next"); - let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!( - config, - Config { - site: "datadoghq.com".to_string(), - ..Config::default() - } - ); + let config = get_config(Path::new("")); + assert!(config.is_ok()); Ok(()) }); } From 9e299641dfa3417a3595dfd1f3d3956eee8e7e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:49:47 -0400 Subject: [PATCH 03/10] add `links` to `SpanContext` --- bottlecap/src/traces/context.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bottlecap/src/traces/context.rs b/bottlecap/src/traces/context.rs index 35e9921ee..e851b7f11 100644 --- a/bottlecap/src/traces/context.rs +++ b/bottlecap/src/traces/context.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use datadog_trace_protobuf::pb::SpanLink; + #[derive(Copy, Clone, Default, Debug)] pub struct Sampling { pub priority: Option, @@ -14,4 +16,5 @@ pub struct SpanContext { pub sampling: Option, pub origin: Option, pub tags: HashMap, + pub links: Vec, } From d2b9b243082662ab35d65228089b8ddfe911500d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:50:35 -0400 Subject: [PATCH 04/10] add composite propagator also known as our internal http propagator, but in reality, http doesnt make any sense to me, its just a composite propagator which we used based on our configuration --- bottlecap/src/traces/propagation/mod.rs | 199 ++++++++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/bottlecap/src/traces/propagation/mod.rs b/bottlecap/src/traces/propagation/mod.rs index ef268ef61..1e0a2e13c 100644 --- a/bottlecap/src/traces/propagation/mod.rs +++ b/bottlecap/src/traces/propagation/mod.rs @@ -1,3 +1,202 @@ +use std::{collections::HashMap, sync::Arc}; + +use crate::{ + config::{self, trace_propagation_style::TracePropagationStyle}, + traces::context::SpanContext, +}; +use carrier::{Extractor, Injector}; +use datadog_trace_protobuf::pb::SpanLink; +use text_map_propagator::{ + BAGGAGE_PREFIX, DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY, DATADOG_LAST_PARENT_ID_KEY, + TRACESTATE_KEY, +}; + pub mod carrier; pub mod error; pub mod text_map_propagator; + +pub trait Propagator { + fn extract(&self, carrier: &dyn Extractor) -> Option; + fn inject(&self, context: SpanContext, carrier: &mut dyn Injector); +} + +pub struct DatadogCompositePropagator { + propagators: Vec>, + config: Arc, +} + +#[allow(clippy::never_loop)] +impl Propagator for DatadogCompositePropagator { + fn extract(&self, carrier: &dyn Extractor) -> Option { + if self.config.trace_propagation_extract_first { + for propagator in &self.propagators { + let context = propagator.extract(carrier); + + if self.config.trace_propagation_http_baggage_enabled { + if let Some(mut context) = context { + Self::attach_baggage(&mut context, carrier); + return Some(context); + } + } + + return context; + } + } + + let (contexts, styles) = self.extract_available_contexts(carrier); + if contexts.is_empty() { + return None; + } + + let mut context = Self::resolve_contexts(contexts, styles, carrier); + if self.config.trace_propagation_http_baggage_enabled { + Self::attach_baggage(&mut context, carrier); + } + + Some(context) + } + + fn inject(&self, _context: SpanContext, _carrier: &mut dyn Injector) { + todo!() + } +} + +impl DatadogCompositePropagator { + #[must_use] + pub fn new(config: Arc) -> Self { + let propagators: Vec> = config + .trace_propagation_style_extract + .iter() + .filter_map(|style| match style { + TracePropagationStyle::Datadog => { + Some(Box::new(text_map_propagator::DatadogHeaderPropagator) + as Box) + } + TracePropagationStyle::TraceContext => { + Some(Box::new(text_map_propagator::TraceContextPropagator) + as Box) + } + _ => None, + }) + .collect(); + + Self { + propagators, + config, + } + } + + fn extract_available_contexts( + &self, + carrier: &dyn Extractor, + ) -> (Vec, Vec) { + let mut contexts = Vec::::new(); + let mut styles = Vec::::new(); + + for (i, propagator) in self.propagators.iter().enumerate() { + if let Some(context) = propagator.extract(carrier) { + contexts.push(context); + styles.push(self.config.trace_propagation_style_extract[i]); + } + } + + (contexts, styles) + } + + fn resolve_contexts( + contexts: Vec, + styles: Vec, + _carrier: &dyn Extractor, + ) -> SpanContext { + let mut primary_context = contexts[0].clone(); + let mut links = Vec::::new(); + + let mut i = 1; + for context in contexts.iter().skip(1) { + let style = styles[i]; + + if context.span_id != 0 + && context.trace_id != 0 + && context.trace_id != primary_context.trace_id + { + let sampling = context.sampling.unwrap_or_default().priority.unwrap_or(0); + let tracestate: Option = match style { + TracePropagationStyle::TraceContext => { + context.tags.get(TRACESTATE_KEY).cloned() + } + _ => None, + }; + let attributes = HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), style.to_string()), + ]); + let trace_id_high_str = context + .tags + .get(DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY) + .cloned() + .unwrap_or_default(); + let trace_ig_high = u64::from_str_radix(&trace_id_high_str, 16).unwrap_or_default(); + + links.push(SpanLink { + trace_id: context.trace_id, + trace_id_high: trace_ig_high, + span_id: context.span_id, + flags: u32::from(sampling > 0), + tracestate: tracestate.unwrap_or_default(), + attributes, + }); + } else if style == TracePropagationStyle::TraceContext { + if let Some(tracestate) = context.tags.get(TRACESTATE_KEY) { + primary_context + .tags + .insert(TRACESTATE_KEY.to_string(), tracestate.clone()); + } + + if primary_context.trace_id == context.trace_id + && primary_context.span_id == context.span_id + { + let mut dd_context: Option = None; + if styles.contains(&TracePropagationStyle::Datadog) { + let position = styles + .iter() + .position(|&s| s == TracePropagationStyle::Datadog) + .unwrap_or_default(); + dd_context = contexts.get(position).cloned(); + } + + if let Some(parent_id) = context.tags.get(DATADOG_LAST_PARENT_ID_KEY) { + primary_context + .tags + .insert(DATADOG_LAST_PARENT_ID_KEY.to_string(), parent_id.clone()); + } else if let Some(sc) = dd_context { + primary_context.tags.insert( + DATADOG_LAST_PARENT_ID_KEY.to_string(), + format!("{:016x}", sc.span_id), + ); + } + + primary_context.span_id = context.span_id; + } + } + + i += 1; + } + + primary_context.links = links; + + primary_context + } + + fn attach_baggage(context: &mut SpanContext, carrier: &dyn Extractor) { + let keys = carrier.keys(); + + for key in keys { + if let Some(stripped) = key.strip_prefix(BAGGAGE_PREFIX) { + context.tags.insert( + stripped.to_string(), + carrier.get(key).unwrap_or_default().to_string(), + ); + } + } + } +} From 523a6500ae357c0a033c1638a25a9574e81637cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:51:37 -0400 Subject: [PATCH 05/10] update `TextMapPropagator`s to comply with interface also updated the naming --- .../traces/propagation/text_map_propagator.rs | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/bottlecap/src/traces/propagation/text_map_propagator.rs b/bottlecap/src/traces/propagation/text_map_propagator.rs index ce3f5abd1..a2a5a72df 100644 --- a/bottlecap/src/traces/propagation/text_map_propagator.rs +++ b/bottlecap/src/traces/propagation/text_map_propagator.rs @@ -5,13 +5,13 @@ use log::warn; use regex::Regex; use tracing::{debug, error}; -use super::{ +use crate::traces::context::{Sampling, SpanContext}; +use crate::traces::propagation::{ carrier::{Extractor, Injector}, error::Error, + Propagator, }; -use crate::traces::context::{Sampling, SpanContext}; - // Datadog Keys const DATADOG_TRACE_ID_KEY: &str = "x-datadog-trace-id"; const DATADOG_PARENT_ID_KEY: &str = "x-datadog-parent-id"; @@ -19,14 +19,16 @@ const DATADOG_SAMPLING_PRIORITY_KEY: &str = "x-datadog-sampling-priority"; const DATADOG_ORIGIN_KEY: &str = "x-datadog-origin"; const DATADOG_TAGS_KEY: &str = "x-datadog-tags"; -const DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY: &str = "_dd.p.tid"; +pub const DATADOG_HIGHER_ORDER_TRACE_ID_BITS_KEY: &str = "_dd.p.tid"; const DATADOG_PROPAGATION_ERROR_KEY: &str = "_dd.propagation_error"; -const DATADOG_LAST_PARENT_ID_KEY: &str = "_dd.parent_id"; +pub const DATADOG_LAST_PARENT_ID_KEY: &str = "_dd.parent_id"; const DATADOG_SAMPLING_DECISION_KEY: &str = "_dd.p.dm"; // Traceparent Keys const TRACEPARENT_KEY: &str = "traceparent"; -const TRACESTATE_KEY: &str = "tracestate"; +pub const TRACESTATE_KEY: &str = "tracestate"; + +pub const BAGGAGE_PREFIX: &str = "ot-baggage-"; lazy_static! { static ref TRACEPARENT_REGEX: Regex = @@ -43,17 +45,12 @@ lazy_static! { Regex::new(r"^-([0-9])$").expect("failed creating regex"); } -pub trait TextMapPropagator { - fn extract(&self, carrier: &dyn Extractor) -> SpanContext; - fn inject(&self, context: SpanContext, carrier: &mut dyn Injector); -} - #[derive(Clone, Copy)] -pub struct DatadogPropagator; +pub struct DatadogHeaderPropagator; -impl TextMapPropagator for DatadogPropagator { - fn extract(&self, carrier: &dyn Extractor) -> SpanContext { - Self::extract_context(carrier).unwrap_or_default() +impl Propagator for DatadogHeaderPropagator { + fn extract(&self, carrier: &dyn Extractor) -> Option { + Self::extract_context(carrier) } fn inject(&self, _context: SpanContext, _carrier: &mut dyn Injector) { @@ -61,7 +58,7 @@ impl TextMapPropagator for DatadogPropagator { } } -impl DatadogPropagator { +impl DatadogHeaderPropagator { fn extract_context(carrier: &dyn Extractor) -> Option { if let Some(trace_id) = Self::extract_trace_id(carrier) { let parent_id = Self::extract_parent_id(carrier).unwrap_or(0); @@ -80,6 +77,7 @@ impl DatadogPropagator { }), origin, tags, + links: Vec::new(), }); } @@ -203,11 +201,11 @@ struct Tracestate { } #[derive(Clone, Copy)] -pub struct TraceparentPropagator; +pub struct TraceContextPropagator; -impl TextMapPropagator for TraceparentPropagator { - fn extract(&self, carrier: &dyn Extractor) -> SpanContext { - Self::extract_context(carrier).unwrap_or_default() +impl Propagator for TraceContextPropagator { + fn extract(&self, carrier: &dyn Extractor) -> Option { + Self::extract_context(carrier) } fn inject(&self, _context: SpanContext, _carrier: &mut dyn Injector) { @@ -215,7 +213,7 @@ impl TextMapPropagator for TraceparentPropagator { } } -impl TraceparentPropagator { +impl TraceContextPropagator { fn extract_context(carrier: &dyn Extractor) -> Option { let tp = carrier.get(TRACEPARENT_KEY)?.trim(); @@ -259,6 +257,7 @@ impl TraceparentPropagator { }), origin, tags, + links: Vec::new(), }) } Err(e) => { @@ -455,9 +454,11 @@ mod test { "_dd.p.test=value,_dd.p.tid=4321,any=tag".to_string(), ); - let propagator = DatadogPropagator; + let propagator = DatadogHeaderPropagator; - let context = propagator.extract(&headers); + let context = propagator + .extract(&headers) + .expect("couldn't extract trace context"); assert_eq!(context.trace_id, 1234); assert_eq!(context.span_id, 5678); @@ -481,8 +482,10 @@ mod test { "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string(), ); - let propagator = TraceparentPropagator; - let context = propagator.extract(&headers); + let propagator = TraceContextPropagator; + let context = propagator + .extract(&headers) + .expect("couldn't extract trace context"); assert_eq!(context.trace_id, 7277407061855694839); assert_eq!(context.span_id, 67667974448284343); From 5290f1ae5dbf882110ed9931409abf59d0291101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:54:03 -0400 Subject: [PATCH 06/10] fmt --- bottlecap/src/config/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index 458cefe0c..17b092f22 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -354,9 +354,10 @@ pub mod tests { jail.set_env("DD_SERVERLESS_FLUSH_STRATEGY", "periodically,100000"); jail.set_env("DD_EXTENSION_VERSION", "next"); let config = get_config(Path::new("")).expect("should parse config"); - assert_eq!(config.serverless_flush_strategy, FlushStrategy::Periodically(PeriodicStrategy { - interval: 100_000 - })); + assert_eq!( + config.serverless_flush_strategy, + FlushStrategy::Periodically(PeriodicStrategy { interval: 100_000 }) + ); Ok(()) }); } From 7d48149f3fcc20932fdcbfa7a550bcda6abd02de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:31:35 -0400 Subject: [PATCH 07/10] add unit testing for `config.rs` --- bottlecap/src/config/mod.rs | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index 17b092f22..870b42d21 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -453,6 +453,52 @@ pub mod tests { }); } + #[test] + fn test_parse_trace_propagation_style() { + figment::Jail::expect_with(|jail| { + jail.clear_env(); + jail.set_env( + "DD_TRACE_PROPAGATION_STYLE", + "datadog,tracecontext,b3,b3multi", + ); + jail.set_env("DD_EXTENSION_VERSION", "next"); + let config = get_config(Path::new("")).expect("should parse config"); + + let expected_styles = vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext, + TracePropagationStyle::B3, + TracePropagationStyle::B3Multi, + ]; + assert_eq!(config.trace_propagation_style, expected_styles); + assert_eq!(config.trace_propagation_style_extract, expected_styles); + Ok(()) + }); + } + + #[test] + fn test_parse_trace_propagation_style_extract() { + figment::Jail::expect_with(|jail| { + jail.clear_env(); + jail.set_env("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "datadog"); + jail.set_env("DD_EXTENSION_VERSION", "next"); + let config = get_config(Path::new("")).expect("should parse config"); + + assert_eq!( + config.trace_propagation_style, + vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext, + ] + ); + assert_eq!( + config.trace_propagation_style_extract, + vec![TracePropagationStyle::Datadog] + ); + Ok(()) + }); + } + #[test] fn test_ignore_apm_replace_tags() { figment::Jail::expect_with(|jail| { From a2757e57bd829cd4ba318ef3688285a5bd0306a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:31:48 -0400 Subject: [PATCH 08/10] add `PartialEq` to `SpanContext` --- bottlecap/src/traces/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/traces/context.rs b/bottlecap/src/traces/context.rs index e851b7f11..600ae1c39 100644 --- a/bottlecap/src/traces/context.rs +++ b/bottlecap/src/traces/context.rs @@ -2,13 +2,13 @@ use std::collections::HashMap; use datadog_trace_protobuf::pb::SpanLink; -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, PartialEq)] pub struct Sampling { pub priority: Option, pub mechanism: Option, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq)] #[allow(clippy::module_name_repetitions)] pub struct SpanContext { pub trace_id: u64, From 2a49451035105309c08c25371e67f5b7343681f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:32:18 -0400 Subject: [PATCH 09/10] correct logic from `text_map_propagator.rs` logic was wrong in some parts, this was discovered through unit tests --- .../traces/propagation/text_map_propagator.rs | 120 ++++++++++-------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/bottlecap/src/traces/propagation/text_map_propagator.rs b/bottlecap/src/traces/propagation/text_map_propagator.rs index a2a5a72df..42b4a17fe 100644 --- a/bottlecap/src/traces/propagation/text_map_propagator.rs +++ b/bottlecap/src/traces/propagation/text_map_propagator.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; use lazy_static::lazy_static; -use log::warn; use regex::Regex; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; use crate::traces::context::{Sampling, SpanContext}; use crate::traces::propagation::{ @@ -60,28 +59,37 @@ impl Propagator for DatadogHeaderPropagator { impl DatadogHeaderPropagator { fn extract_context(carrier: &dyn Extractor) -> Option { - if let Some(trace_id) = Self::extract_trace_id(carrier) { - let parent_id = Self::extract_parent_id(carrier).unwrap_or(0); - let origin = Self::extract_origin(carrier); - let mut tags = Self::extract_tags(carrier); - let sampling_priority = Self::extract_sampling_priority(carrier).unwrap_or(2); - - Self::validate_sampling_decision(&mut tags); - - return Some(SpanContext { - trace_id, - span_id: parent_id, - sampling: Some(Sampling { - priority: Some(sampling_priority), - mechanism: None, - }), - origin, - tags, - links: Vec::new(), - }); - } + let trace_id = match Self::extract_trace_id(carrier) { + Ok(trace_id) => trace_id, + Err(e) => { + debug!("{e}"); + return None; + } + }; - None + let parent_id = Self::extract_parent_id(carrier).unwrap_or(0); + let sampling_priority = match Self::extract_sampling_priority(carrier) { + Ok(sampling_priority) => sampling_priority, + Err(e) => { + debug!("{e}"); + return None; + } + }; + let origin = Self::extract_origin(carrier); + let mut tags = Self::extract_tags(carrier); + Self::validate_sampling_decision(&mut tags); + + Some(SpanContext { + trace_id, + span_id: parent_id, + sampling: Some(Sampling { + priority: Some(sampling_priority), + mechanism: None, + }), + origin, + tags, + links: Vec::new(), + }) } fn validate_sampling_decision(tags: &mut HashMap) { @@ -106,14 +114,18 @@ impl DatadogHeaderPropagator { // todo: appsec standalone } - fn extract_trace_id(carrier: &dyn Extractor) -> Option { - let trace_id = carrier.get(DATADOG_TRACE_ID_KEY)?; + fn extract_trace_id(carrier: &dyn Extractor) -> Result { + let trace_id = carrier + .get(DATADOG_TRACE_ID_KEY) + .ok_or(Error::extract("`trace_id` not found", "datadog"))?; if INVALID_SEGMENT_REGEX.is_match(trace_id) { - return None; + return Err(Error::extract("Invalid `trace_id` found", "datadog")); } - trace_id.parse::().ok() + trace_id + .parse::() + .map_err(|_| Error::extract("Failed to decode `trace_id`", "datadog")) } fn extract_parent_id(carrier: &dyn Extractor) -> Option { @@ -122,10 +134,13 @@ impl DatadogHeaderPropagator { parent_id.parse::().ok() } - fn extract_sampling_priority(carrier: &dyn Extractor) -> Option { - let sampling_priority = carrier.get(DATADOG_SAMPLING_PRIORITY_KEY)?; + fn extract_sampling_priority(carrier: &dyn Extractor) -> Result { + // todo: enum? Default is USER_KEEP=2 + let sampling_priority = carrier.get(DATADOG_SAMPLING_PRIORITY_KEY).unwrap_or("2"); - sampling_priority.parse::().ok() + sampling_priority + .parse::() + .map_err(|_| Error::extract("Failed to decode `sampling_priority`", "datadog")) } fn extract_origin(carrier: &dyn Extractor) -> Option { @@ -145,7 +160,7 @@ impl DatadogHeaderPropagator { for pair in pairs { if let Some((k, v)) = pair.split_once('=') { // todo: reject key on tags extract reject - if k.starts_with("_dd.p") { + if k.starts_with("_dd.p.") { tags.insert(k.to_string(), v.to_string()); } } @@ -317,9 +332,10 @@ impl TraceContextPropagator { tracestate.lower_order_trace_id = Some(lo_tid.to_string()); } + // Convert from `t.` to `_dd.p.` for (k, v) in &dd { - if k.starts_with("t.") { - let nk = format!("_dd.p.{k}"); + if let Some(stripped) = k.strip_prefix("t.") { + let nk = format!("_dd.p.{stripped}"); tags.insert(nk, Self::decode_tag_value(v)); } } @@ -444,15 +460,16 @@ mod test { #[test] fn test_extract_datadog_propagator() { - let mut headers = HashMap::new(); - headers.insert("x-datadog-trace-id".to_string(), "1234".to_string()); - headers.insert("x-datadog-parent-id".to_string(), "5678".to_string()); - headers.insert("x-datadog-sampling-priority".to_string(), "1".to_string()); - headers.insert("x-datadog-origin".to_string(), "synthetics".to_string()); - headers.insert( - "x-datadog-tags".to_string(), - "_dd.p.test=value,_dd.p.tid=4321,any=tag".to_string(), - ); + let headers = HashMap::from([ + ("x-datadog-trace-id".to_string(), "1234".to_string()), + ("x-datadog-parent-id".to_string(), "5678".to_string()), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ("x-datadog-origin".to_string(), "synthetics".to_string()), + ( + "x-datadog-tags".to_string(), + "_dd.p.test=value,_dd.p.tid=4321,any=tag".to_string(), + ), + ]); let propagator = DatadogHeaderPropagator; @@ -472,15 +489,16 @@ mod test { #[test] fn test_extract_traceparent_propagator() { - let mut headers = HashMap::new(); - headers.insert( - "traceparent".to_string(), - "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string(), - ); - headers.insert( - "tracestate".to_string(), - "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string(), - ); + let headers = HashMap::from([ + ( + "traceparent".to_string(), + "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string(), + ), + ( + "tracestate".to_string(), + "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string(), + ), + ]); let propagator = TraceContextPropagator; let context = propagator From 0209456ee86414227a5ae2724697b331026f1805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:32:44 -0400 Subject: [PATCH 10/10] add unit tests for `DatadogCompositePropagator` also corrected some logic --- bottlecap/src/traces/propagation/mod.rs | 673 +++++++++++++++++++++++- 1 file changed, 672 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/traces/propagation/mod.rs b/bottlecap/src/traces/propagation/mod.rs index 1e0a2e13c..e93d81329 100644 --- a/bottlecap/src/traces/propagation/mod.rs +++ b/bottlecap/src/traces/propagation/mod.rs @@ -153,7 +153,7 @@ impl DatadogCompositePropagator { } if primary_context.trace_id == context.trace_id - && primary_context.span_id == context.span_id + && primary_context.span_id != context.span_id { let mut dd_context: Option = None; if styles.contains(&TracePropagationStyle::Datadog) { @@ -200,3 +200,674 @@ impl DatadogCompositePropagator { } } } + +#[cfg(test)] +pub mod tests { + use std::vec; + + use lazy_static::lazy_static; + + use crate::traces::context::Sampling; + + use super::*; + + lazy_static! { + static ref TRACE_ID: u128 = 171395628812617415352188477958425669623; + static ref TRACE_ID_LOWER_ORDER_BITS: u64 = *TRACE_ID as u64; + static ref TRACE_ID_HEX: String = String::from("80f198ee56343ba864fe8b2a57d3eff7"); + + // TraceContext Headers + static ref VALID_TRACECONTEXT_HEADERS_BASIC: HashMap = HashMap::from([ + ( + "traceparent".to_string(), + format!("00-{}-00f067aa0ba902b7-01", *TRACE_ID_HEX) + ), + ( + "tracestate".to_string(), + "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string() + ), + ]); + static ref VALID_TRACECONTEXT_HEADERS_RUM_NO_SAMPLING_DECISION: HashMap = + HashMap::from([ + ( + "traceparent".to_string(), + format!("00-{}-00f067aa0ba902b7-00", *TRACE_ID_HEX) + ), + ( + "tracestate".to_string(), + "dd=o:rum".to_string() + ), + ]); + static ref VALID_TRACECONTEXT_HEADERS: HashMap = HashMap::from([ + ( + "traceparent".to_string(), + format!("00-{}-00f067aa0ba902b7-01", *TRACE_ID_HEX) + ), + ( + "tracestate".to_string(), + "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMz".to_string() + ), + ]); + static ref VALID_TRACECONTEXT_HEADERS_VALID_64_BIT_TRACE_ID: HashMap = + HashMap::from([ + ( + "traceparent".to_string(), + "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string() + ), + ( + "tracestate".to_string(), + "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE".to_string() + ), + ]); + + // Datadog Headers + static ref VALID_DATADOG_HEADERS: HashMap = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "13088165645273925489".to_string(), + ), + ("x-datadog-parent-id".to_string(), "5678".to_string(),), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ("x-datadog-origin".to_string(), "synthetics".to_string()), + ]); + static ref VALID_DATADOG_HEADERS_NO_PRIORITY: HashMap = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "13088165645273925489".to_string(), + ), + ("x-datadog-parent-id".to_string(), "5678".to_string(),), + ("x-datadog-origin".to_string(), "synthetics".to_string()), + ]); + static ref VALID_DATADOG_HEADERS_MATCHING_TRACE_CONTEXT_VALID_TRACE_ID: HashMap = + HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + TRACE_ID_LOWER_ORDER_BITS.to_string() + ), + ("x-datadog-parent-id".to_string(), "5678".to_string()), + ("x-datadog-origin".to_string(), "synthetics".to_string()), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ]); + static ref INVALID_DATADOG_HEADERS: HashMap = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "13088165645273925489".to_string(), + ), + ("x-datadog-parent-id".to_string(), "parent_id".to_string(),), + ("x-datadog-sampling-priority".to_string(), "sample".to_string()), + ]); + + // Fixtures + // + static ref ALL_VALID_HEADERS: HashMap = { + let mut h = HashMap::new(); + h.extend(VALID_DATADOG_HEADERS.clone()); + h.extend(VALID_TRACECONTEXT_HEADERS.clone()); + // todo: add b3 + h + }; + static ref DATADOG_TRACECONTEXT_MATCHING_TRACE_ID_HEADERS: HashMap = { + let mut h = HashMap::new(); + h.extend(VALID_DATADOG_HEADERS_MATCHING_TRACE_CONTEXT_VALID_TRACE_ID.clone()); + // We use 64-bit traceparent trace id value here so it can match for + // both 128-bit enabled and disabled + h.extend(VALID_TRACECONTEXT_HEADERS_VALID_64_BIT_TRACE_ID.clone()); + h + }; + // Edge cases + static ref ALL_HEADERS_CHAOTIC_1: HashMap = { + let mut h = HashMap::new(); + h.extend(VALID_DATADOG_HEADERS_MATCHING_TRACE_CONTEXT_VALID_TRACE_ID.clone()); + h.extend(VALID_TRACECONTEXT_HEADERS_VALID_64_BIT_TRACE_ID.clone()); + // todo: add b3 + h + }; + static ref ALL_HEADERS_CHAOTIC_2: HashMap = { + let mut h = HashMap::new(); + h.extend(VALID_DATADOG_HEADERS.clone()); + h.extend(VALID_TRACECONTEXT_HEADERS_VALID_64_BIT_TRACE_ID.clone()); + // todo: add b3 + h + }; + static ref NO_TRACESTATE_SUPPORT_NOT_MATCHING_TRACE_ID: HashMap = { + let mut h = HashMap::new(); + h.extend(VALID_DATADOG_HEADERS.clone()); + h.extend(VALID_TRACECONTEXT_HEADERS_RUM_NO_SAMPLING_DECISION.clone()); + h + }; + } + + macro_rules! test_propagation_extract { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (styles, carrier, expected) = $value; + let mut config = config::Config::default(); + config.trace_propagation_style_extract = vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]; + if let Some(s) = styles { + config.trace_propagation_style_extract.clone_from(&s); + } + + let propagator = DatadogCompositePropagator::new(Arc::new(config)); + + let context = propagator.extract(&carrier).unwrap_or_default(); + + assert_eq!(context, expected); + } + )* + } + } + + test_propagation_extract! { + // Datadog Headers + valid_datadog_default: ( + None, + VALID_DATADOG_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![], + } + ), + valid_datadog_no_priority: ( + None, + VALID_DATADOG_HEADERS_NO_PRIORITY.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(2), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![], + }, + ), + invalid_datadog: ( + Some(vec![TracePropagationStyle::Datadog]), + INVALID_DATADOG_HEADERS.clone(), + SpanContext::default(), + ), + valid_datadog_explicit_style: ( + Some(vec![TracePropagationStyle::Datadog]), + VALID_DATADOG_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![], + }, + ), + invalid_datadog_negative_trace_id: ( + Some(vec![TracePropagationStyle::Datadog]), + HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "-1".to_string(), + ), + ("x-datadog-parent-id".to_string(), "5678".to_string(),), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ("x-datadog-origin".to_string(), "synthetics".to_string()), + ]), + SpanContext::default(), + ), + valid_datadog_no_datadog_style: ( + Some(vec![TracePropagationStyle::TraceContext]), + VALID_DATADOG_HEADERS.clone(), + SpanContext::default(), + ), + // TraceContext Headers + valid_tracecontext_simple: ( + Some(vec![TracePropagationStyle::TraceContext]), + VALID_TRACECONTEXT_HEADERS_BASIC.clone(), + SpanContext { + trace_id: 7277407061855694839, + span_id: 67667974448284343, + sampling: Some(Sampling { + priority: Some(2), + mechanism: None, + }), + origin: Some("rum".to_string()), + tags: HashMap::from([ + ("tracestate".to_string(), "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string()), + ("_dd.p.tid".to_string(), "9291375655657946024".to_string()), + ("traceparent".to_string(), "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string()), + ("_dd.parent_id".to_string(), "00f067aa0ba902b7".to_string()), + ]), + links: vec![], + } + ), + valid_tracecontext_rum_no_sampling_decision: ( + Some(vec![TracePropagationStyle::TraceContext]), + VALID_TRACECONTEXT_HEADERS_RUM_NO_SAMPLING_DECISION.clone(), + SpanContext { + trace_id: 7277407061855694839, + span_id: 67667974448284343, + sampling: Some(Sampling { + priority: Some(0), + mechanism: None, + }), + origin: Some("rum".to_string()), + tags: HashMap::from([ + ("_dd.p.tid".to_string(), "9291375655657946024".to_string()), + ("tracestate".to_string(), "dd=o:rum".to_string()), + ("traceparent".to_string(), "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-00".to_string()), + ]), + links: vec![], + } + ), + // B3 Headers + // todo: all of them + // B3 single Headers + // todo: all of them + // All Headers + valid_all_headers: ( + None, + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![ + SpanLink { + trace_id: 7277407061855694839, + trace_id_high: 0, + span_id: 67667974448284343, + flags: 1, + tracestate: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMz".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "tracecontext".to_string()), + ]), + } + ], + }, + ), + valid_all_headers_all_styles: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]), + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![ + SpanLink { + trace_id: 7277407061855694839, + trace_id_high: 0, + span_id: 67667974448284343, + flags: 1, + tracestate: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMz".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "tracecontext".to_string()), + ]), + } + // todo: b3 span links + ], + }, + ), + valid_all_headers_datadog_style: ( + Some(vec![TracePropagationStyle::Datadog]), + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![] + }, + ), + // todo: valid_all_headers_b3_style + // todo: valid_all_headers_both_b3_styles + // todo: valid_all_headers_b3_single_style + none_style: ( + Some(vec![TracePropagationStyle::None]), + ALL_VALID_HEADERS.clone(), + SpanContext::default(), + ), + valid_style_and_none_still_extracts: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::None]), + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![], + } + ), + // Order matters + // todo: order_matters_b3_single_header_first + // todo: order_matters_b3_first + // todo: order_matters_b3_second_no_datadog_headers + // Tracestate is still added when TraceContext style comes later and matches + // first style's `trace_id` + additional_tracestate_support_when_present_and_matches_first_style_trace_id: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]), + DATADOG_TRACECONTEXT_MATCHING_TRACE_ID_HEADERS.clone(), + SpanContext { + trace_id: 7277407061855694839, + span_id: 67667974448284343, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()), + ("_dd.parent_id".to_string(), "000000000000162e".to_string()), + (TRACESTATE_KEY.to_string(), "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE".to_string()) + ]), + links: vec![], + } + ), + // Tracestate is not added when TraceContext style comes later and does not + // match first style's `trace_id` + no_additional_tracestate_support_when_present_and_trace_id_does_not_match: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]), + NO_TRACESTATE_SUPPORT_NOT_MATCHING_TRACE_ID.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![ + SpanLink { + trace_id: 7277407061855694839, + trace_id_high: 0, + span_id: 67667974448284343, + flags: 0, + tracestate: "dd=o:rum".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "tracecontext".to_string()), + ]), + } + ], + } + ), + valid_all_headers_no_style: ( + Some(vec![]), + ALL_VALID_HEADERS.clone(), + SpanContext::default(), + ), + datadog_tracecontext_conflicting_span_ids: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]), + HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "9291375655657946024".to_string(), + ), + ("x-datadog-parent-id".to_string(), "15".to_string(),), + ("traceparent".to_string(), "00-000000000000000080f198ee56343ba8-000000000000000a-01".to_string()), + ]), + SpanContext { + trace_id: 9291375655657946024, + span_id: 10, + sampling: Some(Sampling { + priority: Some(2), + mechanism: None, + }), + origin: None, + tags: HashMap::from([ + ("_dd.parent_id".to_string(), "000000000000000f".to_string()), + ("_dd.p.dm".to_string(), "-3".to_string()), + ]), + links: vec![], + } + ), + // todo: all_headers_all_styles_tracecontext_t_id_match_no_span_link + all_headers_all_styles_do_not_create_span_link_for_context_w_out_span_id: ( + Some(vec![TracePropagationStyle::TraceContext, TracePropagationStyle::Datadog]), + ALL_HEADERS_CHAOTIC_2.clone(), + SpanContext { + trace_id: 7277407061855694839, + span_id: 67667974448284343, + sampling: Some(Sampling { + priority: Some(2), + mechanism: None, + }), + origin: Some("rum".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-4".to_string()), + ("_dd.p.tid".to_string(), "0".to_string()), + ("_dd.p.usr.id".to_string(), "baz64".to_string()), + ("traceparent".to_string(), "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string()), + ("tracestate".to_string(), "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE".to_string()), + ]), + links: vec![ + SpanLink { + trace_id: 13088165645273925489, + trace_id_high: 0, + span_id: 5678, + flags: 1, + tracestate: "".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "datadog".to_string()), + ]), + } + ], + } + ), + all_headers_all_styles_tracecontext_primary_only_datadog_t_id_diff: ( + Some(vec![TracePropagationStyle::TraceContext, TracePropagationStyle::Datadog]), + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 7277407061855694839, + span_id: 67667974448284343, + sampling: Some(Sampling { + priority: Some(2), + mechanism: None, + }), + origin: Some("rum".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-4".to_string()), + ("_dd.p.tid".to_string(), "9291375655657946024".to_string()), + ("_dd.p.usr.id".to_string(), "baz64".to_string()), + ("traceparent".to_string(), "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string()), + ("tracestate".to_string(), "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMz".to_string()), + ]), + links: vec![ + SpanLink { + trace_id: 13088165645273925489, + trace_id_high: 0, + span_id: 5678, + flags: 1, + tracestate: "".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "datadog".to_string()), + ]), + } + ], + } + ), + // todo: fix this test + all_headers_all_styles_datadog_primary_only_datadog_t_id_diff: ( + Some(vec![TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext]), + ALL_VALID_HEADERS.clone(), + SpanContext { + trace_id: 13088165645273925489, + span_id: 5678, + sampling: Some(Sampling { + priority: Some(1), + mechanism: None, + }), + origin: Some("synthetics".to_string()), + tags: HashMap::from([ + ("_dd.p.dm".to_string(), "-3".to_string()) + ]), + links: vec![ + SpanLink { + trace_id: 7277407061855694839, + // this should be `9291375655657946024` not `0`, but we don't have this data + // with the current definition of `SpanContext` + trace_id_high: 0, + span_id: 67667974448284343, + flags: 1, + tracestate: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMz".to_string(), + attributes: HashMap::from([ + ("reason".to_string(), "terminated_context".to_string()), + ("context_headers".to_string(), "tracecontext".to_string()), + ]), + } + ], + } + ), + // todo: datadog_primary_match_tracecontext_dif_from_b3_b3multi_invalid + } + + #[test] + fn test_new_filter_propagators() { + let mut config = config::Config::default(); + config.trace_propagation_style_extract = vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext, + TracePropagationStyle::B3, + TracePropagationStyle::B3Multi, + ]; + + let propagator = DatadogCompositePropagator::new(Arc::new(config)); + + assert_eq!(propagator.propagators.len(), 2); + } + + #[test] + fn test_new_no_propagators() { + let mut config = config::Config::default(); + config.trace_propagation_style_extract = vec![TracePropagationStyle::None]; + let propagator = DatadogCompositePropagator::new(Arc::new(config)); + + assert_eq!(propagator.propagators.len(), 0); + } + + #[test] + fn test_extract_available_contexts() { + let mut config = config::Config::default(); + config.trace_propagation_style_extract = vec![ + TracePropagationStyle::Datadog, + TracePropagationStyle::TraceContext, + ]; + + let propagator = DatadogCompositePropagator::new(Arc::new(config)); + + let carrier = HashMap::from([ + ( + "traceparent".to_string(), + "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string(), + ), + ( + "tracestate".to_string(), + "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string(), + ), + ( + "x-datadog-trace-id".to_string(), + "7277407061855694839".to_string(), + ), + ( + "x-datadog-parent-id".to_string(), + "67667974448284343".to_string(), + ), + ("x-datadog-sampling-priority".to_string(), "2".to_string()), + ("x-datadog-origin".to_string(), "rum".to_string()), + ( + "x-datadog-tags".to_string(), + "_dd.p.test=value,_dd.p.tid=9291375655657946024,any=tag".to_string(), + ), + ]); + let (contexts, styles) = propagator.extract_available_contexts(&carrier); + + assert_eq!(contexts.len(), 2); + assert_eq!(styles.len(), 2); + } + + #[test] + fn test_extract_available_contexts_no_contexts() { + let mut config = config::Config::default(); + config.trace_propagation_style_extract = vec![TracePropagationStyle::Datadog]; + + let propagator = DatadogCompositePropagator::new(Arc::new(config)); + + let carrier = HashMap::from([ + ( + "traceparent".to_string(), + "00-80f198ee56343ba864fe8b2a57d3eff7-00f067aa0ba902b7-01".to_string(), + ), + ( + "tracestate".to_string(), + "dd=p:00f067aa0ba902b7;s:2;o:rum".to_string(), + ), + ]); + let (contexts, styles) = propagator.extract_available_contexts(&carrier); + + assert_eq!(contexts.len(), 0); + assert_eq!(styles.len(), 0); + } + + #[test] + fn test_attach_baggage() { + let mut context = SpanContext::default(); + let carrier = HashMap::from([ + ("x-datadog-trace-id".to_string(), "123".to_string()), + ("x-datadog-parent-id".to_string(), "5678".to_string()), + ("ot-baggage-key1".to_string(), "value1".to_string()), + ]); + + DatadogCompositePropagator::attach_baggage(&mut context, &carrier); + + assert_eq!(context.tags.len(), 1); + assert_eq!(context.tags.get("key1").unwrap(), "value1"); + } +}