use std::env;
use tracing::Level;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::{Layer, SubscriberExt};
use tracing_subscriber::util::SubscriberInitExt;
pub fn init() -> Result<(), TracingInitError> {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(format!("{}", Level::INFO)));
let format = env::var("OTEL_LOG_FORMAT").unwrap_or_else(|_| "compact".to_string());
let fmt_layer = match format.as_str() {
"json" => tracing_subscriber::fmt::layer()
.json()
.with_target(true)
.boxed(),
"pretty" => tracing_subscriber::fmt::layer()
.pretty()
.with_target(false)
.boxed(),
_ => tracing_subscriber::fmt::layer()
.compact()
.with_target(false)
.boxed(),
};
tracing_subscriber::registry()
.with(filter)
.with(fmt_layer)
.try_init()?;
#[cfg(feature = "otel")]
{
match otel::try_install_global()? {
Some(()) => tracing::info!("observability: OTLP exporter active"),
None => tracing::info!("observability: fmt-only (OTEL_EXPORTER_OTLP_ENDPOINT not set)"),
}
}
#[cfg(not(feature = "otel"))]
tracing::info!("observability: fmt-only (otel feature disabled)");
Ok(())
}
pub fn shutdown() {
#[cfg(feature = "otel")]
otel::shutdown();
}
#[derive(Debug, thiserror::Error)]
pub enum TracingInitError {
#[error("tracing subscriber already set: {0}")]
SubscriberSet(#[from] tracing::dispatcher::SetGlobalDefaultError),
#[error("tracing init failed: {0}")]
Init(String),
#[error("otlp exporter setup failed: {0}")]
Exporter(String),
}
impl From<tracing_subscriber::util::TryInitError> for TracingInitError {
fn from(err: tracing_subscriber::util::TryInitError) -> Self {
Self::Init(err.to_string())
}
}
#[cfg(feature = "otel")]
mod otel {
use std::env;
use super::TracingInitError;
pub(super) fn try_install_global() -> Result<Option<()>, TracingInitError> {
match env::var("OTEL_EXPORTER_OTLP_ENDPOINT") {
Ok(v) if !v.is_empty() => Ok(Some(())),
_ => Ok(None),
}
}
pub(super) fn shutdown() {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_is_idempotent_per_process() {
let result = init();
match result {
Ok(()) | Err(TracingInitError::SubscriberSet(_)) | Err(TracingInitError::Init(_)) => {}
Err(other) => panic!("unexpected error: {other}"),
}
}
}