1mod otlp;
11
12use std::sync::Arc;
13
14use decdn_common::{cli, config, identity};
15use opentelemetry_sdk::trace::SdkTracerProvider;
16use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields, MakeWriter, format};
17use tracing_subscriber::registry::LookupSpan;
18
19use crate::{metrics, runtime};
20
21pub async fn run(
23 config_path: Option<&std::path::Path>,
24 run_args: &cli::RunArgs,
25) -> anyhow::Result<()> {
26 let (resolved, notices) = config::resolve_config(config_path, run_args)?;
27
28 let (filter, rust_log_pinned) = startup_log_filter(&resolved);
29
30 let node_metrics = Arc::new(metrics::Metrics::new());
33
34 let tracer_provider = resolved
37 .observability
38 .otlp_endpoint
39 .as_deref()
40 .map(|endpoint| otlp::init_otlp_provider(endpoint, Arc::clone(&node_metrics)))
41 .transpose()?;
42
43 let node_key = identity::load_or_create(&resolved.identity.data_dir)?;
47 let node_id = node_key.key.public();
48
49 let log_level_setter = init_tracing(
50 filter,
51 rust_log_pinned,
52 resolved.observability.log_format,
53 &node_id,
54 tracer_provider.as_ref(),
55 );
56
57 runtime::emit_config_notices(¬ices);
62
63 tracing::info!("deCDN node starting");
64 log_node_key(&node_key, &resolved.identity.data_dir);
65 tracing::debug!(
66 data_dir = %resolved.identity.data_dir.display(),
67 bind_port = resolved.network.bind_port,
68 rpc_url = "<redacted>",
69 cache_dir = %resolved.cache.cache_dir.display(),
70 cache_size_mb = resolved.cache.cache_size_mb,
71 rate_per_mb = resolved.payment.rate_per_mb,
72 metrics_port = resolved.observability.metrics_port,
73 "resolved configuration"
74 );
75
76 let reload_state = Arc::new(runtime::RuntimeReloadState::new(
77 run_args.observability.clone(),
78 &resolved,
79 log_level_setter,
80 ));
81
82 let result = Box::pin(runtime::run(
86 resolved,
87 node_key.key,
88 config_path.map(std::path::Path::to_path_buf),
89 reload_state,
90 node_metrics,
91 ))
92 .await;
93
94 if let Some(provider) = tracer_provider {
97 otlp::finish_run(provider, &result).await;
98 }
99
100 result
101}
102
103fn log_node_key(node_key: &identity::LoadedKey, data_dir: &std::path::Path) {
107 if node_key.generated {
108 tracing::info!(
109 path = %identity::key_path(data_dir).display(),
110 "generated new node secret key"
111 );
112 }
113 tracing::info!(node_id = %node_key.key.public(), "loaded node identity");
114}
115
116#[expect(
122 clippy::print_stderr,
123 reason = "runs before the tracing subscriber is initialized"
124)]
125fn startup_log_filter(resolved: &config::ResolvedConfig) -> (tracing_subscriber::EnvFilter, bool) {
126 match tracing_subscriber::EnvFilter::try_from_default_env() {
127 Ok(f) => (f, true),
128 Err(e) => {
129 if std::env::var_os("RUST_LOG").is_some() {
131 eprintln!("warning: ignoring malformed RUST_LOG: {e}");
132 }
133 (
134 tracing_subscriber::EnvFilter::new(resolved.observability.log_level.to_string()),
135 false,
136 )
137 }
138 }
139}
140
141fn init_tracing(
153 filter: tracing_subscriber::EnvFilter,
154 rust_log_pinned: bool,
155 log_format: cli::LogFormat,
156 node_id: &iroh::PublicKey,
157 provider: Option<&SdkTracerProvider>,
158) -> runtime::LogLevelSetter {
159 use tracing_subscriber::prelude::*;
160
161 let fmt_layer = fmt_layer(log_format, node_id, std::io::stdout);
162
163 let (reload_filter, reload_handle) = tracing_subscriber::reload::Layer::new(filter);
166
167 subscriber(fmt_layer, reload_filter, provider).init();
168
169 Box::new(move |lvl| {
170 let Some(directive) = reload_directive(rust_log_pinned, lvl) else {
171 tracing::info!(
172 log_level = %lvl,
173 "RUST_LOG is set; the config file log_level is not applied"
174 );
175 return Ok(runtime::LogLevelApply::KeptRustLog);
176 };
177 let new_filter = tracing_subscriber::EnvFilter::try_new(directive)
178 .map_err(|e| anyhow::anyhow!("invalid log_level {lvl}: {e}"))?;
179 reload_handle
180 .modify(|f| *f = new_filter)
181 .map_err(|e| anyhow::anyhow!("failed to swap tracing filter: {e}"))?;
182 Ok(runtime::LogLevelApply::Installed)
183 })
184}
185
186fn fmt_layer<W>(
189 format: cli::LogFormat,
190 node_id: &iroh::PublicKey,
191 writer: W,
192) -> Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + Send + Sync>
193where
194 W: for<'w> MakeWriter<'w> + Send + Sync + 'static,
195{
196 use tracing_subscriber::prelude::*;
197
198 let layer = tracing_subscriber::fmt::layer().with_writer(writer);
199 match format {
200 cli::LogFormat::Json => layer
201 .json()
202 .map_event_format(|inner| NodeIdJson::new(inner, node_id))
203 .boxed(),
204 cli::LogFormat::Pretty => layer.boxed(),
205 }
206}
207
208struct NodeIdJson<F> {
218 inner: F,
219 prefix: String,
221}
222
223impl<F> NodeIdJson<F> {
224 fn new(inner: F, node_id: &iroh::PublicKey) -> Self {
225 let id = serde_json::Value::from(node_id.to_string());
226 Self {
227 inner,
228 prefix: format!("{{\"node_id\":{id},"),
229 }
230 }
231}
232
233impl<S, N, F> FormatEvent<S, N> for NodeIdJson<F>
234where
235 S: tracing::Subscriber + for<'a> LookupSpan<'a>,
236 N: for<'a> FormatFields<'a> + 'static,
237 F: FormatEvent<S, N>,
238{
239 fn format_event(
240 &self,
241 ctx: &FmtContext<'_, S, N>,
242 mut writer: format::Writer<'_>,
243 event: &tracing::Event<'_>,
244 ) -> std::fmt::Result {
245 let mut line = String::new();
246 self.inner
247 .format_event(ctx, format::Writer::new(&mut line), event)?;
248 let rest = line.strip_prefix('{').ok_or(std::fmt::Error)?;
252 writer.write_str(&self.prefix)?;
253 writer.write_str(rest)
254 }
255}
256
257type LogFilter =
259 tracing_subscriber::reload::Layer<tracing_subscriber::EnvFilter, tracing_subscriber::Registry>;
260
261fn subscriber(
268 fmt_layer: Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + Send + Sync>,
269 log_filter: LogFilter,
270 provider: Option<&SdkTracerProvider>,
271) -> impl tracing::Subscriber + Send + Sync + for<'span> tracing_subscriber::registry::LookupSpan<'span>
272{
273 use tracing_subscriber::prelude::*;
274
275 tracing_subscriber::registry()
276 .with(fmt_layer.with_filter(log_filter))
277 .with(provider.map(otlp::otel_layer))
278}
279
280fn reload_directive(rust_log_pinned: bool, lvl: cli::common::LogLevel) -> Option<String> {
289 (!rust_log_pinned).then(|| lvl.to_string())
290}
291
292#[cfg(test)]
293mod tests;