miden_note_transport_node/
logging.rs1use std::str::FromStr;
2
3use anyhow::Result;
4use opentelemetry::trace::TracerProvider as _;
5use opentelemetry_otlp::WithExportConfig;
6use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
7use opentelemetry_sdk::propagation::TraceContextPropagator;
8use opentelemetry_sdk::trace::SpanExporter;
9use tracing::subscriber::Subscriber;
10use tracing_opentelemetry::OpenTelemetryLayer;
11use tracing_subscriber::layer::{Filter, SubscriberExt};
12use tracing_subscriber::{Layer, Registry};
13
14#[derive(Clone)]
16pub struct TracingConfig {
17 pub otel: OpenTelemetry,
19 pub json_format: bool,
21}
22
23#[derive(Clone)]
25pub enum OpenTelemetry {
26 Enabled {
28 endpoint: String,
30 },
31 Disabled,
33}
34
35impl TracingConfig {
36 pub fn from_env() -> Self {
38 let otel = {
39 let otel_enabled = std::env::var("OTEL_ENABLED")
40 .unwrap_or_else(|_| "false".to_string())
41 .parse()
42 .unwrap_or(false);
43 if otel_enabled {
44 OpenTelemetry::Enabled {
45 endpoint: std::env::var("OTEL_TRACES_ENDPOINT")
46 .ok()
47 .unwrap_or("http://localhost:4317".to_string()),
48 }
49 } else {
50 OpenTelemetry::Disabled
51 }
52 };
53
54 TracingConfig {
55 otel,
56 json_format: std::env::var("JSON_LOGGING")
57 .unwrap_or_else(|_| "false".to_string())
58 .parse()
59 .unwrap_or(false),
60 }
61 }
62}
63
64impl OpenTelemetry {
65 pub fn is_enabled(&self) -> bool {
67 matches!(self, OpenTelemetry::Enabled { .. })
68 }
69}
70
71pub fn setup_tracing(cfg: TracingConfig) -> Result<()> {
79 if cfg.otel.is_enabled() {
80 opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
81
82 setup_metrics_export(&cfg.otel)?;
84 }
85
86 let otel_layer = {
90 if let OpenTelemetry::Enabled { endpoint } = cfg.otel {
91 let exporter_builder =
92 opentelemetry_otlp::SpanExporter::builder().with_tonic().with_endpoint(endpoint);
93
94 match exporter_builder.build() {
95 Ok(exporter) => {
96 Some(open_telemetry_layer(exporter, "miden-note-transport-node".to_string()))
97 },
98 Err(_) => None,
99 }
100 } else {
101 None
102 }
103 };
104
105 let subscriber = Registry::default()
106 .with(stdout_layer(cfg.json_format).with_filter(env_or_default_filter()))
107 .with(otel_layer.with_filter(env_or_default_filter()));
108
109 tracing::subscriber::set_global_default(subscriber).map_err(Into::into)
110}
111
112fn setup_metrics_export(otel_cfg: &OpenTelemetry) -> Result<()> {
114 if let OpenTelemetry::Enabled { endpoint } = otel_cfg {
115 let exporter = opentelemetry_otlp::MetricExporter::builder()
117 .with_tonic()
118 .with_endpoint(endpoint)
119 .build()?;
120
121 let provider = SdkMeterProvider::builder()
122 .with_reader(
123 PeriodicReader::builder(exporter)
124 .with_interval(std::time::Duration::from_secs(5)) .build(),
126 )
127 .build();
128
129 opentelemetry::global::set_meter_provider(provider);
131 }
132
133 Ok(())
134}
135
136#[cfg(test)]
145pub fn setup_test_tracing() -> Result<(
146 tokio::sync::mpsc::UnboundedReceiver<opentelemetry_sdk::trace::SpanData>,
147 tokio::sync::mpsc::UnboundedReceiver<()>,
148)> {
149 let (exporter, rx_export, rx_shutdown) =
150 opentelemetry_sdk::testing::trace::new_tokio_test_exporter();
151
152 let otel_layer = open_telemetry_layer(exporter, "test-service".to_string());
153 let subscriber = Registry::default()
154 .with(stdout_layer(true).with_filter(env_or_default_filter()))
155 .with(otel_layer.with_filter(env_or_default_filter()));
156 tracing::subscriber::set_global_default(subscriber)?;
157 Ok((rx_export, rx_shutdown))
158}
159
160fn open_telemetry_layer<S>(
161 exporter: impl SpanExporter + 'static,
162 service_name: String,
163) -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
164where
165 S: Subscriber + Sync + Send,
166 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
167{
168 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
169 .with_batch_exporter(exporter)
170 .build();
171
172 let tracer = provider.tracer(service_name);
173
174 opentelemetry::global::set_tracer_provider(provider);
176
177 OpenTelemetryLayer::new(tracer).boxed()
178}
179
180fn stdout_layer<S>(
181 json_logging: bool,
182) -> Box<dyn tracing_subscriber::Layer<S> + Send + Sync + 'static>
183where
184 S: Subscriber,
185 for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
186{
187 use tracing_subscriber::fmt::format::FmtSpan;
188
189 if json_logging {
190 tracing_subscriber::fmt::layer()
191 .with_level(true)
192 .with_file(true)
193 .with_line_number(true)
194 .with_target(true)
195 .with_span_events(FmtSpan::CLOSE)
196 .json()
197 .boxed()
198 } else {
199 tracing_subscriber::fmt::layer()
200 .pretty()
201 .compact()
202 .with_level(true)
203 .with_file(true)
204 .with_line_number(true)
205 .with_target(true)
206 .with_span_events(FmtSpan::CLOSE)
207 .boxed()
208 }
209}
210
211fn env_or_default_filter<S>() -> Box<dyn Filter<S> + Send + Sync + 'static> {
217 use tracing::level_filters::LevelFilter;
218 use tracing_subscriber::EnvFilter;
219 use tracing_subscriber::filter::{FilterExt, Targets};
220
221 match std::env::var(EnvFilter::DEFAULT_ENV) {
224 Ok(rust_log) => FilterExt::boxed(
225 EnvFilter::from_str(&rust_log)
226 .expect("RUST_LOG should contain a valid filter configuration"),
227 ),
228 Err(std::env::VarError::NotUnicode(_)) => panic!("RUST_LOG contained non-unicode"),
229 Err(std::env::VarError::NotPresent) => {
230 FilterExt::boxed(
232 Targets::new()
233 .with_default(LevelFilter::INFO)
234 .with_target("axum::rejection", LevelFilter::TRACE),
235 )
236 },
237 }
238}