lite_log/logger.rs
1//! A logger that prints all messages with a simple, readable output format.
2//!
3//! Optional features include timestamps, colored output and logging to stderr.
4//!
5//! ```rust
6//! lite_log::LiteLogger::new().env().init().unwrap();
7//!
8//! log::warn!("This is an example message.");
9//! ```
10//!
11//! Some shortcuts are available for common use cases.
12//!
13//! Just initialize logging without any configuration:
14//!
15//! ```rust
16//! lite_log::init().unwrap();
17//! ```
18//!
19//! Set the log level from the `RUST_LOG` environment variable:
20//!
21//! ```rust
22//! lite_log::init_with_env().unwrap();
23//! ```
24//!
25//! Hardcode a default log level:
26//!
27//! ```rust
28//! lite_log::init_with_level(log::Level::Warn).unwrap();
29//! ```
30
31#![cfg_attr(feature = "nightly", feature(thread_id_value))]
32
33#[cfg(feature = "colored")]
34use colored::*;
35use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
36use std::{collections::HashMap, str::FromStr};
37
38#[cfg(feature = "timestamps")]
39use chrono::{Local, Utc,FixedOffset};
40
41#[cfg(feature = "timestamps")]
42const TIMESTAMP_FORMAT_OFFSET: &str = "%Y-%m-%d %H:%M:%S";
43
44#[cfg(feature = "timestamps")]
45const TIMESTAMP_FORMAT_UTC: &str = "%Y-%m-%dT%H:%M:%S%.3f %z";
46
47#[cfg(feature = "timestamps")]
48#[derive(PartialEq)]
49enum Timestamps {
50 None,
51 Local,
52 Utc,
53 UtcOffset(i32),
54}
55
56/// Implements [`Log`] and a set of simple builder methods for configuration.
57///
58/// Use the various "builder" methods on this struct to configure the logger,
59/// then call [`init`] to configure the [`log`] crate.
60pub struct Logger {
61 /// The default logging level
62 default_level: LevelFilter,
63
64 /// The specific logging level for each module
65 ///
66 /// This is used to override the default value for some specific modules.
67 /// After initialization, the vector is sorted so that the first (prefix) match
68 /// directly gives us the desired log level.
69 module_levels: Vec<(String, LevelFilter)>,
70
71 /// Whether to include thread names (and IDs) or not
72 ///
73 /// This field is only available if the `threads` feature is enabled.
74 #[cfg(feature = "threads")]
75 threads: bool,
76
77 /// Control how timestamps are displayed.
78 ///
79 /// This field is only available if the `timestamps` feature is enabled.
80 #[cfg(feature = "timestamps")]
81 timestamps: Timestamps,
82 #[cfg(feature = "timestamps")]
83 timestamps_format: Option<String>,
84
85 /// Whether to use color output or not.
86 ///
87 /// This field is only available if the `color` feature is enabled.
88 #[cfg(feature = "colored")]
89 colors: bool,
90}
91
92impl Logger {
93 /// Initializes the global logger with a Logger instance with
94 /// default log level set to `Level::Trace`.
95 ///
96 /// ```no_run
97 /// use lite_log::LiteLogger as Logger;
98 /// Logger::new().env().init().unwrap();
99 /// log::warn!("This is an example message.");
100 /// ```
101 ///
102 /// [`init`]: #method.init
103 #[must_use = "You must call init() to begin logging"]
104 pub fn new() -> Logger {
105 Logger {
106 default_level: LevelFilter::Trace,
107 module_levels: Vec::new(),
108
109 #[cfg(feature = "threads")]
110 threads: false,
111
112 #[cfg(feature = "timestamps")]
113 timestamps: Timestamps::Utc,
114
115 #[cfg(feature = "timestamps")]
116 timestamps_format: None,
117
118 #[cfg(feature = "colored")]
119 colors: true,
120 }
121 }
122
123 /// Simulates env_logger behavior, which enables the user to choose log level by
124 /// setting a `RUST_LOG` environment variable. The `RUST_LOG` is not set or its value is not
125 /// recognized as one of the log levels, this function will use the `Error` level by default.
126 ///
127 /// You may use the various builder-style methods on this type to configure
128 /// the logger, and you must call [`init`] in order to start logging messages.
129 ///
130 /// ```no_run
131 /// use lite_log::LiteLogger as Logger;
132 /// Logger::from_env().init().unwrap();
133 /// log::warn!("This is an example message.");
134 /// ```
135 ///
136 /// [`init`]: #method.init
137 #[must_use = "You must call init() to begin logging"]
138 #[deprecated(
139 since = "1.12.0",
140 note = "Use [`env`](#method.env) instead. Will be removed in version 2.0.0."
141 )]
142 pub fn from_env() -> Logger {
143 Logger::new().with_level(log::LevelFilter::Error).env()
144 }
145
146 /// Simulates env_logger behavior, which enables the user to choose log
147 /// level by setting a `RUST_LOG` environment variable. This will use
148 /// the default level set by [`with_level`] if `RUST_LOG` is not set or
149 /// can't be parsed as a standard log level.
150 ///
151 /// This must be called after [`with_level`]. If called before
152 /// [`with_level`], it will have no effect.
153 ///
154 /// [`with_level`]: #method.with_level
155 #[must_use = "You must call init() to begin logging"]
156 pub fn env(mut self) -> Logger {
157 self.default_level = std::env::var("RUST_LOG")
158 .ok()
159 .as_deref()
160 .map(log::LevelFilter::from_str)
161 .and_then(Result::ok)
162 .unwrap_or(self.default_level);
163
164 self
165 }
166
167 /// Set the 'default' log level.
168 ///
169 /// You can override the default level for specific modules and their sub-modules using [`with_module_level`]
170 ///
171 /// This must be called before [`env`]. If called after [`env`], it will override the value loaded from the environment.
172 ///
173 /// [`env`]: #method.env
174 /// [`with_module_level`]: #method.with_module_level
175 #[must_use = "You must call init() to begin logging"]
176 pub fn with_level(mut self, level: LevelFilter) -> Logger {
177 self.default_level = level;
178 self
179 }
180
181 /// Override the log level for some specific modules.
182 ///
183 /// This sets the log level of a specific module and all its sub-modules.
184 /// When both the level for a parent module as well as a child module are set,
185 /// the more specific value is taken. If the log level for the same module is
186 /// specified twice, the resulting log level is implementation defined.
187 ///
188 /// # Examples
189 ///
190 /// Silence an overly verbose crate:
191 ///
192 /// ```no_run
193 /// use lite_log::Logger;
194 /// use log::LevelFilter;
195 ///
196 /// Logger::new().with_module_level("chatty_dependency", LevelFilter::Warn).init().unwrap();
197 /// ```
198 ///
199 /// Disable logging for all dependencies:
200 ///
201 /// ```no_run
202 /// use lite_log::Logger;
203 /// use log::LevelFilter;
204 ///
205 /// Logger::new()
206 /// .with_level(LevelFilter::Off)
207 /// .with_module_level("my_crate", LevelFilter::Info)
208 /// .init()
209 /// .unwrap();
210 /// ```
211 #[must_use = "You must call init() to begin logging"]
212 pub fn with_module_level(mut self, target: &str, level: LevelFilter) -> Logger {
213 self.module_levels.push((target.to_string(), level));
214
215 /* Normally this is only called in `init` to avoid redundancy, but we can't initialize the logger in tests */
216 #[cfg(test)]
217 self.module_levels
218 .sort_by_key(|(name, _level)| name.len().wrapping_neg());
219
220 self
221 }
222
223 /// Override the log level for specific targets.
224 #[must_use = "You must call init() to begin logging"]
225 #[deprecated(
226 since = "1.11.0",
227 note = "Use [`with_module_level`](#method.with_module_level) instead. Will be removed in version 2.0.0."
228 )]
229 pub fn with_target_levels(mut self, target_levels: HashMap<String, LevelFilter>) -> Logger {
230 self.module_levels = target_levels.into_iter().collect();
231
232 /* Normally this is only called in `init` to avoid redundancy, but we can't initialize the logger in tests */
233 #[cfg(test)]
234 self.module_levels
235 .sort_by_key(|(name, _level)| name.len().wrapping_neg());
236
237 self
238 }
239
240 /// Control whether thread names (and IDs) are printed or not.
241 ///
242 /// This method is only available if the `threads` feature is enabled.
243 /// Thread names are disabled by default.
244 #[must_use = "You must call init() to begin logging"]
245 #[cfg(feature = "threads")]
246 pub fn with_threads(mut self, threads: bool) -> Logger {
247 self.threads = threads;
248 self
249 }
250
251 /// Control the format used for timestamps.
252 ///
253 /// Without this, a default format is used depending on the timestamps type.
254 ///
255 /// The syntax for the format can be found in the
256 /// [`chrono` crate book](https://docs.rs/chrono/latest/chrono/format/index.html).
257 ///
258 /// ```
259 /// lite_log::LiteLogger::new()
260 /// .with_level(log::LevelFilter::Debug)
261 /// .env()
262 /// .with_timestamp_format(&String::from("%Y-%m-%d %H:%M:%S"))
263 /// .init()
264 /// .unwrap();
265 /// ```
266 #[must_use = "You must call init() to begin logging"]
267 #[cfg(feature = "timestamps")]
268 pub fn with_timestamp_format(mut self, format:&String) -> Logger {
269 self.timestamps_format = Some(format.clone());
270 self
271 }
272
273 /// Don't display any timestamps.
274 ///
275 /// This method is only available if the `timestamps` feature is enabled.
276 #[must_use = "You must call init() to begin logging"]
277 #[cfg(feature = "timestamps")]
278 pub fn without_timestamps(mut self) -> Logger {
279 self.timestamps = Timestamps::None;
280 self
281 }
282
283 /// Display timestamps using the local timezone.
284 ///
285 /// This method is only available if the `timestamps` feature is enabled.
286 #[must_use = "You must call init() to begin logging"]
287 #[cfg(feature = "timestamps")]
288 pub fn with_local_timestamps(mut self) -> Logger {
289 self.timestamps = Timestamps::Local;
290 self
291 }
292
293 /// Display timestamps using UTC.
294 ///
295 /// This method is only available if the `timestamps` feature is enabled.
296 #[must_use = "You must call init() to begin logging"]
297 #[cfg(feature = "timestamps")]
298 pub fn with_utc_timestamps(mut self) -> Logger {
299 self.timestamps = Timestamps::Utc;
300 self
301 }
302
303 /// Display timestamps using a static UTC offset.
304 ///
305 /// This method is only available if the `timestamps` feature is enabled.
306 #[must_use = "You must call init() to begin logging"]
307 #[cfg(feature = "timestamps")]
308 pub fn with_utc_offset(mut self, offset: i32) -> Logger {
309 self.timestamps = Timestamps::UtcOffset(offset);
310 self
311 }
312
313 /// Control whether messages are colored or not.
314 ///
315 /// This method is only available if the `colored` feature is enabled.
316 #[must_use = "You must call init() to begin logging"]
317 #[cfg(feature = "colored")]
318 pub fn with_colors(mut self, colors: bool) -> Logger {
319 self.colors = colors;
320 self
321 }
322
323 /// 'Init' the actual logger, instantiate it and configure it,
324 /// this method MUST be called in order for the logger to be effective.
325 pub fn init(mut self) -> Result<(), SetLoggerError> {
326 #[cfg(all(windows, feature = "colored"))]
327 set_up_color_terminal();
328
329 /* Sort all module levels from most specific to least specific. The length of the module
330 * name is used instead of its actual depth to avoid module name parsing.
331 */
332 self.module_levels
333 .sort_by_key(|(name, _level)| name.len().wrapping_neg());
334 let max_level = self.module_levels.iter().map(|(_name, level)| level).copied().max();
335 let max_level = max_level
336 .map(|lvl| lvl.max(self.default_level))
337 .unwrap_or(self.default_level);
338 log::set_max_level(max_level);
339 log::set_boxed_logger(Box::new(self))?;
340 Ok(())
341 }
342}
343
344impl Default for Logger {
345 /// See [this](struct.Logger.html#method.new)
346 fn default() -> Self {
347 Logger::new()
348 }
349}
350
351impl Log for Logger {
352 fn enabled(&self, metadata: &Metadata) -> bool {
353 &metadata.level().to_level_filter()
354 <= self
355 .module_levels
356 .iter()
357 /* At this point the Vec is already sorted so that we can simply take
358 * the first match
359 */
360 .find(|(name, _level)| metadata.target().starts_with(name))
361 .map(|(_name, level)| level)
362 .unwrap_or(&self.default_level)
363 }
364
365 fn log(&self, record: &Record) {
366 if self.enabled(record.metadata()) {
367 let level_string = {
368 #[cfg(feature = "colored")]
369 {
370 if self.colors {
371 match record.level() {
372 Level::Error => format!("{:<5}", record.level().to_string()).red().to_string(),
373 Level::Warn => format!("{:<5}", record.level().to_string()).yellow().to_string(),
374 Level::Info => format!("{:<5}", record.level().to_string()).cyan().to_string(),
375 Level::Debug => format!("{:<5}", record.level().to_string()).purple().to_string(),
376 Level::Trace => format!("{:<5}", record.level().to_string()).normal().to_string(),
377 }
378 } else {
379 format!("{:<5}", record.level().to_string())
380 }
381 }
382 #[cfg(not(feature = "colored"))]
383 {
384 format!("{:<5}", record.level().to_string())
385 }
386 };
387
388 let target = if !record.target().is_empty() {
389 record.target()
390 } else {
391 record.module_path().unwrap_or_default()
392 };
393
394 let thread = {
395 #[cfg(feature = "threads")]
396 if self.threads {
397 let thread = std::thread::current();
398
399 format!("@{}", {
400 #[cfg(feature = "nightly")]
401 {
402 thread.name().unwrap_or(&thread.id().as_u64().to_string())
403 }
404
405 #[cfg(not(feature = "nightly"))]
406 {
407 thread.name().unwrap_or("?")
408 }
409 })
410 } else {
411 "".to_string()
412 }
413
414 #[cfg(not(feature = "threads"))]
415 ""
416 };
417
418 let timestamp = {
419 #[cfg(feature = "timestamps")]
420 match self.timestamps {
421 Timestamps::None => "".to_string(),
422 Timestamps::Local => format!( "{} ", Local::now().format(&self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_OFFSET)))),
423 Timestamps::Utc => format!("{} ", Utc::now().format(&self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_UTC)))),
424 Timestamps::UtcOffset(offset) => {
425 let offset = FixedOffset::east_opt(offset).unwrap();
426 let now_with_offset = Utc::now().with_timezone(&offset);
427 let fmt = self.timestamps_format.clone().unwrap_or(String::from(TIMESTAMP_FORMAT_OFFSET));
428 format!("{} ", now_with_offset.format(&fmt))
429 },
430 }
431
432 #[cfg(not(feature = "timestamps"))]
433 ""
434 };
435
436 let message = format!("{}{} [{}{}] {}", timestamp, level_string, target, thread, record.args());
437
438 #[cfg(not(feature = "stderr"))]
439 println!("{}", message);
440
441 #[cfg(feature = "stderr")]
442 eprintln!("{}", message);
443 }
444 }
445
446 fn flush(&self) {}
447}
448
449#[cfg(all(windows, feature = "colored"))]
450fn set_up_color_terminal() {
451 use std::io::{stdout, IsTerminal};
452
453 if stdout().is_terminal() {
454 unsafe {
455 use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
456 use windows_sys::Win32::System::Console::{
457 GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
458 STD_OUTPUT_HANDLE,
459 };
460
461 let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
462
463 if stdout == INVALID_HANDLE_VALUE {
464 return;
465 }
466
467 let mut mode: CONSOLE_MODE = 0;
468
469 if GetConsoleMode(stdout, &mut mode) == 0 {
470 return;
471 }
472
473 SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
474 }
475 }
476}
477
478/// Initialise the logger with its default configuration.
479///
480/// Log messages will not be filtered.
481/// The `RUST_LOG` environment variable is not used.
482pub fn init() -> Result<(), SetLoggerError> {
483 Logger::new().init()
484}
485
486/// Initialise the logger with its default configuration.
487///
488/// Log messages will not be filtered.
489/// The `RUST_LOG` environment variable is not used.
490///
491/// This function is only available if the `timestamps` feature is enabled.
492#[cfg(feature = "timestamps")]
493pub fn init_utc() -> Result<(), SetLoggerError> {
494 Logger::new().with_utc_timestamps().init()
495}
496
497/// Initialise the logger with the `RUST_LOG` environment variable.
498///
499/// Log messages will be filtered based on the `RUST_LOG` environment variable.
500pub fn init_with_env() -> Result<(), SetLoggerError> {
501 Logger::new().env().init()
502}
503
504/// Initialise the logger with a specific log level.
505///
506/// Log messages below the given [`Level`] will be filtered.
507/// The `RUST_LOG` environment variable is not used.
508pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
509 Logger::new().with_level(level.to_level_filter()).init()
510}
511
512/// Use [`init_with_env`] instead.
513///
514/// This does the same as [`init_with_env`] but unwraps the result.
515#[deprecated(
516 since = "1.12.0",
517 note = "Use [`init_with_env`] instead, which does not unwrap the result. Will be removed in version 2.0.0."
518)]
519pub fn init_by_env() {
520 init_with_env().unwrap()
521}