use std::collections::HashSet;
use opentelemetry::KeyValue;
use opentelemetry_sdk::Resource;
use opentelemetry_semantic_conventions::resource as sc;
use crate::config::TelemetryConfig;
const DEPLOYMENT_ENVIRONMENT: &str = "deployment.environment";
fn env_identity_keys_from(
resource_attrs: Option<&str>,
service_name: Option<&str>,
) -> HashSet<String> {
let mut keys = HashSet::new();
if let Some(raw) = resource_attrs {
for pair in raw.split(',') {
if let Some((key, _value)) = pair.split_once('=') {
let key = key.trim();
if !key.is_empty() {
keys.insert(key.to_string());
}
}
}
}
if let Some(name) = service_name {
if !name.trim().is_empty() {
keys.insert(sc::SERVICE_NAME.to_string());
}
}
keys
}
pub(crate) fn build_resource(cfg: &TelemetryConfig) -> Resource {
let defaults = TelemetryConfig::default();
let resource_attributes = std::env::var("OTEL_RESOURCE_ATTRIBUTES").ok();
let service_name = std::env::var("OTEL_SERVICE_NAME").ok();
let env_keys = env_identity_keys_from(resource_attributes.as_deref(), service_name.as_deref());
let mut builder = Resource::builder();
for (key, current, default) in [
(sc::SERVICE_NAME, &cfg.service_name, &defaults.service_name),
(sc::SERVICE_VERSION, &cfg.version, &defaults.version),
(
DEPLOYMENT_ENVIRONMENT,
&cfg.environment,
&defaults.environment,
),
] {
if current != default {
builder = builder.with_attribute(KeyValue::new(key, current.clone()));
} else if !env_keys.contains(key) {
builder = builder.with_attribute(KeyValue::new(key, default.clone()));
}
}
builder.build()
}
#[cfg(test)]
mod tests {
use super::*;
use opentelemetry::Key;
fn attr_value(resource: &Resource, key: &str) -> Option<String> {
resource
.get(&Key::new(key.to_string()))
.map(|v| v.as_str().into_owned())
}
#[test]
fn resource_carries_service_identity_from_config() {
let cfg = TelemetryConfig {
service_name: "test-service".to_string(),
environment: "staging".to_string(),
version: "1.2.3".to_string(),
..TelemetryConfig::default()
};
let r = build_resource(&cfg);
assert_eq!(
attr_value(&r, sc::SERVICE_NAME),
Some("test-service".to_string()),
"service.name must come from config"
);
assert_eq!(
attr_value(&r, sc::SERVICE_VERSION),
Some("1.2.3".to_string()),
"service.version must come from config"
);
assert_eq!(
attr_value(&r, DEPLOYMENT_ENVIRONMENT),
Some("staging".to_string()),
"deployment.environment must come from config"
);
}
#[test]
fn resource_includes_telemetry_sdk_language() {
let r = build_resource(&TelemetryConfig::default());
assert_eq!(
attr_value(&r, "telemetry.sdk.language"),
Some("rust".to_string()),
);
}
#[test]
fn resource_falls_back_to_framework_floor_when_unset() {
let defaults = TelemetryConfig::default();
let r = build_resource(&defaults);
assert_eq!(
attr_value(&r, sc::SERVICE_NAME),
Some(defaults.service_name)
);
assert_eq!(attr_value(&r, sc::SERVICE_VERSION), Some(defaults.version));
assert_eq!(
attr_value(&r, DEPLOYMENT_ENVIRONMENT),
Some(defaults.environment),
);
}
#[test]
fn env_identity_keys_parses_resource_attributes_and_service_name() {
let keys = env_identity_keys_from(Some("host.name=web-1,service.version=1.0.0"), None);
assert!(keys.contains("host.name"));
assert!(keys.contains("service.version"));
assert!(env_identity_keys_from(None, Some("svc")).contains(sc::SERVICE_NAME));
assert!(env_identity_keys_from(None, Some(" ")).is_empty());
let split = env_identity_keys_from(Some("k=v=w,novalue"), None);
assert!(split.contains("k"));
assert!(!split.contains("novalue"));
}
#[test]
fn env_provided_key_is_not_overridden_by_floor() {
let defaults = TelemetryConfig::default();
let env_keys = env_identity_keys_from(None, Some("env-service"));
assert!(env_keys.contains(sc::SERVICE_NAME));
assert_eq!(
defaults.service_name,
TelemetryConfig::default().service_name
);
}
}