108 lines
3.9 KiB
Rust
108 lines
3.9 KiB
Rust
//! Tracing init with optional OTLP export.
|
|
//!
|
|
//! Both bin targets (`worker`, `hrrr_point_worker`) call `init(svc)` at
|
|
//! startup. The local fmt layer (JSON to stdout) is always on so kubectl
|
|
//! logs keeps working. When `OTEL_EXPORTER_OTLP_ENDPOINT` is set we
|
|
//! additionally install a `tracing-opentelemetry` layer that ships
|
|
//! spans over OTLP/gRPC to the cluster's OTel Collector — see
|
|
//! `k8s/deployment-grid-rs.yaml` + `deployment-hrrr-point-rs.yaml`
|
|
//! for the env wiring.
|
|
//!
|
|
//! Returning the `TracerProvider` from `init` is deliberate: the caller
|
|
//! must hold it until shutdown and call `.shutdown()` so queued spans
|
|
//! flush before the process exits. Dropping it mid-run cancels the
|
|
//! background exporter and loses recent spans.
|
|
//!
|
|
//! Service identity is set via OpenTelemetry resource attributes
|
|
//! (`service.name`, `service.namespace`) so Tempo groups spans per
|
|
//! binary. `service.namespace=prop` matches the k8s namespace.
|
|
|
|
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
|
|
use opentelemetry_otlp::WithExportConfig;
|
|
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
|
|
|
/// Opaque handle the caller must hold until process shutdown; dropping
|
|
/// it stops the OTLP exporter. Returns `None` when OTLP is disabled
|
|
/// (no endpoint env var) so callers don't need to care which mode is
|
|
/// active.
|
|
pub struct TelemetryGuard {
|
|
provider: Option<SdkTracerProvider>,
|
|
}
|
|
|
|
impl TelemetryGuard {
|
|
/// Flush queued spans and shut down the exporter. Safe to call
|
|
/// multiple times; subsequent calls are no-ops.
|
|
pub fn shutdown(mut self) {
|
|
if let Some(provider) = self.provider.take() {
|
|
let _ = provider.shutdown();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for TelemetryGuard {
|
|
fn drop(&mut self) {
|
|
if let Some(provider) = self.provider.take() {
|
|
let _ = provider.shutdown();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initialize tracing for a binary. `service_name` becomes the
|
|
/// `service.name` attribute in exported spans and the log target
|
|
/// prefix.
|
|
pub fn init(service_name: &'static str) -> TelemetryGuard {
|
|
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
|
|
|
let fmt_layer = tracing_subscriber::fmt::layer().json();
|
|
|
|
// If the collector endpoint isn't set, skip the OTLP layer entirely
|
|
// — useful for local cargo runs where there's no collector to ship
|
|
// to. The fmt layer alone keeps dev parity with prod log output.
|
|
let otlp_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok();
|
|
|
|
match otlp_endpoint {
|
|
Some(endpoint) if !endpoint.is_empty() => {
|
|
let exporter = opentelemetry_otlp::SpanExporter::builder()
|
|
.with_tonic()
|
|
.with_endpoint(endpoint)
|
|
.build()
|
|
.expect("build OTLP span exporter");
|
|
|
|
let resource = Resource::builder()
|
|
.with_attributes([
|
|
KeyValue::new("service.name", service_name),
|
|
KeyValue::new("service.namespace", "prop"),
|
|
])
|
|
.build();
|
|
|
|
let provider = SdkTracerProvider::builder()
|
|
.with_batch_exporter(exporter)
|
|
.with_resource(resource)
|
|
.build();
|
|
|
|
let tracer = provider.tracer(service_name);
|
|
global::set_tracer_provider(provider.clone());
|
|
|
|
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
|
|
|
|
tracing_subscriber::registry()
|
|
.with(env_filter)
|
|
.with(fmt_layer)
|
|
.with(otel_layer)
|
|
.init();
|
|
|
|
TelemetryGuard {
|
|
provider: Some(provider),
|
|
}
|
|
}
|
|
_ => {
|
|
tracing_subscriber::registry()
|
|
.with(env_filter)
|
|
.with(fmt_layer)
|
|
.init();
|
|
|
|
TelemetryGuard { provider: None }
|
|
}
|
|
}
|
|
}
|