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"))
}
}