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