Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
389 changes: 388 additions & 1 deletion datadog-opentelemetry/src/core/configuration/configuration.rs

Large diffs are not rendered by default.

17 changes: 4 additions & 13 deletions datadog-opentelemetry/src/core/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@
//!
//! # Sources of configuration
//!
//! ```text
//! ^ Highest precedence
//! |
//! * Opentelemetry Resource object
//! |
//! * ConfigBuilder setters
//! |
//! * "DD" prefixed env variables
//! |
//! * Default values
//! |
//! v Lowest level of precedence
//! ```
//! General precedence is: OpenTelemetry Resource object, `ConfigBuilder` setters, "DD"-prefixed
//! environment variables, then defaults. Environment uses a field-specific order:
//! `DD_ENV`/`ConfigBuilder::set_env`, Resource `deployment.environment.name` or
//! `deployment.environment`, `DD_TAGS[env]`, then `OTEL_RESOURCE_ATTRIBUTES`.

#[allow(clippy::module_inception)]
mod configuration;
Expand Down
78 changes: 65 additions & 13 deletions datadog-opentelemetry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,34 @@ mod telemetry_metrics_exporter;
mod text_map_propagator;
mod trace_id;

use std::sync::{Arc, RwLock};
use std::{
borrow::Cow,
sync::{Arc, RwLock},
};

use opentelemetry::{Key, KeyValue, Value};
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
use opentelemetry_semantic_conventions::resource::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME};
use opentelemetry_semantic_conventions::resource::SERVICE_NAME;

pub(crate) const DEPLOYMENT_ENVIRONMENT_NAME: &str =
opentelemetry_semantic_conventions::resource::DEPLOYMENT_ENVIRONMENT_NAME;
pub(crate) const LEGACY_DEPLOYMENT_ENVIRONMENT: &str = "deployment.environment";

pub(crate) fn is_environment_attribute(key: &str) -> bool {
key == DEPLOYMENT_ENVIRONMENT_NAME || key == LEGACY_DEPLOYMENT_ENVIRONMENT
}

pub(crate) fn resource_environment(resource: &Resource) -> Option<Cow<'_, str>> {
[DEPLOYMENT_ENVIRONMENT_NAME, LEGACY_DEPLOYMENT_ENVIRONMENT]
.into_iter()
.find_map(|key| {
resource
.iter()
.find(|(resource_key, _)| resource_key.as_str() == key)
.map(|(_, value)| value.as_str())
.filter(|value| !value.is_empty())
})
}

