Skip to content
Merged
191 changes: 102 additions & 89 deletions bottlecap/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -62,6 +65,13 @@ pub struct Config {
pub serverless_flush_strategy: FlushStrategy,
pub enhanced_metrics: bool,
pub https_proxy: Option<String>,
// Trace Propagation
#[serde(deserialize_with = "deserialize_trace_propagation_style")]
pub trace_propagation_style: Vec<TracePropagationStyle>,
#[serde(deserialize_with = "deserialize_trace_propagation_style")]
pub trace_propagation_style_extract: Vec<TracePropagationStyle>,
pub trace_propagation_extract_first: bool,
pub trace_propagation_http_baggage_enabled: bool,
}

impl Default for Config {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -180,6 +198,15 @@ pub fn get_config(config_directory: &Path) -> Result<Config, ConfigError> {
}
}

// 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)
}

Expand Down Expand Up @@ -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");
Comment thread
duncanista marked this conversation as resolved.
Ok(())
});
}
Expand All @@ -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(())
});
}
Expand All @@ -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(())
});
}
Expand All @@ -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(())
});
}
Expand All @@ -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()
}
);
Expand All @@ -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(())
});
}
Expand All @@ -356,14 +355,8 @@ pub mod tests {
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()
}
config.serverless_flush_strategy,
FlushStrategy::Periodically(PeriodicStrategy { interval: 100_000 })
);
Ok(())
});
Expand All @@ -376,13 +369,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(())
});
}
Expand All @@ -397,13 +384,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(())
});
}
Expand All @@ -430,17 +411,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(())
});
Expand All @@ -464,39 +441,75 @@ 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(())
});
}

#[test]
fn test_ignore_apm_replace_tags() {
fn test_parse_trace_propagation_style() {
figment::Jail::expect_with(|jail| {
jail.clear_env();
jail.set_env(
"DD_APM_REPLACE_TAGS",
r#"[{"name":"resource.name","pattern":"(.*)/(foo[:%].+)","repl":"$1/{foo}"}]"#,
"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,
Config {
site: "datadoghq.com".to_string(),
..Config::default()
}
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| {
jail.clear_env();
jail.set_env(
"DD_APM_REPLACE_TAGS",
r#"[{"name":"resource.name","pattern":"(.*)/(foo[:%].+)","repl":"$1/{foo}"}]"#,
);
jail.set_env("DD_EXTENSION_VERSION", "next");
let config = get_config(Path::new(""));
assert!(config.is_ok());
Ok(())
});
}
Expand Down
58 changes: 58 additions & 0 deletions bottlecap/src/config/trace_propagation_style.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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<Vec<TracePropagationStyle>, 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()
}
7 changes: 5 additions & 2 deletions bottlecap/src/traces/context.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
use std::collections::HashMap;

#[derive(Copy, Clone, Default, Debug)]
use datadog_trace_protobuf::pb::SpanLink;

#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct Sampling {
pub priority: Option<i8>,
pub mechanism: Option<u8>,
}

#[derive(Clone, Default, Debug)]
#[derive(Clone, Default, Debug, PartialEq)]
#[allow(clippy::module_name_repetitions)]
pub struct SpanContext {
pub trace_id: u64,
pub span_id: u64,
pub sampling: Option<Sampling>,
pub origin: Option<String>,
pub tags: HashMap<String, String>,
pub links: Vec<SpanLink>,
}
Loading