/ src / util / logger.rs
logger.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use std::{env, fmt, time::UNIX_EPOCH};
 20  
 21  use nu_ansi_term::{Color, Style};
 22  use tracing::{field::Field, Event, Level as TracingLevel, Metadata, Subscriber};
 23  use tracing_appender::non_blocking::NonBlocking;
 24  use tracing_subscriber::{
 25      fmt::{
 26          format, format::FmtSpan, time::FormatTime, FmtContext, FormatEvent, FormatFields,
 27          FormattedFields, Layer as FmtLayer,
 28      },
 29      layer::{Context, Filter, SubscriberExt},
 30      registry::LookupSpan,
 31      util::SubscriberInitExt,
 32      Layer, Registry,
 33  };
 34  
 35  use crate::{util::time::DateTime, Result};
 36  
 37  // Creates a `verbose` log level by wrapping an info! macro and
 38  // adding a `verbose` field.
 39  // This allows us to extract the field name in the event metadata and
 40  // identify the event as a `verbose` event log.
 41  // Currently, it only supports a subset of argument forms; additional arms
 42  // can be added as needed to handle more use cases.
 43  #[macro_export]
 44  macro_rules! verbose {
 45      (target: $target:expr, $($arg:tt)*) => {
 46          tracing::info!(target: $target, verbose=true, $($arg)*);
 47      };
 48      ($($arg:tt)*) => {
 49          tracing::info!(verbose=true, $($arg)*);
 50      };
 51  }
 52  
 53  pub use verbose;
 54  
 55  /// A custom log level type.
 56  ///
 57  /// This extends the standard [`tracing::Level`] by introducing
 58  /// additional log levels.
 59  ///
 60  /// This type is intended for use in custom filtering, formatting,
 61  /// and logging layers that need to distinguish custom logging levels.
 62  #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
 63  pub enum Level {
 64      Trace = 0,
 65      Debug = 1,
 66      Verbose = 2,
 67      Info = 3,
 68      Warn = 4,
 69      Error = 5,
 70  }
 71  
 72  impl Level {
 73      /// Creates a custom [`Level`] from a [`Metadata`] instance.
 74      ///
 75      /// This method inspects the metadata's fields for a custom level
 76      /// (e.g., a `verbose` field). If a custom level is present, it is returned.
 77      /// Otherwise, the standard `tracing::Level` is converted to this type.
 78      fn new(metadata: &Metadata) -> Self {
 79          if metadata.fields().field("verbose").is_some() {
 80              Level::Verbose
 81          } else {
 82              (*metadata.level()).into()
 83          }
 84      }
 85  }
 86  
 87  impl From<TracingLevel> for Level {
 88      fn from(value: TracingLevel) -> Self {
 89          match value {
 90              TracingLevel::TRACE => Level::Trace,
 91              TracingLevel::DEBUG => Level::Debug,
 92              TracingLevel::INFO => Level::Info,
 93              TracingLevel::WARN => Level::Warn,
 94              TracingLevel::ERROR => Level::Error,
 95          }
 96      }
 97  }
 98  
 99  impl fmt::Display for Level {
100      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101          match self {
102              Level::Trace => write!(f, "TRACE"),
103              Level::Debug => write!(f, "DEBUG"),
104              Level::Verbose => write!(f, "VERBOSE"),
105              Level::Info => write!(f, "INFO"),
106              Level::Warn => write!(f, "WARN"),
107              Level::Error => write!(f, "ERROR"),
108          }
109      }
110  }
111  
112  /// A formatter for the custom [`Level`]
113  ///
114  /// It implements [`fmt::Display`] and can produce a colored output
115  /// when writing to terminal and plain otherwise.
116  struct LevelFormatter<'a> {
117      level: &'a Level,
118      ansi: bool,
119  }
120  
121  impl<'a> LevelFormatter<'a> {
122      fn new(level: &'a Level, ansi: bool) -> Self {
123          Self { level, ansi }
124      }
125  }
126  
127  impl fmt::Display for LevelFormatter<'_> {
128      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129          if self.ansi {
130              match self.level {
131                  Level::Trace => write!(f, "{}", Color::Purple.paint(format!("[{}]", self.level))),
132                  Level::Debug => write!(f, "{}", Color::Blue.paint(format!("[{}]", self.level))),
133                  Level::Verbose => write!(f, "{}", Color::Cyan.paint(format!("[{}]", self.level))),
134                  Level::Info => write!(f, "{}", Color::Green.paint(format!("[{}]", self.level))),
135                  Level::Warn => write!(f, "{}", Color::Yellow.paint(format!("[{}]", self.level))),
136                  Level::Error => write!(f, "{}", Color::Red.paint(format!("[{}]", self.level))),
137              }
138          } else {
139              write!(f, "[{}]", self.level)
140          }
141      }
142  }
143  
144  /// Formats event timestamps as `HH:MM:SS` for `tracing` output.
145  pub struct EventTimeFormatter;
146  
147  impl FormatTime for EventTimeFormatter {
148      fn format_time(&self, w: &mut format::Writer<'_>) -> fmt::Result {
149          let now = DateTime::from_timestamp(UNIX_EPOCH.elapsed().unwrap().as_secs(), 0);
150          write!(w, "{:02}:{:02}:{:02}", now.hour, now.min, now.sec)
151      }
152  }
153  
154  /// Formats `tracing` events for output, with support for custom log levels.
155  ///
156  /// `EventFormatter` behaves like the default event formatter from
157  /// tracing-subscriber except for extracting custom levels from
158  /// event metadata fields and formatting them accordingly.
159  pub struct EventFormatter {
160      ansi: bool,
161      display_target: bool,
162      timer: EventTimeFormatter,
163  }
164  
165  impl EventFormatter {
166      pub fn new(ansi: bool, display_target: bool) -> Self {
167          Self { ansi, display_target, timer: EventTimeFormatter {} }
168      }
169  }
170  
171  impl<S, N> FormatEvent<S, N> for EventFormatter
172  where
173      S: Subscriber + for<'a> LookupSpan<'a>,
174      N: for<'a> FormatFields<'a> + 'static,
175  {
176      fn format_event(
177          &self,
178          ctx: &FmtContext<'_, S, N>,
179          mut writer: format::Writer<'_>,
180          event: &Event<'_>,
181      ) -> fmt::Result {
182          let meta = event.metadata();
183          // creates a custom level
184          let level = Level::new(meta);
185  
186          // format timestamp
187          if self.ansi {
188              let style = Style::new().dimmed();
189              write!(writer, "{}", style.prefix())?;
190              self.timer.format_time(&mut writer)?;
191              write!(writer, "{} ", style.suffix())?
192          } else {
193              self.timer.format_time(&mut writer)?;
194              write!(writer, " ")?;
195          }
196  
197          // format custom level
198          write!(writer, "{} ", LevelFormatter::new(&level, self.ansi))?;
199  
200          // format span
201          let dimmed = if self.ansi { Style::new().dimmed() } else { Style::new() };
202  
203          if let Some(scope) = ctx.event_scope() {
204              let bold = if self.ansi { Style::new().bold() } else { Style::new() };
205  
206              let mut seen = false;
207  
208              for span in scope.from_root() {
209                  write!(writer, "{}", bold.paint(span.metadata().name()))?;
210                  seen = true;
211  
212                  let ext = span.extensions();
213                  if let Some(fields) = &ext.get::<FormattedFields<N>>() {
214                      if !fields.is_empty() {
215                          write!(writer, "{}{}{}", bold.paint("{"), fields, bold.paint("}"))?;
216                      }
217                  }
218                  write!(writer, "{}", dimmed.paint(":"))?;
219              }
220  
221              if seen {
222                  writer.write_char(' ')?;
223              }
224          };
225  
226          // format target
227          if self.display_target {
228              write!(writer, "{}{} ", dimmed.paint(meta.target()), dimmed.paint(":"))?;
229          }
230  
231          // format event fields
232          ctx.format_fields(writer.by_ref(), event)?;
233          writeln!(writer)
234      }
235  }
236  
237  /// Formats event fields for terminal output, hiding level fields like `verbose`.
238  pub fn terminal_field_formatter(
239      writer: &mut format::Writer<'_>,
240      field: &Field,
241      value: &dyn fmt::Debug,
242  ) -> fmt::Result {
243      match field.name() {
244          // skip showing verbose field
245          "verbose" => Ok(()),
246          "message" => write!(writer, "{value:?}"),
247          name if name.starts_with("log.") => Ok(()),
248          name if name.starts_with("r#") => write!(
249              writer,
250              " {}{}{:?}",
251              Style::new().italic().paint(&name[2..]),
252              Style::new().dimmed().paint("="),
253              value
254          ),
255          name => write!(
256              writer,
257              " {}{}{:?}",
258              Style::new().italic().paint(name),
259              Style::new().dimmed().paint("="),
260              value
261          ),
262      }
263  }
264  
265  /// Formats event fields for file logging, hiding level fields like `verbose`.
266  pub fn file_field_formatter(
267      writer: &mut format::Writer<'_>,
268      field: &Field,
269      value: &dyn fmt::Debug,
270  ) -> fmt::Result {
271      match field.name() {
272          // skip showing verbose field
273          "verbose" => Ok(()),
274          name if name.starts_with("log.") => Ok(()),
275          name if name.starts_with("r#") => write!(writer, " {}={value:?}", &name[2..],),
276          "message" => write!(writer, "{value:?}"),
277          name => write!(writer, " {name}={value:?}"),
278      }
279  }
280  /// A `tracing-subscriber` layer that filters events based on their target.
281  pub struct TargetFilter {
282      /// Targets where logs should always be ignored.
283      ignored_targets: Vec<String>,
284      /// If non-empty, only these targets are allowed.
285      allowed_targets: Vec<String>,
286      /// Per-target minimum level that must be met for an event to be logged.
287      target_levels: Vec<(String, Level)>,
288      /// Default minimum level if no override is found
289      default_level: Level,
290  }
291  
292  impl TargetFilter {
293      /// Create a `TargetFilter` by parsing `allowed_targets` and
294      /// `ignored_targets` from a string in the format `target1,target2`
295      /// or `!target1,!target2`
296      pub fn parse_targets(mut self, log_targets: String) -> Self {
297          let targets: Vec<String> = log_targets.split(',').map(|s| s.to_string()).collect();
298  
299          for target in targets {
300              if target.starts_with('!') {
301                  self.ignored_targets.push(target.trim_start_matches('!').to_string());
302                  continue
303              }
304  
305              self.allowed_targets.push(target.to_string());
306          }
307  
308          self
309      }
310  
311      /// Overrides default level with verbosity level.
312      pub fn with_verbosity(mut self, verbosity_level: u8) -> Self {
313          let level = match verbosity_level {
314              0 => Level::Info,
315              1 => Level::Verbose,
316              2 => Level::Debug,
317              _ => Level::Trace,
318          };
319  
320          self.default_level = level;
321          self
322      }
323  
324      /// Adds multiple allowed targets.
325      pub fn allow_targets<I, S>(mut self, targets: I) -> Self
326      where
327          I: IntoIterator<Item = S>,
328          S: AsRef<str>,
329      {
330          self.allowed_targets.extend(targets.into_iter().map(|s| s.as_ref().to_string()));
331          self
332      }
333  
334      /// Adds multiple ignored targets.
335      pub fn ignore_targets<I, S>(mut self, targets: I) -> Self
336      where
337          I: IntoIterator<Item = S>,
338          S: AsRef<str>,
339      {
340          self.ignored_targets.extend(targets.into_iter().map(|s| s.as_ref().to_string()));
341          self
342      }
343  
344      /// Assign a log level filter for multiple targets.
345      pub fn targets_level<I, S>(mut self, targets: I, level: Level) -> Self
346      where
347          I: IntoIterator<Item = S>,
348          S: AsRef<str>,
349      {
350          self.target_levels.extend(targets.into_iter().map(|s| (s.as_ref().to_string(), level)));
351          self
352      }
353  
354      /// Override default level filter.
355      pub fn default_level(mut self, level: Level) -> Self {
356          self.default_level = level;
357          self
358      }
359  
360      /// Helper method to implement the filtering logic.
361      fn filter<S: Subscriber>(&self, metadata: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
362          let target = metadata.target();
363  
364          // Explicit ignore is given priority.
365          if self.ignored_targets.iter().any(|s| target.starts_with(s)) {
366              return false;
367          }
368  
369          // If allowed list exists, only allow those.
370          if !self.allowed_targets.is_empty() {
371              return self.allowed_targets.iter().any(|s| target.starts_with(s));
372          }
373  
374          let level = Level::new(metadata);
375          // check level filter overrides.
376          if let Some(min_level) = self
377              .target_levels
378              .iter()
379              .filter(|(s, _)| target.starts_with(s))
380              .max_by_key(|(s, _)| s.len()) // select the most specific target
381              .map(|(_, lvl)| *lvl)
382          {
383              return level >= min_level;
384          }
385  
386          // Otherwise default to level check.
387          level >= self.default_level
388      }
389  }
390  
391  impl Default for TargetFilter {
392      fn default() -> Self {
393          Self {
394              allowed_targets: Vec::new(),
395              ignored_targets: Vec::new(),
396              target_levels: Vec::new(),
397              default_level: Level::Info,
398          }
399      }
400  }
401  
402  /// Implement [`Filter`] to be able to filter inside a specific Layer.
403  impl<S: Subscriber> Filter<S> for TargetFilter {
404      fn enabled(&self, metadata: &Metadata<'_>, ctx: &Context<'_, S>) -> bool {
405          self.filter(metadata, ctx)
406      }
407  }
408  
409  /// Implement [`Filter`] to be able to add the filter as a Layer thus
410  /// filtering for multiple layers.
411  impl<S: Subscriber> Layer<S> for TargetFilter {
412      fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
413          self.filter(metadata, &ctx)
414      }
415  }
416  
417  /// Helper for setting up logging for bins.
418  pub fn setup_logging(verbosity_level: u8, log_file: Option<NonBlocking>) -> Result<()> {
419      let terminal_field_format = format::debug_fn(terminal_field_formatter);
420      let file_field_format = format::debug_fn(file_field_formatter);
421  
422      let terminal_layer = FmtLayer::new()
423          .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
424          .event_format(EventFormatter::new(true, verbosity_level != 0))
425          .fmt_fields(terminal_field_format)
426          .with_writer(std::io::stdout);
427  
428      let mut target_filter = TargetFilter::default().with_verbosity(verbosity_level);
429      if let Ok(log_targets) = env::var("LOG_TARGETS") {
430          target_filter = target_filter.parse_targets(log_targets);
431      }
432  
433      let file_layer = log_file.map(|log_file| {
434          FmtLayer::new()
435              .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
436              .event_format(EventFormatter::new(true, true))
437              .fmt_fields(file_field_format)
438              .with_writer(log_file)
439      });
440  
441      Ok(Registry::default().with(terminal_layer).with(file_layer).with(target_filter).try_init()?)
442  }
443  
444  /// Helper for setting up terminal logging for tests.
445  pub fn setup_test_logger(ignored_targets: &[&str], show_target: bool, level: Level) -> Result<()> {
446      let terminal_field_format = format::debug_fn(terminal_field_formatter);
447      let terminal_layer = FmtLayer::new()
448          .event_format(EventFormatter::new(true, show_target))
449          .fmt_fields(terminal_field_format)
450          .with_writer(std::io::stdout);
451  
452      let target_filter =
453          TargetFilter::default().ignore_targets(ignored_targets).default_level(level);
454  
455      Ok(Registry::default().with(terminal_layer).with(target_filter).try_init()?)
456  }