use crate::{
core::configuration::{Config, RemoteConfigUpdate},
Expand Down Expand Up @@ -369,7 +392,10 @@ impl DatadogTracingBuilder {
/// context](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/profiles/4719-process-ctx.md).
/// Publication errors are logged but otherwise ignored.
pub fn init(self) -> SdkTracerProvider {
let config = self.config.unwrap_or_else(|| Config::builder().build());
let config = Arc::new(self.config.unwrap_or_else(|| Config::builder().build()));

let (tracer_provider, propagator) =
make_tracer(config.clone(), self.tracer_provider, self.resource);

// For now, otel process context spec is linux-specific.
#[cfg(target_os = "linux")]
Expand All @@ -379,8 +405,6 @@ impl DatadogTracingBuilder {
dd_warn!("Couldn't publish the tracer metadata during global initialization. External readers such as an eBPF profiler won't be able to access the corresponding resource attributes: {e}");
}

let (tracer_provider, propagator) =
make_tracer(Arc::new(config), self.tracer_provider, self.resource);
opentelemetry::global::set_text_map_propagator(propagator);
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
tracer_provider
Expand Down Expand Up @@ -542,6 +566,8 @@ fn make_tracer(
resource: Option<Resource>,
) -> (SdkTracerProvider, DatadogPropagator) {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let dd_resource =
resolve_dd_resource(resource.unwrap_or(Resource::builder().build()), &config);
let registry = TraceRegistry::new(config.clone());
let resource_slot = Arc::new(RwLock::new(Resource::builder_empty().build()));
// Sampler only needs config for initialization (reads initial sampling rules)
Expand All @@ -554,8 +580,6 @@ fn make_tracer(

let agent_response_handler = sampler.on_agent_response();

let dd_resource =
create_dd_resource(resource.unwrap_or(Resource::builder().build()), &config);
tracer_provider_builder = tracer_provider_builder.with_resource(dd_resource);
let propagator = DatadogPropagator::new(config.clone(), registry.clone());

Expand Down Expand Up @@ -629,7 +653,21 @@ fn merge_resource<I: IntoIterator<Item = (Key, Value)>>(
builder.build()
}

fn create_dd_resource(resource: Resource, cfg: &Config) -> Resource {
fn without_environment_attributes(resource: &Resource) -> Resource {
let attributes = resource
.iter()
.filter(|(key, _)| !is_environment_attribute(key.as_str()))
.map(|(key, value)| KeyValue::new(key.clone(), value.clone()));
let mut builder = Resource::builder_empty();
if let Some(schema_url) = resource.schema_url() {
builder = builder.with_schema_url(attributes, schema_url.to_string());
} else {
builder = builder.with_attributes(attributes);
}
builder.build()
}

fn create_dd_resource(mut resource: Resource, cfg: &Config) -> Resource {
let otel_service_name: Option<Value> = resource.get(&Key::from_static_str(SERVICE_NAME));

// Collect attributes to add
Expand Down Expand Up @@ -662,11 +700,17 @@ fn create_dd_resource(resource: Resource, cfg: &Config) -> Resource {
));
}

// Handle environment - add it if configured and not already present
if let Some(env) = cfg.env() {
let otel_env: Option<Value> =
resource.get(&Key::from_static_str(DEPLOYMENT_ENVIRONMENT_NAME));
if otel_env.is_none() {
if let Some(env) = cfg.explicit_env() {
resource = without_environment_attributes(&resource);
attributes.push((
Key::from_static_str(DEPLOYMENT_ENVIRONMENT_NAME),
Value::from(env.to_string()),
));
} else if resource_environment(&resource).is_none() {
if let Some(env) = cfg
.global_tags_environment()
.or_else(|| cfg.otel_resource_environment())
{
attributes.push((
Key::from_static_str(DEPLOYMENT_ENVIRONMENT_NAME),
Value::from(env.to_string()),
Expand All @@ -682,6 +726,14 @@ fn create_dd_resource(resource: Resource, cfg: &Config) -> Resource {
}
}

fn resolve_dd_resource(resource: Resource, config: &Config) -> Resource {
let resource = create_dd_resource(resource, config);
config.set_calculated_environment(
resource_environment(&resource).map(|environment| environment.into_owned()),
);
resource
}

#[cfg(feature = "test-utils")]
pub fn make_test_tracer(shared_config: Arc<Config>) -> (SdkTracerProvider, DatadogPropagator) {
#![allow(missing_docs)]
Expand Down
1 change: 1 addition & 0 deletions datadog-opentelemetry/src/mappings/cached_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ impl CachedConfig {

let global_tags = cfg
.global_tags()
.filter(|(key, _)| *key != "env")
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect();

Expand Down
5 changes: 5 additions & 0 deletions datadog-opentelemetry/src/mappings/transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,11 @@ pub fn otel_span_to_dd_span<'a>(
}

for (key, value) in otel_resource.iter() {
if key.as_str() == DEPLOYMENT_ENVIRONMENT_NAME.key()
|| key.as_str() == DEPLOYMENT_ENVIRONMENT.key()
{
continue;
}
set_meta_otlp_with_semconv_mappings(key.as_str(), value, &mut dd_span);
}

Expand Down
66 changes: 66 additions & 0 deletions datadog-opentelemetry/src/mappings/transform/transform_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,72 @@ mod tests {
}
}

#[test]
fn test_otel_span_to_dd_span_consumes_environment_resource_attributes() {
for (name, resource_attributes, expected_env) in [
(
"stable only",
vec![("deployment.environment.name", "stable")],
"stable",
),
(
"legacy only",
vec![("deployment.environment", "legacy")],
"legacy",
),
(
"stable before legacy",
vec![
("deployment.environment.name", "stable"),
("deployment.environment", "legacy"),
],
"stable",
),
(
"legacy before stable",
vec![
("deployment.environment", "legacy"),
("deployment.environment.name", "stable"),
],
"stable",
),
] {
let resource = Resource::builder_empty()
.with_attributes(
resource_attributes
.into_iter()
.chain([("unrelated", "value")])
.map(|(key, value)| KeyValue::new(key, value)),
)
.build();
let input_span = test_cases().remove(0).input_span;
let output = otel_span_to_dd_span(&test_span_to_sdk_span(&input_span), &resource);

assert_eq!(
output
.meta
.get(&CowStr::from_str("env"))
.map(CowStr::as_str),
Some(expected_env),
"{name} environment"
);
assert_eq!(
output
.meta
.get(&CowStr::from_str("unrelated"))
.map(CowStr::as_str),
Some("value"),
"{name} unrelated resource attribute"
);
for source_key in ["deployment.environment.name", "deployment.environment"] {
assert!(
!output.meta.contains_key(&CowStr::from_str(source_key)),
"{name} retained {source_key}"
);
}
}
}

#[track_caller]
fn hashmap_diff<'a, V: PartialEq + Debug>(
output: &VecMap<CowStr<'a>, V>,
Expand Down
Loading
Loading