sensible_env_logger/lib.rs
1#![doc(html_root_url = "https://docs.rs/sensible-env-logger/0.3.2")]
2#![warn(rust_2018_idioms, missing_docs)]
3#![deny(warnings, dead_code, unused_imports, unused_mut)]
4#![allow(clippy::uninlined_format_args)]
5//! [![github]](https://github.com/rnag/sensible-env-logger) [![crates-io]](https://crates.io/crates/sensible-env-logger) [![docs-rs]](https://docs.rs/sensible-env-logger)
6//!
7//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
8//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
9//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
10//!
11//! <br>
12//!
13//! A simple logger, optionally configured via environment variables which
14//! writes to standard error with nice colored output for log levels.
15//! It sets up logging with "sensible" defaults that make it ideal for
16//! running *[examples]* and *[tests]* on a crate of choice.
17//!
18//! [examples]: http://xion.io/post/code/rust-examples.html
19//! [tests]: https://doc.rust-lang.org/book/ch11-01-writing-tests.html
20//! <br>
21//!
22//! ## Usage
23//!
24//! Even though it has `env` in the name, the `sensible-env-logger`
25//! requires minimal configuration and setup to use:
26//!
27//! ```
28//! #[macro_use] extern crate log;
29//!
30//! fn main() {
31//! sensible_env_logger::init!();
32//!
33//! trace!("a trace example");
34//! debug!("deboogging");
35//! info!("such information");
36//! warn!("o_O");
37//! error!("boom");
38//! }
39//! ```
40//!
41//! Run the program and you should see all the log output for your crate.
42//!
43//! Alternatively, run the program with the environment variables that control
44//! the log level for *your* crate as well as *external* crates explicitly set,
45//! like `RUST_LOG=debug` and `GLOBAL_RUST_LOG=error`.
46//!
47//! ## Defaults
48//!
49//! The defaults can be setup by calling `init!()` or `try_init!()` at the start
50//! of the program.
51//!
52//! ## Examples
53//!
54//! You can check out sample usage of this crate in the [examples/](https://github.com/rnag/sensible-env-logger/tree/main/examples)
55//! folder in the project repo on GitHub.
56//!
57//! ## Readme Docs
58//!
59//! You can find the crate's readme documentation on the
60//! [crates.io] page, or alternatively in the [`README.md`] file on the GitHub project repo.
61//!
62//! [crates.io]: https://crates.io/crates/sensible-env-logger
63//! [`README.md`]: https://github.com/rnag/sensible-env-logger
64//!
65//! ## Enable logging
66//!
67//! This crate uses [pretty_env_logger] and [env_logger] internally, so the
68//! same ways of enabling logs through an environment variable are supported.
69//!
70//! The `sensible_env_logger` crate re-exports these crates, through the
71//! `pretty` and `env` namespaces respectively.
72//!
73//! [pretty_env_logger]: https://docs.rs/pretty_env_logger
74//! [env_logger]: https://docs.rs/env_logger
75
76use std::borrow::Cow;
77
78use env::Builder;
79use log::{SetLoggerError, trace};
80#[doc(hidden)]
81pub use pretty_env_logger as pretty;
82#[doc(hidden)]
83pub use pretty_env_logger::env_logger as env;
84
85#[cfg(feature = "local-time")]
86pub use local_time::*;
87
88/// Default log level for the Cargo crate or package under test.
89pub(crate) const CRATE_LOG_LEVEL: &str = "trace";
90
91/// Default log level for external crates, other than the one under test.
92pub(crate) const GLOBAL_LOG_LEVEL: &str = "warn";
93
94/// Initializes the global logger with a pretty, sensible env logger.
95///
96/// This should be called early in the execution of a Rust program, and the
97/// global logger may only be initialized once. Future initialization attempts
98/// will return an error.
99///
100/// # Panics
101///
102/// This macro fails to set the global logger if one has already been set.
103#[macro_export]
104macro_rules! init {
105 () => {
106 $crate::try_init!().unwrap()
107 };
108}
109
110/// Initializes the global logger with a timed pretty, sensible env logger.
111///
112/// This should be called early in the execution of a Rust program, and the
113/// global logger may only be initialized once. Future initialization attempts
114/// will return an error.
115///
116/// # Panics
117///
118/// This macro fails to set the global logger if one has already been set.
119#[macro_export]
120macro_rules! init_timed {
121 () => {
122 $crate::try_init_timed!().unwrap();
123 };
124}
125
126/// Initializes the global logger with a pretty, sensible env logger.
127///
128/// This variant should ideally only be used in **tests**. It should be called
129/// early in the execution of a Rust program.
130///
131/// Future initialization attempts will *safely ignore* any errors.
132#[macro_export]
133macro_rules! safe_init {
134 () => {
135 let _ = $crate::try_init!();
136 };
137}
138
139/// Initializes the global logger with a timed pretty, sensible env logger.
140///
141/// This variant should ideally only be used in **tests**. It should be called
142/// early in the execution of a Rust program.
143///
144/// Future initialization attempts will *safely ignore* any errors.
145#[macro_export]
146macro_rules! safe_init_timed {
147 () => {
148 let _ = $crate::try_init_timed!();
149 };
150}
151
152/// Initializes the global logger with a pretty, sensible env logger.
153///
154/// This should be called early in the execution of a Rust program, and the
155/// global logger may only be initialized once. Future initialization attempts
156/// will return an error.
157///
158/// # Errors
159///
160/// This macro fails to set the global logger if one has already been set.
161#[macro_export]
162macro_rules! try_init {
163 () => {
164 $crate::try_init_custom_env_and_builder(
165 "RUST_LOG",
166 "GLOBAL_RUST_LOG",
167 env!("CARGO_PKG_NAME"),
168 module_path!(),
169 $crate::pretty::formatted_builder,
170 )
171 };
172}
173
174/// Initializes the global logger with a timed pretty, sensible env logger.
175///
176/// This should be called early in the execution of a Rust program, and the
177/// global logger may only be initialized once. Future initialization attempts
178/// will return an error.
179///
180/// # Errors
181///
182/// This macro fails to set the global logger if one has already been set.
183#[macro_export]
184macro_rules! try_init_timed {
185 () => {
186 $crate::try_init_custom_env_and_builder(
187 "RUST_LOG",
188 "GLOBAL_RUST_LOG",
189 env!("CARGO_PKG_NAME"),
190 module_path!(),
191 $crate::pretty::formatted_timed_builder,
192 )
193 };
194}
195
196/// Initializes the global logger with a pretty, sensible env logger, with custom
197/// variable names and a custom builder function.
198///
199/// This should be called early in the execution of a Rust program, and the
200/// global logger may only be initialized once. Future initialization attempts
201/// will return an error.
202///
203/// # Example
204/// ```rust
205/// let _ = sensible_env_logger::try_init_custom_env_and_builder(
206/// "MY_RUST_LOG",
207/// "MY_GLOBAL_RUST_LOG",
208/// env!("CARGO_PKG_NAME"),
209/// module_path!(),
210/// sensible_env_logger::pretty::formatted_timed_builder,
211/// );
212/// ```
213///
214/// # How It works
215///
216/// The `package_name` and `module_name` arguments are ideally evaluated from
217/// the `$CARGO_PKG_NAME` and `$CARGO_CRATE_NAME` environment variables
218/// respectively. These environment variables are automatically set
219/// by Cargo when compiling your crate. It then builds a custom directives
220/// string in the same form as the `$RUST_LOG` environment variable, and then
221/// parses this generated directives string using
222/// `env_logger::Builder::parse_filters`.
223///
224/// # Errors
225///
226/// This function fails to set the global logger if one has already been set.
227pub fn try_init_custom_env_and_builder(
228 log_env_var: &str,
229 global_log_env_var: &str,
230 package_name: &str,
231 module_name: &str,
232 builder_fn: impl Fn() -> Builder,
233) -> Result<(), SetLoggerError> {
234 let package_name = package_name.replace('-', "_");
235 let module_name = base_module(module_name);
236
237 let log_level = get_env(log_env_var, CRATE_LOG_LEVEL);
238 let global_log_level = get_env(global_log_env_var, GLOBAL_LOG_LEVEL);
239
240 let filters_str = if log_level.contains('=') {
241 // The env variable `$RUST_LOG` is set to a more complex value such as
242 // `warn,my_module=info`. In that case, just pass through the value.
243 log_level.into_owned()
244 } else if package_name != module_name {
245 format!(
246 "{default_lvl},{pkg}={lvl},{mod}={lvl}",
247 default_lvl = global_log_level,
248 pkg = package_name,
249 mod = module_name,
250 lvl = log_level
251 )
252 } else {
253 format!(
254 "{default_lvl},{pkg}={lvl}",
255 default_lvl = global_log_level,
256 pkg = package_name,
257 lvl = log_level
258 )
259 };
260
261 let mut builder: Builder = builder_fn();
262 builder.parse_filters(&filters_str);
263
264 let result = builder.try_init();
265
266 trace!("Filter: {}", filters_str);
267
268 result
269}
270
271/// Retrieve the value of an environment variable.
272pub(crate) fn get_env<'a>(env_var_name: &'a str, default: &'a str) -> Cow<'a, str> {
273 match std::env::var(env_var_name) {
274 Ok(value) => Cow::Owned(value),
275 _ => Cow::Borrowed(default),
276 }
277}
278
279/// Returns the base module name, given the path to a module.
280///
281/// # Example
282/// ```no_test
283/// assert_eq!(base_module("my_bin::my_module::tests"), "my_bin");
284/// ```
285///
286pub(crate) fn base_module(module_name: &str) -> &str {
287 match module_name.split_once("::") {
288 Some((first, _)) => first,
289 None => module_name,
290 }
291}
292
293#[cfg(feature = "local-time")]
294mod local_time {
295 use std::fmt;
296 use std::sync::atomic::{AtomicUsize, Ordering};
297
298 use chrono::Local;
299 use env::fmt::{Color, Style, StyledValue};
300 use log::Level;
301
302 use super::*;
303
304 /// Local time zone format (only time)
305 ///
306 /// # Example
307 /// `10:17:52.831`
308 ///
309 pub const TIME_ONLY_FMT: &str = "%l:%M:%S.%3f";
310
311 /// Local time zone format
312 ///
313 /// # Example
314 /// `2022-07-27 10:17:52.831 -`
315 ///
316 pub const LOCAL_TIME_FMT: &str = "%Y-%m-%d %l:%M:%S.%3f -";
317
318 /// ISO 8601 / RFC 3339 date & time format.
319 ///
320 /// # Example
321 /// `2022-07-27T17:34:44.531+08:00`
322 ///
323 pub const ISO_FMT: &str = "%Y-%m-%dT%H:%M:%S.%3f%:z";
324
325 /// Initializes the global logger with an "abbreviated" timed pretty, sensible
326 /// env logger.
327 ///
328 /// This should be called early in the execution of a Rust program, and the
329 /// global logger may only be initialized once. Future initialization attempts
330 /// will return an error.
331 ///
332 /// # Details
333 ///
334 /// This variant formats log messages with a localized timestamp, without
335 /// the date part.
336 ///
337 /// ## Example
338 ///
339 /// ```console
340 /// 12:15:31.683 INFO my_module > an info message!
341 /// ```
342 ///
343 /// # Requirements
344 ///
345 /// Using this macro requires the `local-time` feature to be enabled:
346 ///
347 /// ```toml
348 /// [dependencies]
349 /// sensible-env-logger = { version = "*", features = ["local-time"] }
350 /// ```
351 ///
352 /// # Panics
353 ///
354 /// This macro fails to set the global logger if one has already been set.
355 #[macro_export]
356 macro_rules! init_timed_short {
357 () => {
358 $crate::try_init_timed_short!().unwrap();
359 };
360 }
361
362 /// Initializes the global logger with a "no-frills" local date/time
363 /// pretty, sensible env logger.
364 ///
365 /// This should be called early in the execution of a Rust program, and the
366 /// global logger may only be initialized once. Future initialization attempts
367 /// will return an error.
368 ///
369 /// # Details
370 ///
371 /// This variant formats log messages with a localized timestamp,
372 /// prefixed by the date part.
373 ///
374 /// ## Example
375 ///
376 /// ```console
377 /// 2021-10-27 12:15:31.683 - INFO my_module > an info message!
378 /// ```
379 ///
380 /// # Requirements
381 ///
382 /// Using this macro requires the `local-time` feature to be enabled:
383 ///
384 /// ```toml
385 /// [dependencies]
386 /// sensible-env-logger = { version = "*", features = ["local-time"] }
387 /// ```
388 ///
389 /// # Panics
390 ///
391 /// This macro fails to set the global logger if one has already been set.
392 #[macro_export]
393 macro_rules! init_timed_local {
394 () => {
395 $crate::try_init_timed_local!().unwrap();
396 };
397 }
398
399 /// Initializes the global logger with a local-timed pretty, sensible
400 /// env logger.
401 ///
402 /// This should be called early in the execution of a Rust program, and the
403 /// global logger may only be initialized once. Future initialization attempts
404 /// will return an error.
405 ///
406 /// # Details
407 ///
408 /// This variant formats log messages with a localized timestamp and zone,
409 /// in complete ISO-8601/ RFC 3339 date & time format.
410 ///
411 /// ## Example
412 ///
413 /// ```console
414 /// 2022-10-27T12:15:31.683+08:00 INFO my_module > an info message!
415 /// ```
416 ///
417 /// # Requirements
418 ///
419 /// Using this macro requires the `local-time` feature to be enabled:
420 ///
421 /// ```toml
422 /// [dependencies]
423 /// sensible-env-logger = { version = "*", features = ["local-time"] }
424 /// ```
425 ///
426 /// # Panics
427 ///
428 /// This macro fails to set the global logger if one has already been set.
429 #[macro_export]
430 macro_rules! init_timed_local_iso {
431 () => {
432 $crate::try_init_timed_local_iso!().unwrap();
433 };
434 }
435
436 /// Initializes the global logger with an "abbreviated" timed pretty, sensible
437 /// env logger.
438 /// See [`init_timed_short!`](macro.init_timed_short.html) for more info.
439 ///
440 /// This variant should ideally only be used in **tests**. It should be called
441 /// early in the execution of a Rust program.
442 ///
443 /// Future initialization attempts will *safely ignore* any errors.
444 #[macro_export]
445 macro_rules! safe_init_timed_short {
446 () => {
447 let _ = $crate::try_init_timed_short!();
448 };
449 }
450
451 /// Initializes the global logger with a "no-frills" local date/time
452 /// pretty, sensible env logger.
453 /// See [`init_timed_local!`](macro.init_timed_local.html) for more info.
454 ///
455 /// This variant should ideally only be used in **tests**. It should be called
456 /// early in the execution of a Rust program.
457 ///
458 /// Future initialization attempts will *safely ignore* any errors.
459 #[macro_export]
460 macro_rules! safe_init_timed_local {
461 () => {
462 let _ = $crate::try_init_timed_local!();
463 };
464 }
465
466 /// Initializes the global logger with a local-timed pretty, sensible
467 /// env logger.
468 /// See [`init_timed_local_iso!`](macro.init_timed_local_iso.html) for more info.
469 ///
470 /// This variant should ideally only be used in **tests**. It should be called
471 /// early in the execution of a Rust program.
472 ///
473 /// Future initialization attempts will *safely ignore* any errors.
474 #[macro_export]
475 macro_rules! safe_init_timed_local_iso {
476 () => {
477 let _ = $crate::try_init_timed_local_iso!();
478 };
479 }
480
481 /// Initializes the global logger with an "abbreviated" timed pretty, sensible
482 /// env logger.
483 ///
484 /// See [`init_timed_short!`](macro.init_timed_short.html) for more info.
485 ///
486 /// This should be called early in the execution of a Rust program, and the
487 /// global logger may only be initialized once. Future initialization attempts
488 /// will return an error.
489 ///
490 /// # Errors
491 ///
492 /// This macro fails to set the global logger if one has already been set.
493 #[macro_export]
494 macro_rules! try_init_timed_short {
495 () => {
496 $crate::try_init_custom_env_and_builder(
497 "RUST_LOG",
498 "GLOBAL_RUST_LOG",
499 env!("CARGO_PKG_NAME"),
500 module_path!(),
501 $crate::formatted_local_time_builder_fn($crate::TIME_ONLY_FMT),
502 )
503 };
504 }
505
506 /// Initializes the global logger with a "no-frills" local date/time
507 /// pretty, sensible env logger.
508 ///
509 /// See [`init_timed_local!`](macro.init_timed_local.html) for more info.
510 ///
511 /// This should be called early in the execution of a Rust program, and the
512 /// global logger may only be initialized once. Future initialization attempts
513 /// will return an error.
514 ///
515 /// # Errors
516 ///
517 /// This macro fails to set the global logger if one has already been set.
518 #[macro_export]
519 macro_rules! try_init_timed_local {
520 () => {
521 $crate::try_init_custom_env_and_builder(
522 "RUST_LOG",
523 "GLOBAL_RUST_LOG",
524 env!("CARGO_PKG_NAME"),
525 module_path!(),
526 $crate::formatted_local_time_builder_fn($crate::LOCAL_TIME_FMT),
527 )
528 };
529 }
530
531 /// Initializes the global logger with a local-timed pretty, sensible
532 /// env logger.
533 ///
534 /// See [`init_timed_local_iso!`](macro.init_timed_local_iso.html) for more info.
535 ///
536 /// This should be called early in the execution of a Rust program, and the
537 /// global logger may only be initialized once. Future initialization attempts
538 /// will return an error.
539 ///
540 /// # Errors
541 ///
542 /// This macro fails to set the global logger if one has already been set.
543 #[macro_export]
544 macro_rules! try_init_timed_local_iso {
545 () => {
546 $crate::try_init_custom_env_and_builder(
547 "RUST_LOG",
548 "GLOBAL_RUST_LOG",
549 env!("CARGO_PKG_NAME"),
550 module_path!(),
551 $crate::formatted_local_time_builder_fn($crate::ISO_FMT),
552 )
553 };
554 }
555
556 /// Returns a function (closure) that returns a formatted builder which
557 /// adds local time to log messages, per a specified date/time format.
558 ///
559 /// ## Example
560 /// ```console
561 /// 12:15:31.683 INFO my_module > an info message!
562 /// ```
563 ///
564 pub fn formatted_local_time_builder_fn(fmt: &'static str) -> impl Fn() -> Builder {
565 || {
566 let mut builder = Builder::new();
567
568 builder.format(|f, record| {
569 use std::io::Write;
570 let target = record.target();
571 let max_width = max_target_width(target);
572
573 let mut style = f.style();
574 let level = colored_level(&mut style, record.level());
575
576 let mut style = f.style();
577 let target = style.set_bold(true).value(Padded {
578 value: target,
579 width: max_width,
580 });
581
582 let time = Local::now().format(fmt);
583
584 writeln!(f, " {} {} {} > {}", time, level, target, record.args(),)
585 });
586
587 builder
588 }
589 }
590
591 /// Helper functions
592 ///
593 /// Below are copied verbatim from [`pretty_env_logger`]
594 ///
595 /// [`pretty_env_logger`]: https://github.com/seanmonstar/pretty-env-logger/blob/master/src/lib.rs
596 ///
597
598 struct Padded<T> {
599 value: T,
600 width: usize,
601 }
602
603 impl<T: fmt::Display> fmt::Display for Padded<T> {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 write!(f, "{: <width$}", self.value, width = self.width)
606 }
607 }
608
609 static MAX_MODULE_WIDTH: AtomicUsize = AtomicUsize::new(0);
610
611 fn max_target_width(target: &str) -> usize {
612 let max_width = MAX_MODULE_WIDTH.load(Ordering::Relaxed);
613 if max_width < target.len() {
614 MAX_MODULE_WIDTH.store(target.len(), Ordering::Relaxed);
615 target.len()
616 } else {
617 max_width
618 }
619 }
620
621 fn colored_level(style: &'_ mut Style, level: Level) -> StyledValue<'_, &'static str> {
622 match level {
623 Level::Trace => style.set_color(Color::Magenta).value("TRACE"),
624 Level::Debug => style.set_color(Color::Blue).value("DEBUG"),
625 Level::Info => style.set_color(Color::Green).value("INFO "),
626 Level::Warn => style.set_color(Color::Yellow).value("WARN "),
627 Level::Error => style.set_color(Color::Red).value("ERROR"),
628 }
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use log::*;
635
636 use super::*;
637
638 #[test]
639 fn logging_in_tests() {
640 // Initialize the global logger with sensible defaults
641 init!();
642
643 trace!("A simple trace message");
644 debug!("Debugging something...");
645 warn!("This is a WARNING!");
646 }
647
648 #[test]
649 fn test_base_module_simple() {
650 let result = base_module("hello_world");
651 assert_eq!(result, "hello_world");
652 }
653
654 #[test]
655 fn test_base_module_with_nested_path() {
656 let result = base_module("my_bin::my_module::tests");
657 assert_eq!(result, "my_bin");
658 }
659}