1#![deny(clippy::pedantic)]
2#![allow(clippy::missing_errors_doc)]
3#![allow(clippy::missing_panics_doc)]
4
5use std::fs::File;
18use std::{env, io};
19
20use tracing_subscriber::layer::SubscriberExt;
21use tracing_subscriber::util::SubscriberInitExt;
22use tracing_subscriber::{EnvFilter, Layer};
23
24pub const LOG_CONSENSUS: &str = "fm::consensus";
25pub const LOG_CORE: &str = "fm::core";
26pub const LOG_SERVER: &str = "fm::server";
27pub const LOG_DB: &str = "fm::db";
28pub const LOG_DEVIMINT: &str = "fm::devimint";
29pub const LOG_NET: &str = "fm::net";
30pub const LOG_NET_IROH: &str = "fm::net::iroh";
31pub const LOG_NET_WS: &str = "fm::net::ws";
32pub const LOG_NET_API: &str = "fm::net::api";
33pub const LOG_NET_PEER_DKG: &str = "fm::net::peer::dkg";
34pub const LOG_NET_PEER: &str = "fm::net::peer";
35pub const LOG_NET_AUTH: &str = "fm::net::auth";
36pub const LOG_TASK: &str = "fm::task";
37pub const LOG_RUNTIME: &str = "fm::runtime";
38pub const LOG_TEST: &str = "fm::test";
39pub const LOG_TIMING: &str = "fm::timing";
40pub const LOG_CLIENT: &str = "fm::client";
41pub const LOG_CLIENT_DB: &str = "fm::client::db";
42pub const LOG_CLIENT_EVENT_LOG: &str = "fm::client::event-log";
43pub const LOG_MODULE_MINT: &str = "fm::module::mint";
44pub const LOG_MODULE_META: &str = "fm::module::meta";
45pub const LOG_MODULE_WALLET: &str = "fm::module::wallet";
46pub const LOG_MODULE_WALLETV2: &str = "fm::module::walletv2";
47pub const LOG_MODULE_LN: &str = "fm::module::ln";
48pub const LOG_MODULE_LNV2: &str = "fm::module::lnv2";
49pub const LOG_CLIENT_REACTOR: &str = "fm::client::reactor";
50pub const LOG_CLIENT_NET: &str = "fm::client::net";
51pub const LOG_CLIENT_NET_API: &str = "fm::client::net::api";
52pub const LOG_CLIENT_BACKUP: &str = "fm::client::backup";
53pub const LOG_CLIENT_RECOVERY: &str = "fm::client::recovery";
54pub const LOG_CLIENT_RECOVERY_MINT: &str = "fm::client::recovery::mint";
55pub const LOG_CLIENT_MODULE_MINT: &str = "fm::client::module::mint";
56pub const LOG_CLIENT_MODULE_META: &str = "fm::client::module::meta";
57pub const LOG_CLIENT_MODULE_LN: &str = "fm::client::module::ln";
58pub const LOG_CLIENT_MODULE_LNV2: &str = "fm::client::module::lnv2";
59pub const LOG_CLIENT_MODULE_WALLET: &str = "fm::client::module::wallet";
60pub const LOG_CLIENT_MODULE_WALLETV2: &str = "fm::client::module::walletv2";
61pub const LOG_CLIENT_MODULE_GW: &str = "fm::client::module::gw";
62pub const LOG_CLIENT_MODULE_GWV2: &str = "fm::client::module::gwv2";
63pub const LOG_GATEWAY: &str = "fm::gw";
64pub const LOG_GATEWAY_UI: &str = "fm::gw::ui";
65pub const LOG_LIGHTNING: &str = "fm::gw::lightning";
66pub const LOG_LIGHTNING_LDK: &str = "fm::gw::lightning::ldk";
67pub const LOG_BITCOIND_ESPLORA: &str = "fm::bitcoind::esplora";
68pub const LOG_BITCOIND_CORE: &str = "fm::bitcoind::bitcoincore";
69pub const LOG_BITCOIND: &str = "fm::bitcoind";
70pub const LOG_BITCOIN: &str = "fm::bitcoin";
71
72#[cfg(feature = "telemetry")]
74static TRACER_PROVIDER: std::sync::OnceLock<opentelemetry_sdk::trace::SdkTracerProvider> =
75 std::sync::OnceLock::new();
76
77#[derive(Default)]
79pub struct TracingSetup {
80 base_level: Option<String>,
81 extra_directives: Option<String>,
82 #[cfg(feature = "telemetry")]
83 tokio_console_bind: Option<std::net::SocketAddr>,
84 #[cfg(feature = "telemetry")]
85 with_jaeger: bool,
86 with_file: Option<File>,
87}
88
89impl TracingSetup {
90 #[cfg(feature = "telemetry")]
92 pub fn tokio_console_bind(&mut self, address: Option<std::net::SocketAddr>) -> &mut Self {
93 self.tokio_console_bind = address;
94 self
95 }
96
97 #[cfg(feature = "telemetry")]
111 pub fn with_jaeger(&mut self, enabled: bool) -> &mut Self {
112 self.with_jaeger = enabled;
113 self
114 }
115
116 pub fn with_file(&mut self, file: Option<File>) -> &mut Self {
117 self.with_file = file;
118 self
119 }
120
121 pub fn with_base_level(&mut self, level: impl Into<String>) -> &mut Self {
125 self.base_level = Some(level.into());
126 self
127 }
128
129 pub fn with_directive(&mut self, directive: &str) -> &mut Self {
131 if let Some(old) = self.extra_directives.as_mut() {
132 *old = format!("{old},{directive}");
133 } else {
134 self.extra_directives = Some(directive.to_owned());
135 }
136 self
137 }
138
139 pub fn init(&mut self) -> anyhow::Result<()> {
141 use tracing_subscriber::fmt::writer::{BoxMakeWriter, Tee};
142
143 let var = env::var(tracing_subscriber::EnvFilter::DEFAULT_ENV).unwrap_or_default();
144 let filter_layer = EnvFilter::builder().parse(format!(
145 "{},{},{},{},{},{},{},{},{}",
149 self.base_level.as_deref().unwrap_or("info"),
150 "jsonrpsee_core::client::async_client=off",
151 "hyper=off",
152 "h2=off",
153 "jsonrpsee_server=warn,jsonrpsee_server::transport=off",
154 "AlephBFT-=error",
155 "iroh=error",
156 var,
157 self.extra_directives.as_deref().unwrap_or(""),
158 ))?;
159
160 let fmt_writer = match self.with_file.take() {
161 Some(file) => BoxMakeWriter::new(Tee::new(io::stderr, file)),
162 _ => BoxMakeWriter::new(io::stderr),
163 };
164
165 let fmt_layer = tracing_subscriber::fmt::layer()
166 .with_thread_names(false) .with_writer(fmt_writer)
168 .with_filter(filter_layer);
169
170 let console_opt = || -> Option<Box<dyn Layer<_> + Send + Sync + 'static>> {
171 #[cfg(feature = "telemetry")]
172 if let Some(l) = self.tokio_console_bind {
173 let tracer = console_subscriber::ConsoleLayer::builder()
174 .retention(std::time::Duration::from_mins(1))
175 .server_addr(l)
176 .spawn()
177 .with_filter(EnvFilter::new("tokio=trace,runtime=trace"));
179 return Some(tracer.boxed());
180 }
181 None
182 };
183
184 let telemetry_layer_opt = || -> Option<Box<dyn Layer<_> + Send + Sync + 'static>> {
185 #[cfg(feature = "telemetry")]
186 if self.with_jaeger {
187 use opentelemetry::trace::TracerProvider as _;
188
189 let exporter = match opentelemetry_otlp::SpanExporter::builder()
194 .with_tonic()
195 .build()
196 {
197 Ok(exporter) => exporter,
198 Err(e) => {
199 eprintln!(
200 "Failed to create OTLP span exporter, continuing without telemetry: {e}"
201 );
202 return None;
203 }
204 };
205
206 let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
208 .with_batch_exporter(exporter)
209 .with_resource(
210 opentelemetry_sdk::Resource::builder()
211 .with_service_name("fedimint")
212 .build(),
213 )
214 .build();
215
216 let _ = TRACER_PROVIDER.set(tracer_provider.clone());
218 opentelemetry::global::set_tracer_provider(tracer_provider.clone());
219
220 let tracer = tracer_provider.tracer("fedimint");
221
222 return Some(tracing_opentelemetry::layer().with_tracer(tracer).boxed());
223 }
224 None
225 };
226
227 tracing_subscriber::registry()
228 .with(fmt_layer)
229 .with(console_opt())
230 .with(telemetry_layer_opt())
231 .try_init()?;
232 Ok(())
233 }
234}
235
236pub fn shutdown() {
237 #[cfg(feature = "telemetry")]
238 if let Some(provider) = TRACER_PROVIDER.get()
239 && let Err(e) = provider.shutdown()
240 {
241 eprintln!("Error shutting down tracer provider: {e}");
242 }
243}