rivetkit_core/telemetry/
export.rs1use std::time::Duration;
11
12use anyhow::{Context, Result};
13use opentelemetry::KeyValue;
14use opentelemetry::trace::TracerProvider as _;
15use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig as _};
16use opentelemetry_sdk::Resource;
17use opentelemetry_sdk::trace::{SdkTracer, SdkTracerProvider};
18use parking_lot::Mutex;
19use tracing_subscriber::registry::LookupSpan;
20use tracing_subscriber::{EnvFilter, Layer};
21
22enum ProviderState {
23 Uninitialized,
24 Active(SdkTracerProvider),
25 ShutDown,
26}
27
28static PROVIDER: Mutex<ProviderState> = Mutex::new(ProviderState::Uninitialized);
29
30const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
32
33pub fn layer<S>() -> Result<Option<impl Layer<S>>>
37where
38 S: tracing::Subscriber + for<'a> LookupSpan<'a>,
39{
40 let Some(tracer) = initialize_if_configured()? else {
41 return Ok(None);
42 };
43 Ok(Some(
44 tracing_opentelemetry::layer()
45 .with_tracer(tracer)
46 .with_location(false)
47 .with_threads(false)
48 .with_tracked_inactivity(false)
49 .with_filter(EnvFilter::new("rivetkit::telemetry=info")),
50 ))
51}
52
53fn initialize_if_configured() -> Result<Option<SdkTracer>> {
56 if !export_is_configured() {
57 return Ok(None);
58 }
59 let mut stored_provider = PROVIDER.lock();
60 match &*stored_provider {
61 ProviderState::Active(provider) => return Ok(Some(provider.tracer("rivetkit"))),
62 ProviderState::ShutDown => return Ok(None),
63 ProviderState::Uninitialized => {}
64 }
65
66 let exporter = match configured_protocol()? {
67 Protocol::Grpc => SpanExporter::builder()
68 .with_tonic()
69 .build()
70 .context("build otlp span exporter")?,
71 protocol @ (Protocol::HttpBinary | Protocol::HttpJson) => SpanExporter::builder()
72 .with_http()
73 .with_protocol(protocol)
74 .build()
75 .context("build otlp span exporter")?,
76 };
77 let resource = Resource::builder()
78 .with_attribute(KeyValue::new("rivetkit.version", env!("CARGO_PKG_VERSION")))
79 .build();
80 let provider = SdkTracerProvider::builder()
81 .with_resource(resource)
82 .with_batch_exporter(exporter)
83 .build();
84 let tracer = provider.tracer("rivetkit");
85 *stored_provider = ProviderState::Active(provider);
86 Ok(Some(tracer))
87}
88
89fn configured_protocol() -> Result<Protocol> {
97 let configured = std::env::var("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")
98 .or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL"))
99 .unwrap_or_else(|_| "http/protobuf".to_owned());
100 match configured.as_str() {
101 "grpc" => Ok(Protocol::Grpc),
102 "http/protobuf" => Ok(Protocol::HttpBinary),
103 "http/json" => Ok(Protocol::HttpJson),
104 other => anyhow::bail!(
105 "native trace export supports grpc, http/protobuf and http/json, got {other:?}"
106 ),
107 }
108}
109
110fn export_is_configured() -> bool {
111 if std::env::var("OTEL_SDK_DISABLED").is_ok_and(|value| value.eq_ignore_ascii_case("true"))
112 || std::env::var("OTEL_TRACES_EXPORTER")
113 .is_ok_and(|value| value.eq_ignore_ascii_case("none"))
114 {
115 return false;
116 }
117
118 [
119 "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
120 "OTEL_EXPORTER_OTLP_ENDPOINT",
121 ]
122 .into_iter()
123 .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
124}
125
126pub async fn shutdown_best_effort() {
129 let provider = {
130 let mut state = PROVIDER.lock();
131 match std::mem::replace(&mut *state, ProviderState::ShutDown) {
132 ProviderState::Active(provider) => Some(provider),
133 ProviderState::Uninitialized | ProviderState::ShutDown => None,
134 }
135 };
136 let Some(provider) = provider else {
137 return;
138 };
139 let shutdown = tokio::task::spawn_blocking(move || provider.shutdown());
140 match tokio::time::timeout(SHUTDOWN_TIMEOUT, shutdown).await {
141 Ok(Ok(Ok(()))) => {}
142 Ok(Ok(Err(error))) => tracing::warn!(
143 ?error,
144 "OpenTelemetry trace provider shutdown failed; queued spans may be lost"
145 ),
146 Ok(Err(error)) => tracing::warn!(
147 ?error,
148 "OpenTelemetry trace provider shutdown task failed; queued spans may be lost"
149 ),
150 Err(_) => tracing::warn!(
151 "OpenTelemetry trace provider shutdown timed out; queued spans may be lost"
152 ),
153 }
154}