iroh_node_util/
logging.rs1use std::{env, path::Path};
3
4use derive_more::FromStr;
5use serde::{Deserialize, Serialize};
6use serde_with::{DeserializeFromStr, SerializeDisplay};
7use tracing_appender::{non_blocking, rolling};
8use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, Layer};
9
10pub(crate) const DEFAULT_FILE_RUST_LOG: &str = "rustyline=warn,debug";
13
14pub fn env_file_rust_log(bin: &'static str) -> Option<anyhow::Result<EnvFilter>> {
17 let env_file_rust_log = format!("{}_FILE_RUST_LOG", bin.to_uppercase());
18 match env::var(env_file_rust_log) {
19 Ok(s) => Some(crate::logging::EnvFilter::from_str(&s).map_err(Into::into)),
20 Err(e) => match e {
21 env::VarError::NotPresent => None,
22 e @ env::VarError::NotUnicode(_) => Some(Err(e.into())),
23 },
24 }
25}
26
27pub fn init_terminal_and_file_logging(
45 file_log_config: &FileLogging,
46 logs_root: &Path,
47) -> anyhow::Result<non_blocking::WorkerGuard> {
48 let terminal_layer = fmt::layer()
49 .with_writer(std::io::stderr)
50 .with_filter(tracing_subscriber::EnvFilter::from_default_env());
51 let (file_layer, guard) = {
52 let FileLogging {
53 rust_log,
54 max_files,
55 rotation,
56 dir,
57 } = file_log_config;
58
59 let filter = rust_log.layer();
60
61 let (file_logger, guard) = {
62 let file_appender = if *max_files == 0 || &filter.to_string() == "off" {
63 fmt::writer::OptionalWriter::none()
64 } else {
65 let rotation = match rotation {
66 Rotation::Hourly => rolling::Rotation::HOURLY,
67 Rotation::Daily => rolling::Rotation::DAILY,
68 Rotation::Never => rolling::Rotation::NEVER,
69 };
70
71 let logs_path = dir.clone().unwrap_or_else(|| logs_root.join("logs"));
73
74 let file_appender = rolling::Builder::new()
75 .rotation(rotation)
76 .max_log_files(*max_files)
77 .filename_prefix("iroh")
78 .filename_suffix("log")
79 .build(logs_path)?;
80 fmt::writer::OptionalWriter::some(file_appender)
81 };
82 non_blocking(file_appender)
83 };
84
85 let layer = fmt::Layer::new()
86 .with_ansi(false)
87 .with_line_number(true)
88 .with_writer(file_logger)
89 .with_filter(filter);
90 (layer, guard)
91 };
92 tracing_subscriber::registry()
93 .with(file_layer)
94 .with(terminal_layer)
95 .try_init()?;
96 Ok(guard)
97}
98
99pub fn init_terminal_logging() -> anyhow::Result<()> {
105 let terminal_layer = fmt::layer()
106 .with_writer(std::io::stderr)
107 .with_filter(tracing_subscriber::EnvFilter::from_default_env());
108 tracing_subscriber::registry()
109 .with(terminal_layer)
110 .try_init()?;
111 Ok(())
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
118#[serde(default, deny_unknown_fields)]
119pub struct FileLogging {
120 pub rust_log: EnvFilter,
122 pub max_files: usize,
124 pub rotation: Rotation,
126 pub dir: Option<std::path::PathBuf>,
128}
129
130impl Default for FileLogging {
131 fn default() -> Self {
132 Self {
133 rust_log: EnvFilter::default(),
134 max_files: 4,
135 rotation: Rotation::default(),
136 dir: None,
137 }
138 }
139}
140
141#[derive(
143 Debug, Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr, derive_more::Display,
144)]
145#[display("{_0}")]
146pub struct EnvFilter(String);
147
148impl FromStr for EnvFilter {
149 type Err = <tracing_subscriber::EnvFilter as FromStr>::Err;
150
151 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 let _valid_env = tracing_subscriber::EnvFilter::from_str(s)?;
154 Ok(EnvFilter(s.into()))
155 }
156}
157
158impl Default for EnvFilter {
159 fn default() -> Self {
160 Self(DEFAULT_FILE_RUST_LOG.into())
161 }
162}
163
164impl EnvFilter {
165 pub(crate) fn layer(&self) -> tracing_subscriber::EnvFilter {
166 tracing_subscriber::EnvFilter::from_str(&self.0).expect("validated RUST_LOG statement")
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
174#[serde(rename_all = "lowercase")]
175#[allow(missing_docs)]
176pub enum Rotation {
177 #[default]
178 Hourly,
179 Daily,
180 Never,
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
189 fn test_default_file_rust_log() {
190 let _ = EnvFilter::default().layer();
191 }
192}