diff --git a/libdd-data-pipeline/src/otlp/metrics.rs b/libdd-data-pipeline/src/otlp/metrics.rs index 2fb67b0b9b..36dd5421d5 100644 --- a/libdd-data-pipeline/src/otlp/metrics.rs +++ b/libdd-data-pipeline/src/otlp/metrics.rs @@ -181,6 +181,11 @@ fn build_attributes( push(k, v); } } + for tag in &group.additional_metric_tags { + if let Some((k, v)) = tag.split_once(':') { + push(k, v); + } + } if !otel_trace_semantics_enabled { push("datadog.operation.name", &group.name); push("datadog.span.type", &group.r#type); @@ -580,6 +585,38 @@ mod tests { assert_eq!(ok_s + err_s, ns_to_s(combined_ns)); } + #[test] + fn emits_additional_metric_tags_as_attributes() { + let g = group_with_exact(&[1_000_000_000], &[], |g| { + g.additional_metric_tags = vec![ + "custom.primary:a".into(), + "region:us-east".into(), + // Only the first `:` is a delimiter; the value keeps any embedded `:`. + "endpoint:https://host:8080".into(), + ]; + }); + let req = map_stats_to_otlp_metrics(&buckets(vec![g.clone()]), &resource(), false).unwrap(); + let a = points(&req)[0]["attributes"].as_array().unwrap(); + assert_eq!(str_at(a, "custom.primary"), Some("a")); + assert_eq!(str_at(a, "region"), Some("us-east")); + assert_eq!(str_at(a, "endpoint"), Some("https://host:8080")); + + // Additional metric tags are user/tracer-defined (not Datadog-internal), so unlike + // `datadog.*` attributes they still pass through in OTel-semantics mode. + let req = map_stats_to_otlp_metrics(&buckets(vec![g]), &resource(), true).unwrap(); + let a = points(&req)[0]["attributes"].as_array().unwrap(); + assert_eq!(str_at(a, "custom.primary"), Some("a")); + + // Malformed (no `:`) or empty-value entries are skipped rather than emitted verbatim. + let g = group_with_exact(&[1_000_000_000], &[], |g| { + g.additional_metric_tags = vec!["malformed".into(), "empty:".into()]; + }); + let req = map_stats_to_otlp_metrics(&buckets(vec![g]), &resource(), false).unwrap(); + let a = points(&req)[0]["attributes"].as_array().unwrap(); + assert!(!a.iter().any(|kv| kv["key"] == "malformed")); + assert!(!a.iter().any(|kv| kv["key"] == "empty")); + } + #[test] fn test_grpc_status_code_to_name() { assert_eq!(grpc_status_code_to_name("0"), Some("OK"));