diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index de46f0b9c8..07f269ba73 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -732,6 +732,8 @@ struct OtelSetting { otel_exporter_otlp_protocol: Option, #[serde(default, deserialize_with = "empty_as_none")] otel_exporter_otlp_compression: Option, + #[serde(default, deserialize_with = "empty_as_none")] + otel_resource_attributes: Option, } pub async fn load_otel(db: &DB) { @@ -788,6 +790,15 @@ pub async fn load_otel(db: &DB) { std::env::set_var("OTEL_EXPORTER_OTLP_COMPRESSION", compression); } } + if let Some(resource_attributes) = o.otel_resource_attributes { + let merged = windmill_common::tracing_init::merge_otel_resource_attributes( + &resource_attributes, + std::env::var("OTEL_RESOURCE_ATTRIBUTES").ok().as_deref(), + ); + unsafe { + std::env::set_var("OTEL_RESOURCE_ATTRIBUTES", merged); + } + } println!("OTEL settings loaded: tracing ({tracing_enabled}), logs ({logs_enabled}), metrics ({metrics_enabled}), endpoint ({:?}), headers defined: ({})", endpoint, headers.is_some()); } else { diff --git a/backend/tests/otel.rs b/backend/tests/otel.rs index 1a4040d390..7318f45326 100644 --- a/backend/tests/otel.rs +++ b/backend/tests/otel.rs @@ -745,3 +745,22 @@ fn test_otlp_resource_dedicated_overrides_win() { Some(windmill_common::utils::GIT_VERSION) ); } + +#[test] +#[serial_test::serial] +fn test_otlp_resource_setting_attributes_keep_pod_attributes() { + let merged = windmill_common::tracing_init::merge_otel_resource_attributes( + "team=platform,region=eu-west-1", + Some("k8s.pod.uid=abc-123,region=us-east-1"), + ); + std::env::set_var("OTEL_RESOURCE_ATTRIBUTES", merged); + let attrs = resource_attrs(); + std::env::remove_var("OTEL_RESOURCE_ATTRIBUTES"); + + assert_eq!( + attrs.get("k8s.pod.uid").map(String::as_str), + Some("abc-123") + ); + assert_eq!(attrs.get("team").map(String::as_str), Some("platform")); + assert_eq!(attrs.get("region").map(String::as_str), Some("us-east-1")); +} diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index e28bf139cf..22ebc92f0a 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -631,6 +631,8 @@ pub struct OtelSettings { pub otel_exporter_otlp_protocol: Option, #[serde(skip_serializing_if = "Option::is_none")] pub otel_exporter_otlp_compression: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub otel_resource_attributes: Option, } /// Per-language HTTP request tracing proxy configuration. diff --git a/backend/windmill-common/src/tracing_init.rs b/backend/windmill-common/src/tracing_init.rs index b456e370fc..e7e08a14ca 100644 --- a/backend/windmill-common/src/tracing_init.rs +++ b/backend/windmill-common/src/tracing_init.rs @@ -48,6 +48,17 @@ pub const VERBOSE_TARGET: &str = "windmill_verbose"; /// when `OTEL_JOB_LOGS=true`. Stripped before forwarding to the tracing layer. pub const OTEL_PREFIX: &str = "OTEL: "; +/// Combines the instance setting's resource attributes with the `OTEL_RESOURCE_ATTRIBUTES` the +/// process started with, which a deployment sets per pod (e.g. `k8s.pod.uid` from the downward +/// API). Overwriting it would drop those; the env's pairs go last because the SDK keeps the last +/// value of a duplicate key, so the pod's own value wins. +pub fn merge_otel_resource_attributes(from_setting: &str, from_env: Option<&str>) -> String { + match from_env.map(str::trim).filter(|v| !v.is_empty()) { + Some(from_env) => format!("{from_setting},{from_env}"), + None => from_setting.to_string(), + } +} + /// Creates a Targets filter that optionally filters out verbose logs when quiet mode is enabled. fn create_targets_filter(default_env_filter: LevelFilter) -> Targets { let targets = diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 7b6c5e312e..dbe95927cd 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -694,6 +694,26 @@ +
+ + + + Added to everything Windmill exports, alongside the OTEL_RESOURCE_ATTRIBUTES env + var, which wins on a shared key. Windmill's own service.name, service.version, + host.name and deployment.environment take precedence over both. + +
{/if} {:else if setting.fieldType == 'otel_tracing_proxy'}