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}