1mod appender;
10mod error;
11mod layers;
12#[cfg(feature = "process-metrics")]
13pub mod metrics;
14
15use crate::error::Result;
16use layers::TracingLayers;
17use serde::{Deserialize, Serialize};
18use std::path::PathBuf;
19use tracing::info;
20use tracing_core::dispatcher::DefaultGuard;
21use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt};
22
23pub use error::Error;
24pub use layers::ReloadHandle;
25pub use tracing_appender::non_blocking::WorkerGuard;
26
27pub use tracing_core::Level;
29
30#[derive(Debug, Clone)]
31pub enum LogOutputDest {
32 Stderr,
33 Stdout,
34 Path(PathBuf),
35}
36
37impl LogOutputDest {
38 pub fn parse_from_str(val: &str) -> Result<Self> {
39 match val {
40 "stdout" => Ok(LogOutputDest::Stdout),
41 "data-dir" => {
42 let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
44
45 let dir = match dirs_next::data_dir() {
47 Some(dir) => dir
48 .join("autonomi")
49 .join("client")
50 .join("logs")
51 .join(format!("log_{timestamp}")),
52 None => {
53 return Err(Error::LoggingConfiguration(
54 "could not obtain data directory path".to_string(),
55 ))
56 }
57 };
58 Ok(LogOutputDest::Path(dir))
59 }
60 value => Ok(LogOutputDest::Path(PathBuf::from(value))),
63 }
64 }
65}
66
67impl std::fmt::Display for LogOutputDest {
68 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
69 match self {
70 LogOutputDest::Stderr => write!(f, "stderr"),
71 LogOutputDest::Stdout => write!(f, "stdout"),
72 LogOutputDest::Path(p) => write!(f, "{}", p.to_string_lossy()),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
78pub enum LogFormat {
79 Default,
80 Json,
81}
82
83impl LogFormat {
84 pub fn parse_from_str(val: &str) -> Result<Self> {
85 match val {
86 "default" => Ok(LogFormat::Default),
87 "json" => Ok(LogFormat::Json),
88 _ => Err(Error::LoggingConfiguration(
89 "The only valid values for this argument are \"default\" or \"json\"".to_string(),
90 )),
91 }
92 }
93
94 pub fn as_str(&self) -> &'static str {
95 match self {
96 LogFormat::Default => "default",
97 LogFormat::Json => "json",
98 }
99 }
100}
101
102pub struct LogBuilder {
103 default_logging_targets: Vec<(String, Level)>,
104 output_dest: LogOutputDest,
105 format: LogFormat,
106 max_log_files: Option<usize>,
107 max_archived_log_files: Option<usize>,
108 print_updates_to_stdout: bool,
110}
111
112impl LogBuilder {
113 pub fn new(default_logging_targets: Vec<(String, Level)>) -> Self {
118 Self {
119 default_logging_targets,
120 output_dest: LogOutputDest::Stderr,
121 format: LogFormat::Default,
122 max_log_files: None,
123 max_archived_log_files: None,
124 print_updates_to_stdout: true,
125 }
126 }
127
128 pub fn output_dest(&mut self, output_dest: LogOutputDest) {
130 self.output_dest = output_dest;
131 }
132
133 pub fn format(&mut self, format: LogFormat) {
135 self.format = format
136 }
137
138 pub fn max_log_files(&mut self, files: usize) {
140 self.max_log_files = Some(files);
141 }
142
143 pub fn max_archived_log_files(&mut self, files: usize) {
145 self.max_archived_log_files = Some(files);
146 }
147
148 pub fn print_updates_to_stdout(&mut self, print: bool) {
150 self.print_updates_to_stdout = print;
151 }
152
153 pub fn initialize(self) -> Result<(ReloadHandle, Option<WorkerGuard>)> {
158 let mut layers = TracingLayers::default();
159
160 let reload_handle = layers.fmt_layer(
161 self.default_logging_targets.clone(),
162 &self.output_dest,
163 self.format,
164 self.max_log_files,
165 self.max_archived_log_files,
166 self.print_updates_to_stdout,
167 )?;
168
169 #[cfg(feature = "otlp")]
170 {
171 match std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") {
172 Ok(_) => layers.otlp_layer(self.default_logging_targets)?,
173 Err(_) => println!(
174 "The OTLP feature is enabled but the OTEL_EXPORTER_OTLP_ENDPOINT variable is not \
175 set, so traces will not be submitted."
176 ),
177 }
178 }
179
180 if tracing_subscriber::registry()
181 .with(layers.layers)
182 .try_init()
183 .is_err()
184 {
185 eprintln!("Tried to initialize and set global default subscriber more than once");
186 }
187
188 Ok((reload_handle, layers.log_appender_guard))
189 }
190
191 pub fn init_single_threaded_tokio_test(
197 test_file_name: &str,
198 disable_networking_logs: bool,
199 ) -> (Option<WorkerGuard>, DefaultGuard) {
200 let layers = Self::get_test_layers(test_file_name, disable_networking_logs);
201 let log_guard = tracing_subscriber::registry()
202 .with(layers.layers)
203 .set_default();
204 if let Some(test_name) = std::thread::current().name() {
206 info!("Running test: {test_name}");
207 }
208 (layers.log_appender_guard, log_guard)
209 }
210
211 pub fn init_multi_threaded_tokio_test(
218 test_file_name: &str,
219 disable_networking_logs: bool,
220 ) -> Option<WorkerGuard> {
221 let layers = Self::get_test_layers(test_file_name, disable_networking_logs);
222 tracing_subscriber::registry()
223 .with(layers.layers)
224 .try_init()
225 .expect("You have tried to init multi_threaded tokio logging twice\nRefer ant_logging::get_test_layers docs for more.");
226
227 layers.log_appender_guard
228 }
229
230 fn get_test_layers(test_file_name: &str, disable_networking_logs: bool) -> TracingLayers {
234 if disable_networking_logs {
236 std::env::set_var(
237 "ANT_LOG",
238 format!("{test_file_name}=TRACE,all,ant_networking=WARN,all"),
239 );
240 } else {
241 std::env::set_var("ANT_LOG", format!("{test_file_name}=TRACE,all"));
242 }
243
244 let output_dest = match dirs_next::data_dir() {
245 Some(dir) => {
246 let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
248 let path = dir
249 .join("autonomi")
250 .join("client")
251 .join("logs")
252 .join(format!("log_{timestamp}"));
253 LogOutputDest::Path(path)
254 }
255 None => LogOutputDest::Stdout,
256 };
257
258 println!("Logging test at {test_file_name:?} to {output_dest:?}");
259
260 let mut layers = TracingLayers::default();
261
262 let _reload_handle = layers
263 .fmt_layer(vec![], &output_dest, LogFormat::Default, None, None, false)
264 .expect("Failed to get TracingLayers");
265 layers
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use crate::{layers::LogFormatter, ReloadHandle};
272 use color_eyre::Result;
273 use tracing::{trace, warn, Level};
274 use tracing_subscriber::{
275 filter::Targets,
276 fmt as tracing_fmt,
277 layer::{Filter, SubscriberExt},
278 reload,
279 util::SubscriberInitExt,
280 Layer, Registry,
281 };
282 use tracing_test::internal::global_buf;
283
284 #[test]
285 fn reload_handle_should_change_log_levels() -> Result<()> {
288 let mock_writer = tracing_test::internal::MockWriter::new(global_buf());
290
291 let layer = tracing_fmt::layer()
293 .with_ansi(false)
294 .with_target(false)
295 .event_format(LogFormatter)
296 .with_writer(mock_writer)
297 .boxed();
298
299 let test_target = "ant_logging::tests".to_string();
300 let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
302 Box::new(Targets::new().with_targets(vec![(test_target.clone(), Level::TRACE)]));
303
304 let (filter, handle) = reload::Layer::new(target_filters);
306 let reload_handle = ReloadHandle(handle);
307 let layer = layer.with_filter(filter);
308 tracing_subscriber::registry().with(layer).try_init()?;
309
310 let _span = tracing::info_span!("info span");
312
313 trace!("First trace event");
314
315 {
316 let buf = global_buf().lock().unwrap();
317
318 let events: Vec<&str> = std::str::from_utf8(&buf)
319 .expect("Logs contain invalid UTF8")
320 .lines()
321 .collect();
322 assert_eq!(events.len(), 1);
323 assert!(events[0].contains("First trace event"));
324 }
325
326 reload_handle.modify_log_level("ant_logging::tests=WARN")?;
327
328 trace!("Second trace event");
330 warn!("First warn event");
331
332 {
333 let buf = global_buf().lock().unwrap();
334
335 let events: Vec<&str> = std::str::from_utf8(&buf)
336 .expect("Logs contain invalid UTF8")
337 .lines()
338 .collect();
339
340 assert_eq!(events.len(), 2);
341 assert!(events[0].contains("First trace event"));
342 assert!(events[1].contains("First warn event"));
343 }
344
345 Ok(())
346 }
347}