use std::env;
use std::sync::{Arc, Mutex, OnceLock};
use opentelemetry::global::BoxedTracer;
use opentelemetry::metrics::{Meter, MeterProvider};
use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use subms::SubMsObserver;
use crate::resource::SubMsOtelResource;
use crate::{OtelObserver, OtelObserverAsync};
#[cfg(feature = "exporter-otlp")]
use crate::exporter_otlp::{ExporterOtlpHelper, OtlpProtocol};
#[cfg(feature = "exporter-prometheus")]
use crate::exporter_prometheus::ExporterPrometheusHelper;
#[cfg(feature = "exporter-stdout")]
use crate::exporter_stdout::ExporterStdoutHelper;
pub struct SubMsOtelAutoConfig {
pub meter: Meter,
pub tracer: BoxedTracer,
pub observer: Arc<dyn SubMsObserver>,
pub meter_provider: SdkMeterProvider,
pub tracer_provider: SdkTracerProvider,
}
pub struct SubMsObserverRegistration {
pub name: &'static str,
pub observer: Arc<dyn SubMsObserver>,
}
fn registry() -> &'static Mutex<Vec<SubMsObserverRegistration>> {
static REGISTERED: OnceLock<Mutex<Vec<SubMsObserverRegistration>>> = OnceLock::new();
REGISTERED.get_or_init(|| Mutex::new(Vec::new()))
}
pub fn register_observer(name: &'static str, observer: Arc<dyn SubMsObserver>) {
let mut lock = registry().lock().expect("observer registry poisoned");
lock.push(SubMsObserverRegistration { name, observer });
}
pub fn clear_registered_observers() {
let mut lock = registry().lock().expect("observer registry poisoned");
lock.clear();
}
pub fn registered_observers() -> Vec<Arc<dyn SubMsObserver>> {
let lock = registry().lock().expect("observer registry poisoned");
lock.iter().map(|r| Arc::clone(&r.observer)).collect()
}
pub fn auto_configure() -> SubMsOtelAutoConfig {
let resource = Resource::builder_empty()
.with_attributes(SubMsOtelResource::detect())
.build();
let (meter_provider, tracer_provider) = build_providers(resource);
let meter = meter_provider.meter("subms-otel");
let tracer = tracer_provider.tracer("subms-otel");
let boxed_tracer = BoxedTracer::new(Box::new(tracer));
let observer: Arc<dyn SubMsObserver> =
if let Some(o) = registered_observers().into_iter().next() {
o
} else if pick_async() {
Arc::new(OtelObserverAsync::new(meter.clone()))
} else {
Arc::new(OtelObserver::new(meter.clone()))
};
SubMsOtelAutoConfig {
meter,
tracer: boxed_tracer,
observer,
meter_provider,
tracer_provider,
}
}
fn pick_async() -> bool {
!matches!(
env::var("SUBMS_OTEL_ASYNC").ok().as_deref(),
Some("false") | Some("0") | Some("no")
)
}
#[allow(dead_code)]
fn exemplar_k() -> usize {
env::var("SUBMS_OTEL_EXEMPLARS_K")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(crate::DEFAULT_RESERVOIR_K)
}
fn build_providers(resource: Resource) -> (SdkMeterProvider, SdkTracerProvider) {
let endpoint_raw = env::var("OTEL_EXPORTER_OTLP_ENDPOINT")
.ok()
.filter(|s| !s.is_empty());
let _has_endpoint = endpoint_raw.is_some();
#[cfg(any(feature = "exporter-otlp", feature = "exporter-prometheus"))]
let endpoint = endpoint_raw;
#[cfg(not(any(feature = "exporter-otlp", feature = "exporter-prometheus")))]
let _ = endpoint_raw;
#[cfg(feature = "exporter-otlp")]
if endpoint.is_some() {
let protocol =
OtlpProtocol::from_env(env::var("OTEL_EXPORTER_OTLP_PROTOCOL").ok().as_deref());
if let Some(pair) =
ExporterOtlpHelper::build(endpoint.as_deref(), protocol, resource.clone())
{
return pair;
}
}
#[cfg(feature = "exporter-prometheus")]
{
let prom_requested = env::var("SUBMS_OTEL_PROMETHEUS")
.map(|v| matches!(v.as_str(), "1" | "true"))
.unwrap_or(false);
if !_has_endpoint
&& prom_requested
&& let Some((mp, _exporter)) = ExporterPrometheusHelper::build(resource.clone())
{
let tp = SdkTracerProvider::builder()
.with_resource(resource.clone())
.build();
return (mp, tp);
}
}
#[cfg(feature = "exporter-stdout")]
{
return ExporterStdoutHelper::build(resource);
}
#[allow(unreachable_code)]
no_op_providers(resource)
}
#[allow(dead_code)]
fn no_op_providers(resource: Resource) -> (SdkMeterProvider, SdkTracerProvider) {
let mp = SdkMeterProvider::builder()
.with_resource(resource.clone())
.build();
let tp = SdkTracerProvider::builder().with_resource(resource).build();
(mp, tp)
}