Compare commits

...
Author SHA1 Message Date
Ruben FiszelandClaude Opus 5 971f50993d feat: set OTEL resource attributes from the instance settings
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbLzVfFDZ9pjBGHSzCSrNZ
2026-09-10 16:43:31 +02:00
5 changed files with 63 additions and 0 deletions
+11
View File
@@ -732,6 +732,8 @@ struct OtelSetting {
otel_exporter_otlp_protocol: Option<String>,
#[serde(default, deserialize_with = "empty_as_none")]
otel_exporter_otlp_compression: Option<String>,
#[serde(default, deserialize_with = "empty_as_none")]
otel_resource_attributes: Option<String>,
}
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 {
+19
View File
@@ -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"));
}
@@ -631,6 +631,8 @@ pub struct OtelSettings {
pub otel_exporter_otlp_protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub otel_exporter_otlp_compression: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub otel_resource_attributes: Option<String>,
}
/// Per-language HTTP request tracing proxy configuration.
@@ -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 =
@@ -694,6 +694,26 @@
<option value="http/protobuf">http/protobuf</option>
</select>
</div>
<div class="flex flex-col gap-1">
<label
for="OTEL_RESOURCE_ATTRIBUTES"
class="block text-xs font-semibold text-emphasis">Resource attributes</label
>
<TextInput
inputProps={{
type: 'text',
placeholder: 'team=platform,region=eu-west-1',
id: 'OTEL_RESOURCE_ATTRIBUTES',
disabled: !$enterpriseLicense
}}
bind:value={$values[setting.key].otel_resource_attributes}
/>
<span class="text-2xs font-normal text-secondary">
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.
</span>
</div>
{/if}
</div>
{:else if setting.fieldType == 'otel_tracing_proxy'}