af_core/log/
logger.rs

1// Copyright © 2020 Alexandra Frydl
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7pub use af_core_macros::logger_init as init;
8
9use super::*;
10use crate::{channel, thread};
11use dashmap::DashMap;
12use log::{Level, LevelFilter, Log, Metadata, Record, RecordBuilder};
13use parking_lot::RwLock;
14use std::cell::RefCell;
15use std::sync::atomic::{self, AtomicUsize};
16
17/// A logger to register with the `log` crate.
18struct Logger {
19  dropped_messages: AtomicUsize,
20  max_level: RwLock<LevelFilter>,
21  max_level_of: DashMap<String, LevelFilter>,
22  output: (channel::Sender<String>, channel::Receiver<String>),
23}
24
25/// The shared logger instance.
26static LOGGER: Lazy<Logger> = Lazy::new(|| Logger {
27  dropped_messages: default(),
28  max_level: RwLock::new(LevelFilter::Warn),
29  max_level_of: default(),
30  output: channel::with_capacity(4096),
31});
32
33thread_local! {
34  /// A thread-local buffer for formatting messages.
35  static THREAD_BUFFER: RefCell<String> = default();
36}
37
38/// Initializes the logger.
39pub fn init() {
40  if log::set_logger(&*LOGGER).is_err() {
41    return;
42  }
43
44  log::set_max_level(LevelFilter::Trace);
45
46  set_level_of(
47    "af_core",
48    match cfg!(debug_assertions) {
49      true => Debug,
50      false => Info,
51    },
52  );
53
54  thread::start("af_runtime::logger", || thread::block_on(output_messages()));
55}
56
57/// Sets the level of the logger.
58///
59/// Records above this level are hidden. Use `None` to hide all records.
60pub fn set_level(level: impl Into<Option<Level>>) {
61  let level = level.into().map(|lv| lv.to_level_filter()).unwrap_or(LevelFilter::Off);
62  let mut max_level = LOGGER.max_level.write();
63
64  *max_level = level;
65}
66
67/// Sets the level of a target.
68///
69/// Records above this level are hidden. Use `None` to hide all records.
70pub fn set_level_of(name: impl Into<String>, level: impl Into<Option<Level>>) {
71  let level = level.into().map(|lv| lv.to_level_filter()).unwrap_or(LevelFilter::Off);
72  let name = name.into();
73
74  LOGGER.max_level_of.insert(name, level);
75}
76
77/// Writes each message received from the given channel to stderr.
78async fn output_messages() {
79  let mut buffer = String::with_capacity(128);
80  let logger = &*LOGGER;
81  let messages = &logger.output.1;
82  let mut stderr = console::Term::stderr();
83
84  while let Ok(message) = messages.recv().await {
85    // If one or more messages were dropped, write an error message about it.
86
87    let dropped_messages = logger.dropped_messages.swap(0, atomic::Ordering::Relaxed);
88
89    if dropped_messages > 0 {
90      write_message(
91        Time::now(),
92        &RecordBuilder::new()
93          .level(Level::Error)
94          .target(module_path!())
95          .args(format_args!(
96            "Too many messages. {} {} dropped.",
97            dropped_messages,
98            match dropped_messages {
99              1 => "message",
100              _ => "messages",
101            }
102          ))
103          .build(),
104        &mut buffer,
105      )
106      .unwrap();
107
108      writeln!(stderr, "{}", buffer).unwrap();
109
110      buffer.clear();
111    }
112
113    // Then write the message itself.
114
115    writeln!(stderr, "{}", message).unwrap();
116  }
117}
118
119/// Writes a record to the given string.
120fn write_message(time: Time, record: &Record, f: &mut String) -> fmt::Result {
121  use console::style;
122
123  // Write the timestamp in bright black.
124
125  write!(f, "{} ", style(time.format("%F %T%.3f")).black().bright())?;
126
127  // Write the log level with an appropriate color.
128
129  match record.level() {
130    Level::Trace => {
131      write!(f, "{} ", style("TRACE").black().bright())?;
132    }
133
134    Level::Debug => {
135      write!(f, "{} ", style("DEBUG").magenta())?;
136    }
137
138    Level::Info => {
139      write!(f, " {} ", style("INFO").blue())?;
140    }
141
142    Level::Warn => {
143      write!(f, " {} ", style("WARN").yellow())?;
144    }
145
146    Level::Error => {
147      write!(f, "{} ", style("ERROR").red())?;
148    }
149  }
150
151  // Write the source of the message.
152
153  if !record.target().is_empty() {
154    let mut name = style(fmt::surround("[", record.target(), "] "));
155
156    name = match record.level() {
157      Level::Trace => name.black().bright(),
158      _ => name.white(),
159    };
160
161    write!(f, "{}", name)?;
162  }
163
164  // Finally, write the message.
165
166  let message = style(record.args()).bright();
167
168  let styled = match record.level() {
169    Level::Trace => message.black(),
170    _ => message.white(),
171  };
172
173  write!(f, "{}", styled)
174}
175
176// Implement `Log` to send messages to the output task.
177
178impl Log for Logger {
179  fn enabled(&self, metadata: &Metadata) -> bool {
180    let mut target = Some(metadata.target());
181
182    while let Some(t) = target {
183      match self.max_level_of.get(t) {
184        Some(filter) => return metadata.level() <= *filter,
185
186        None => {
187          let mut split = t.rsplitn(2, "::");
188
189          split.next();
190
191          target = split.next();
192        }
193      }
194    }
195
196    metadata.level() <= *LOGGER.max_level.read()
197  }
198
199  fn log(&self, record: &Record) {
200    if !self.enabled(record.metadata()) {
201      return;
202    }
203
204    let time = Time::now();
205
206    let message = THREAD_BUFFER.with(|buffer| {
207      let mut buffer = buffer.borrow_mut();
208
209      write_message(time, record, &mut buffer).unwrap();
210
211      buffer.split_off(0)
212    });
213
214    // Send a message to the output task immediately or increment the dropped
215    // count.
216
217    if self.output.0.try_send(message).is_err() {
218      LOGGER.dropped_messages.fetch_add(1, atomic::Ordering::Relaxed);
219    }
220  }
221
222  fn flush(&self) {}
223}