#![cfg(any(feature = "otlp", feature = "azure", feature = "gcp"))]
use std::sync::{Arc, Mutex};
use greentic_telemetry::{
TelemetryCtx, annotate_current_span, annotate_span, set_current_telemetry_ctx, with_task_local,
};
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter};
use tracing::subscriber;
use tracing_subscriber::prelude::*;
#[derive(Clone, Debug)]
struct TestExporter {
spans: Arc<Mutex<Vec<SpanData>>>,
}
impl SpanExporter for TestExporter {
fn export(
&self,
batch: Vec<SpanData>,
) -> impl std::future::Future<Output = OTelSdkResult> + Send {
let spans = self.spans.clone();
async move {
spans.lock().unwrap().extend(batch);
Ok(())
}
}
}
fn attr_keys(span: &SpanData) -> Vec<String> {
span.attributes
.iter()
.map(|kv| kv.key.to_string())
.collect()
}
fn attr_value(span: &SpanData, key: &str) -> Option<String> {
span.attributes
.iter()
.find(|kv| kv.key.as_str() == key)
.map(|kv| kv.value.to_string())
}
#[test]
fn task_local_ctx_exports_as_span_attributes_without_declared_fields() {
let exported = Arc::new(Mutex::new(Vec::new()));
let provider = SdkTracerProvider::builder()
.with_simple_exporter(TestExporter {
spans: exported.clone(),
})
.build();
let tracer = provider.tracer("b11-context-export");
let subscriber =
tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer));
let _guard = subscriber::set_default(subscriber);
let ctx = TelemetryCtx::new("acme")
.with_env("prod-eu")
.with_customer_id("cust-acme")
.with_deployment_id("01JTKS")
.with_bundle_id("customer.support")
.with_revision_id("01JTKR");
let span = tracing::info_span!("plain.op");
annotate_span(&span, &ctx);
{
let _enter = span.enter();
}
drop(span);
let _ = provider.force_flush();
let _ = provider.shutdown();
let spans = exported.lock().unwrap();
let span = spans.last().expect("span exported");
let keys = attr_keys(span);
for expected in [
"gt.tenant",
"gt.env",
"gt.customer_id",
"gt.deployment_id",
"gt.bundle_id",
"gt.revision_id",
] {
assert!(
keys.contains(&expected.to_string()),
"exported span missing `{expected}`; got {keys:?}"
);
}
assert_eq!(
attr_value(span, "gt.customer_id").as_deref(),
Some("cust-acme")
);
assert_eq!(
attr_value(span, "gt.revision_id").as_deref(),
Some("01JTKR")
);
}
#[test]
fn unset_rollout_ids_are_absent_from_exported_attributes() {
let exported = Arc::new(Mutex::new(Vec::new()));
let provider = SdkTracerProvider::builder()
.with_simple_exporter(TestExporter {
spans: exported.clone(),
})
.build();
let tracer = provider.tracer("b11-context-export-unset");
let subscriber =
tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer));
let _guard = subscriber::set_default(subscriber);
let span = tracing::info_span!("plain.op");
annotate_span(&span, &TelemetryCtx::new("acme").with_env("local"));
{
let _enter = span.enter();
}
drop(span);
let _ = provider.force_flush();
let _ = provider.shutdown();
let spans = exported.lock().unwrap();
let span = spans.last().expect("span exported");
let keys = attr_keys(span);
assert!(keys.contains(&"gt.tenant".to_string()));
assert!(keys.contains(&"gt.env".to_string()));
for absent in [
"gt.customer_id",
"gt.deployment_id",
"gt.bundle_id",
"gt.revision_id",
] {
assert!(
!keys.contains(&absent.to_string()),
"`{absent}` must be absent when unset; got {keys:?}"
);
}
}
#[tokio::test]
async fn annotate_current_span_uses_task_local_ctx() {
let exported = Arc::new(Mutex::new(Vec::new()));
let provider = SdkTracerProvider::builder()
.with_simple_exporter(TestExporter {
spans: exported.clone(),
})
.build();
let tracer = provider.tracer("b11-context-export-tasklocal");
let subscriber =
tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer));
let _guard = subscriber::set_default(subscriber);
with_task_local(async {
set_current_telemetry_ctx(
TelemetryCtx::new("acme")
.with_env("prod-eu")
.with_deployment_id("01JTKS"),
);
let span = tracing::info_span!("plain.op");
annotate_current_span(&span);
{
let _enter = span.enter();
}
drop(span);
})
.await;
let _ = provider.force_flush();
let _ = provider.shutdown();
let spans = exported.lock().unwrap();
let span = spans.last().expect("span exported");
assert_eq!(
attr_value(span, "gt.deployment_id").as_deref(),
Some("01JTKS")
);
assert_eq!(attr_value(span, "gt.env").as_deref(), Some("prod-eu"));
}