gsm-telemetry 0.1.4

Tracing and OpenTelemetry configuration helpers for Greentic messaging services.
Documentation
use std::env;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TelemetryProtocol {
    Grpc,
    HttpProtobuf,
}

#[derive(Debug, Clone)]
pub struct TelemetryConfig {
    pub endpoint: String,
    pub protocol: TelemetryProtocol,
    pub service_name: String,
    pub service_version: String,
    pub environment: String,
    pub json_logs: bool,
    pub enabled: bool,
}

impl TelemetryConfig {
    pub fn from_env(default_service_name: &str, default_service_version: &str) -> Self {
        let endpoint = env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap_or_default();
        let protocol = env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
            .map(|v| match v.to_lowercase().as_str() {
                "http" | "http/protobuf" => TelemetryProtocol::HttpProtobuf,
                _ => TelemetryProtocol::Grpc,
            })
            .unwrap_or(TelemetryProtocol::Grpc);
        let service_name =
            env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| default_service_name.to_string());
        let service_version = env::var("OTEL_SERVICE_VERSION")
            .unwrap_or_else(|_| default_service_version.to_string());
        let environment = env::var("OTEL_RESOURCE_ATTRIBUTES")
            .ok()
            .and_then(parse_environment_from_resource)
            .unwrap_or_else(|| env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "dev".into()));
        let json_logs = env::var("LOG_FORMAT")
            .map(|v| !matches!(v.to_lowercase().as_str(), "text" | "pretty" | "plain"))
            .unwrap_or(true);
        let enabled_flag = env::var("ENABLE_OTEL")
            .map(|v| matches!(v.to_lowercase().as_str(), "1" | "true" | "yes" | "on"))
            .unwrap_or(false);
        let enabled = enabled_flag && !endpoint.trim().is_empty();

        Self {
            endpoint,
            protocol,
            service_name,
            service_version,
            environment,
            json_logs,
            enabled,
        }
    }

    pub fn exporter_enabled(&self) -> bool {
        self.enabled && !self.endpoint.trim().is_empty()
    }
}

fn parse_environment_from_resource(value: String) -> Option<String> {
    for kv in value.split(',') {
        let mut parts = kv.splitn(2, '=');
        let key = parts.next()?.trim();
        let val = parts.next()?.trim();
        if key == "deployment.environment" {
            return Some(val.to_string());
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_support::env_guard;

    fn clear_env(keys: &[&str]) {
        for key in keys {
            std::env::remove_var(key);
        }
    }

    #[test]
    fn from_env_uses_defaults_when_unset() {
        let _guard = env_guard();
        clear_env(&[
            "OTEL_EXPORTER_OTLP_ENDPOINT",
            "OTEL_EXPORTER_OTLP_PROTOCOL",
            "OTEL_SERVICE_NAME",
            "OTEL_SERVICE_VERSION",
            "OTEL_RESOURCE_ATTRIBUTES",
            "DEPLOYMENT_ENV",
            "LOG_FORMAT",
            "ENABLE_OTEL",
        ]);

        let cfg = TelemetryConfig::from_env("svc", "1.0.0");
        assert_eq!(cfg.service_name, "svc");
        assert_eq!(cfg.service_version, "1.0.0");
        assert_eq!(cfg.environment, "dev");
        assert!(!cfg.exporter_enabled());
        assert!(cfg.json_logs);
        assert!(matches!(cfg.protocol, TelemetryProtocol::Grpc));
    }

    #[test]
    fn from_env_reads_protocol_and_flags() {
        let _guard = env_guard();
        std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", "https://otel.local");
        std::env::set_var("OTEL_EXPORTER_OTLP_PROTOCOL", "http");
        std::env::set_var("OTEL_SERVICE_NAME", "svc-http");
        std::env::set_var("OTEL_SERVICE_VERSION", "2.0.0");
        std::env::set_var(
            "OTEL_RESOURCE_ATTRIBUTES",
            "foo=bar,deployment.environment=prod",
        );
        std::env::set_var("LOG_FORMAT", "text");
        std::env::set_var("ENABLE_OTEL", "true");

        let cfg = TelemetryConfig::from_env("svc", "1.0.0");
        assert!(cfg.exporter_enabled());
        assert_eq!(cfg.endpoint, "https://otel.local");
        assert!(matches!(cfg.protocol, TelemetryProtocol::HttpProtobuf));
        assert_eq!(cfg.environment, "prod");
        assert!(!cfg.json_logs);
        assert_eq!(cfg.service_name, "svc-http");
        assert_eq!(cfg.service_version, "2.0.0");

        clear_env(&[
            "OTEL_EXPORTER_OTLP_ENDPOINT",
            "OTEL_EXPORTER_OTLP_PROTOCOL",
            "OTEL_SERVICE_NAME",
            "OTEL_SERVICE_VERSION",
            "OTEL_RESOURCE_ATTRIBUTES",
            "LOG_FORMAT",
            "ENABLE_OTEL",
        ]);
    }

    #[test]
    fn parse_environment_handles_missing() {
        assert_eq!(parse_environment_from_resource("foo=bar".into()), None);
        assert_eq!(
            parse_environment_from_resource("deployment.environment=stage".into()),
            Some("stage".into())
        );
    }
}