miden_node_utils/
logging.rs1use std::str::FromStr;
2use std::sync::OnceLock;
3
4use opentelemetry::trace::TracerProvider as _;
5use opentelemetry_otlp::WithTonicConfig;
6use opentelemetry_sdk::propagation::TraceContextPropagator;
7use opentelemetry_sdk::trace::SdkTracerProvider;
8use tracing::subscriber::Subscriber;
9use tracing_opentelemetry::OpenTelemetryLayer;
10use tracing_subscriber::layer::{Filter, SubscriberExt};
11use tracing_subscriber::{Layer, Registry};
12
13use crate::tracing::OpenTelemetrySpanExt;
14
15static TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();
20
21#[derive(Clone, Copy)]
23pub enum OpenTelemetry {
24 Enabled,
25 Disabled,
26}
27
28impl OpenTelemetry {
29 fn is_enabled(self) -> bool {
30 matches!(self, OpenTelemetry::Enabled)
31 }
32}
33
34pub struct OtelGuard {
37 tracer_provider: SdkTracerProvider,
38}
39
40impl Drop for OtelGuard {
41 fn drop(&mut self) {
42 if let Err(err) = self.tracer_provider.shutdown() {
43 eprintln!("{err:?}");
44 }
45 }
46}
47
48pub fn setup_tracing(otel: OpenTelemetry) -> anyhow::Result<Option<OtelGuard>> {
61 if otel.is_enabled() {
62 opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
63 }
64
65 let tracer_provider = if otel.is_enabled() {
69 let provider = init_tracer_provider()?;
70
71 TRACER_PROVIDER
74 .set(provider.clone())
75 .expect("setup_tracing should only be called once");
76
77 Some(provider)
78 } else {
79 None
80 };
81 let otel_layer = tracer_provider.as_ref().map(|provider| {
82 OpenTelemetryLayer::new(provider.tracer("tracing-otel-subscriber")).boxed()
83 });
84
85 let subscriber = Registry::default()
86 .with(stdout_layer().with_filter(env_or_default_filter()))
87 .with(otel_layer.with_filter(env_or_default_filter()));
88 tracing::subscriber::set_global_default(subscriber).map_err(Into::<anyhow::Error>::into)?;
89
90 let default_hook = std::panic::take_hook();
93 std::panic::set_hook(Box::new(move |info| {
94 tracing::error!(panic = true, info = %info, "panic");
95
96 let info_str = info.to_string();
98 let wrapped = anyhow::Error::msg(info_str);
99 tracing::Span::current().set_error(wrapped.as_ref());
100
101 if let Some(provider) = TRACER_PROVIDER.get() {
104 if let Err(err) = provider.force_flush() {
105 eprintln!("Failed to flush traces on panic: {err:?}");
106 }
107 }
108
109 default_hook(info);
111 }));
112
113 Ok(tracer_provider.map(|tracer_provider| OtelGuard { tracer_provider }))
114}
115
116fn init_tracer_provider() -> anyhow::Result<SdkTracerProvider> {
117 let exporter = opentelemetry_otlp::SpanExporter::builder()
118 .with_tonic()
119 .with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
120 .build()?;
121
122 Ok(opentelemetry_sdk::trace::SdkTracerProvider::builder()
123 .with_batch_exporter(exporter)
124 .build())
125}
126
127#[cfg(feature = "testing")]
136pub fn setup_test_tracing() -> anyhow::Result<(
137 tokio::sync::mpsc::UnboundedReceiver<opentelemetry_sdk::trace::SpanData>,
138 tokio::sync::mpsc::UnboundedReceiver<()>,
139)> {
140 let (exporter, rx_export, rx_shutdown) =
141 opentelemetry_sdk::testing::trace::new_tokio_test_exporter();
142
143 let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
144 .with_batch_exporter(exporter)
145 .build();
146 let otel_layer =
147 OpenTelemetryLayer::new(tracer_provider.tracer("tracing-otel-subscriber")).boxed();
148 let subscriber = Registry::default()
149 .with(stdout_layer().with_filter(env_or_default_filter()))
150 .with(otel_layer.with_filter(env_or_default_filter()));
151 tracing::subscriber::set_global_default(subscriber)?;
152 Ok((rx_export, rx_shutdown))
153}
154
155#[cfg(not(feature = "tracing-forest"))]
156fn stdout_layer<S>() -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
157where
158 S: Subscriber,
159 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
160{
161 use tracing_subscriber::fmt::format::FmtSpan;
162
163 tracing_subscriber::fmt::layer()
164 .pretty()
165 .compact()
166 .with_level(true)
167 .with_file(true)
168 .with_line_number(true)
169 .with_target(true)
170 .with_span_events(FmtSpan::CLOSE)
171 .boxed()
172}
173
174#[cfg(feature = "tracing-forest")]
175fn stdout_layer<S>() -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
176where
177 S: Subscriber,
178 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
179{
180 tracing_forest::ForestLayer::default().boxed()
181}
182
183fn env_or_default_filter<S>() -> Box<dyn Filter<S> + Send + Sync + 'static> {
189 use tracing::level_filters::LevelFilter;
190 use tracing_subscriber::EnvFilter;
191 use tracing_subscriber::filter::{FilterExt, Targets};
192
193 match std::env::var(EnvFilter::DEFAULT_ENV) {
196 Ok(rust_log) => FilterExt::boxed(
197 EnvFilter::from_str(&rust_log)
198 .expect("RUST_LOG should contain a valid filter configuration"),
199 ),
200 Err(std::env::VarError::NotUnicode(_)) => panic!("RUST_LOG contained non-unicode"),
201 Err(std::env::VarError::NotPresent) => {
202 FilterExt::boxed(
204 Targets::new()
205 .with_default(LevelFilter::INFO)
206 .with_target("axum::rejection", LevelFilter::TRACE),
207 )
208 },
209 }
210}