use crate::resources::kube_env_resource;
use once_cell::sync::Lazy;
use opentelemetry::{global, trace::TracerProvider, InstrumentationScope, KeyValue};
use opentelemetry_otlp::{SpanExporter, WithExportConfig};
use opentelemetry_sdk::{trace as sdktrace, Resource};
use opentelemetry_semantic_conventions::resource;
use std::sync::Mutex;
use crate::SubscriberConfig;
fn normalize_collector_url(collector_url: &str) -> String {
let collector_url = collector_url
.strip_suffix('/')
.unwrap_or(collector_url)
.strip_suffix("/v1/traces")
.unwrap_or(collector_url);
let collector_url = collector_url.strip_suffix('/').unwrap_or(collector_url);
collector_url.to_string() + "/v1/traces"
}
pub fn configure<T>(config: &SubscriberConfig<T>) -> sdktrace::Tracer {
let telemetry = config
.telemetry
.as_ref()
.expect("Telemetry config must be provided when the `traces` feature is enabled.");
let collector_url = normalize_collector_url(&telemetry.collector_url);
let otlp_exporter = SpanExporter::builder()
.with_http()
.with_endpoint(collector_url)
.build()
.expect("Failed to configure the OpenTelemetry OTLP span exporter");
let resource_builder = Resource::builder()
.with_service_name(telemetry.service_name.clone())
.with_attribute(KeyValue::new("environment", config.env.to_string()))
.with_attribute(KeyValue::new("country", config.country.to_string()))
.with_attributes(kube_env_resource());
let resource_builder = if let Some(version) = &config.version {
resource_builder.with_attribute(KeyValue::new(
resource::SERVICE_VERSION,
version.to_string(),
))
} else {
resource_builder
};
let resource = resource_builder.build();
let tracer_provider = sdktrace::SdkTracerProvider::builder()
.with_batch_exporter(otlp_exporter)
.with_resource(resource)
.build();
set_tracer_provider(tracer_provider.clone());
let scope = InstrumentationScope::builder("prima-tracing")
.with_version(env!("CARGO_PKG_VERSION"))
.build();
tracer_provider.tracer_with_scope(scope)
}
static TRACER_PROVIDER: Lazy<Mutex<Option<sdktrace::SdkTracerProvider>>> =
Lazy::new(Default::default);
fn set_tracer_provider(new_provider: sdktrace::SdkTracerProvider) {
global::set_tracer_provider(new_provider.clone());
let mut tracer_provider = TRACER_PROVIDER
.lock()
.expect("OpenTelemetry tracer provider mutex poisoned");
tracer_provider.replace(new_provider);
}
pub(crate) fn shutdown_tracer_provider() {
let tracer_provider = TRACER_PROVIDER
.lock()
.expect("OpenTelemetry tracer provider mutex poisoned")
.take()
.expect("OpenTelemetry tracer provider is missing, cannot shutdown");
if let Err(err) = tracer_provider.shutdown() {
eprintln!("Failed to shutdown the OpenTelemetry tracer provider: {err:?}");
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn normalize_collector_url_test() {
let base = "http://localhost:8080";
let expected = "http://localhost:8080/v1/traces";
assert_eq!(normalize_collector_url(base), expected);
let with_trailing_slash = format!("{base}/");
assert_eq!(
normalize_collector_url(with_trailing_slash.as_str()),
expected
);
let complete = format!("{base}/v1/traces");
assert_eq!(normalize_collector_url(complete.as_str()), expected);
}
}