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
13static TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();
18
19#[derive(Clone, Copy)]
21pub enum OpenTelemetry {
22 Enabled,
23 Disabled,
24}
25
26impl OpenTelemetry {
27 fn is_enabled(self) -> bool {
28 matches!(self, OpenTelemetry::Enabled)
29 }
30}
31
32pub struct OtelGuard {
35 tracer_provider: SdkTracerProvider,
36}
37
38impl Drop for OtelGuard {
39 fn drop(&mut self) {
40 if let Err(err) = self.tracer_provider.shutdown() {
41 eprintln!("{err:?}");
42 }
43 }
44}
45
46pub fn setup_tracing(otel: OpenTelemetry) -> anyhow::Result<Option<OtelGuard>> {
59 if otel.is_enabled() {
60 opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
61 }
62
63 let tracer_provider = if otel.is_enabled() {
67 let provider = init_tracer_provider()?;
68
69 TRACER_PROVIDER
72 .set(provider.clone())
73 .expect("setup_tracing should only be called once");
74
75 Some(provider)
76 } else {
77 None
78 };
79 let otel_layer = tracer_provider.as_ref().map(|provider| {
80 OpenTelemetryLayer::new(provider.tracer("tracing-otel-subscriber")).boxed()
81 });
82
83 let subscriber = Registry::default()
84 .with(stdout_layer().with_filter(env_or_default_filter()))
85 .with(otel_layer.with_filter(env_or_default_filter()));
86 tracing::subscriber::set_global_default(subscriber).map_err(Into::<anyhow::Error>::into)?;
87
88 let default_hook = std::panic::take_hook();
91 std::panic::set_hook(Box::new(move |info| {
92 tracing::error!(panic = true, "{info}");
93
94 if let Some(provider) = TRACER_PROVIDER.get() {
97 if let Err(err) = provider.force_flush() {
98 eprintln!("Failed to flush traces on panic: {err:?}");
99 }
100 }
101
102 default_hook(info);
104 }));
105
106 Ok(tracer_provider.map(|tracer_provider| OtelGuard { tracer_provider }))
107}
108
109fn init_tracer_provider() -> anyhow::Result<SdkTracerProvider> {
110 let exporter = opentelemetry_otlp::SpanExporter::builder()
111 .with_tonic()
112 .with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
113 .build()?;
114
115 Ok(opentelemetry_sdk::trace::SdkTracerProvider::builder()
116 .with_batch_exporter(exporter)
117 .build())
118}
119
120#[cfg(feature = "testing")]
129pub fn setup_test_tracing() -> anyhow::Result<(
130 tokio::sync::mpsc::UnboundedReceiver<opentelemetry_sdk::trace::SpanData>,
131 tokio::sync::mpsc::UnboundedReceiver<()>,
132)> {
133 let (exporter, rx_export, rx_shutdown) =
134 opentelemetry_sdk::testing::trace::new_tokio_test_exporter();
135
136 let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
137 .with_batch_exporter(exporter)
138 .build();
139 let otel_layer =
140 OpenTelemetryLayer::new(tracer_provider.tracer("tracing-otel-subscriber")).boxed();
141 let subscriber = Registry::default()
142 .with(stdout_layer().with_filter(env_or_default_filter()))
143 .with(otel_layer.with_filter(env_or_default_filter()));
144 tracing::subscriber::set_global_default(subscriber)?;
145 Ok((rx_export, rx_shutdown))
146}
147
148#[cfg(not(feature = "tracing-forest"))]
149fn stdout_layer<S>() -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
150where
151 S: Subscriber,
152 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
153{
154 use tracing_subscriber::fmt::format::FmtSpan;
155
156 tracing_subscriber::fmt::layer()
157 .pretty()
158 .compact()
159 .with_level(true)
160 .with_file(true)
161 .with_line_number(true)
162 .with_target(true)
163 .with_span_events(FmtSpan::CLOSE)
164 .boxed()
165}
166
167#[cfg(feature = "tracing-forest")]
168fn stdout_layer<S>() -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
169where
170 S: Subscriber,
171 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
172{
173 tracing_forest::ForestLayer::default().boxed()
174}
175
176fn env_or_default_filter<S>() -> Box<dyn Filter<S> + Send + Sync + 'static> {
182 use tracing::level_filters::LevelFilter;
183 use tracing_subscriber::EnvFilter;
184 use tracing_subscriber::filter::{FilterExt, Targets};
185
186 match std::env::var(EnvFilter::DEFAULT_ENV) {
189 Ok(rust_log) => FilterExt::boxed(
190 EnvFilter::from_str(&rust_log)
191 .expect("RUST_LOG should contain a valid filter configuration"),
192 ),
193 Err(std::env::VarError::NotUnicode(_)) => panic!("RUST_LOG contained non-unicode"),
194 Err(std::env::VarError::NotPresent) => {
195 FilterExt::boxed(
197 Targets::new()
198 .with_default(LevelFilter::INFO)
199 .with_target("axum::rejection", LevelFilter::TRACE),
200 )
201 },
202 }
203}