use crate::platform::Config;
use tracing_subscriber::Layer;
use tracing_subscriber::filter::{FilterExt, Targets};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
pub(crate) fn init(config: &Config, service: &str) -> Option<Flush> {
if tracing::dispatcher::has_been_set() {
return None;
}
let level = config.get("log.level").unwrap_or("info");
let (filter, bad_level) = match level.parse::<Targets>() {
Ok(f) => (f, false),
Err(_) => (Targets::new().with_default(tracing::Level::INFO), true),
};
let events_only = filter.clone().and(tracing_subscriber::filter::filter_fn(|m| m.is_event()));
let stderr =
tracing_subscriber::fmt::layer().with_writer(std::io::stderr).with_ansi(false).with_filter(events_only);
#[cfg(feature = "otel")]
let (otel, provider, otel_error) = match otel::exporter(config, service) {
Ok(Some(provider)) => (Some(otel::layer(&provider).with_filter(filter)), Some(provider), None),
Ok(None) => (None, None, None),
Err(e) => (None, None, Some(e)),
};
#[cfg(not(feature = "otel"))]
let (otel, provider, otel_error): (Option<tracing_subscriber::layer::Identity>, Option<()>, Option<String>) = {
let _ = (service, filter);
(None, None, None)
};
if tracing_subscriber::registry().with(stderr).with(otel).try_init().is_err() {
return None;
}
if bad_level {
tracing::warn!(level, "log.level is not a valid filter; using info");
}
if let Some(e) = otel_error {
tracing::warn!("OTLP export is off: {e}");
}
provider.map(Flush)
}
#[cfg(feature = "otel")]
pub(crate) struct Flush(opentelemetry_sdk::trace::SdkTracerProvider);
#[cfg(not(feature = "otel"))]
pub(crate) struct Flush(());
impl Flush {
pub(crate) async fn shutdown(self) {
#[cfg(feature = "otel")]
{
let provider = self.0;
if let Ok(Err(e)) = tokio::task::spawn_blocking(move || provider.shutdown()).await {
tracing::warn!("trace export did not finish: {e}");
}
}
}
}
#[cfg(feature = "otel")]
mod otel {
use super::Config;
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::WithExportConfig as _;
use opentelemetry_sdk::trace::SdkTracerProvider;
use tracing_subscriber::registry::LookupSpan;
pub(super) fn exporter(config: &Config, service: &str) -> Result<Option<SdkTracerProvider>, String> {
let Some(endpoint) = config.get("otel.endpoint").map(|e| e.trim_end_matches('/')) else { return Ok(None) };
let endpoint =
if endpoint.ends_with("/v1/traces") { endpoint.to_string() } else { format!("{endpoint}/v1/traces") };
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_http()
.with_endpoint(endpoint)
.build()
.map_err(|e| e.to_string())?;
let name = config.get("otel.service_name").unwrap_or(service).to_string();
Ok(Some(
SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(opentelemetry_sdk::Resource::builder().with_service_name(name).build())
.build(),
))
}
pub(super) fn layer<S>(provider: &SdkTracerProvider) -> impl tracing_subscriber::Layer<S>
where
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
{
tracing_opentelemetry::layer().with_tracer(provider.tracer("zeyn"))
}
}