123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- //! A logger that prints all messages with a simple, readable output format.
- //!
- //! Optional features include timestamps, colored output and logging to stderr.
- //!
- //! ```
- //! simple_logger::SimpleLogger::new().env().init().unwrap();
- //!
- //! log::warn!("This is an example message.");
- //! ```
- //!
- //! Some shortcuts are available for common use cases.
- //!
- //! Just initialize logging without any configuration:
- //!
- //! ```
- //! simple_logger::init().unwrap();
- //! ```
- //!
- //! Set the log level from the `RUST_LOG` environment variable:
- //!
- //! ```
- //! simple_logger::init_with_env().unwrap();
- //! ```
- //!
- //! Hardcode a default log level:
- //!
- //! ```
- //! simple_logger::init_with_level(log::Level::Warn).unwrap();
- //! ```
- #![cfg_attr(feature = "nightly", feature(thread_id_value))]
- #[cfg(feature = "colored")]
- use colored::*;
- use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
- use std::collections::HashMap;
- #[cfg(feature = "timestamps")]
- use time::{format_description::FormatItem, OffsetDateTime, UtcOffset};
- #[cfg(feature = "timestamps")]
- const TIMESTAMP_FORMAT_OFFSET: &[FormatItem] = time::macros::format_description!(
- "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3][offset_hour sign:mandatory]:[offset_minute]"
- );
- #[cfg(feature = "timestamps")]
- const TIMESTAMP_FORMAT_UTC: &[FormatItem] = time::macros::format_description!(
- "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
- );
- #[cfg(feature = "timestamps")]
- #[derive(PartialEq)]
- enum Timestamps {
- None,
- Local,
- Utc,
- UtcOffset(UtcOffset),
- }
- /// Implements [`Log`] and a set of simple builder methods for configuration.
- ///
- /// Use the various "builder" methods on this struct to configure the logger,
- /// then call [`init`] to configure the [`log`] crate.
- pub struct SimpleLogger {
- /// The default logging level
- default_level: LevelFilter,
- /// The specific logging level for each module
- ///
- /// This is used to override the default value for some specific modules.
- /// After initialization, the vector is sorted so that the first (prefix) match
- /// directly gives us the desired log level.
- module_levels: Vec<(String, LevelFilter)>,
- /// Whether to include thread names (and IDs) or not
- ///
- /// This field is only available if the `threads` feature is enabled.
- #[cfg(feature = "threads")]
- threads: bool,
- /// Control how timestamps are displayed.
- ///
- /// This field is only available if the `timestamps` feature is enabled.
- #[cfg(feature = "timestamps")]
- timestamps: Timestamps,
- /// Whether to use color output or not.
- ///
- /// This field is only available if the `color` feature is enabled.
- #[cfg(feature = "colored")]
- colors: bool,
- }
- impl SimpleLogger {
- /// Initializes the global logger with a SimpleLogger instance with
- /// default log level set to `Level::Trace`.
- ///
- /// ```no_run
- /// use simple_logger::SimpleLogger;
- /// SimpleLogger::new().env().init().unwrap();
- /// log::warn!("This is an example message.");
- /// ```
- ///
- /// [`init`]: #method.init
- #[must_use = "You must call init() to begin logging"]
- pub fn new() -> SimpleLogger {
- SimpleLogger {
- default_level: LevelFilter::Trace,
- module_levels: Vec::new(),
- #[cfg(feature = "threads")]
- threads: false,
- #[cfg(feature = "timestamps")]
- timestamps: Timestamps::Utc,
- #[cfg(feature = "colored")]
- colors: true,
- }
- }
- /// Simulates env_logger behavior, which enables the user to choose log level by
- /// setting a `RUST_LOG` environment variable. The `RUST_LOG` is not set or its value is not
- /// recognized as one of the log levels, this function will use the `Error` level by default.
- ///
- /// You may use the various builder-style methods on this type to configure
- /// the logger, and you must call [`init`] in order to start logging messages.
- ///
- /// ```no_run
- /// use simple_logger::SimpleLogger;
- /// SimpleLogger::from_env().init().unwrap();
- /// log::warn!("This is an example message.");
- /// ```
- ///
- /// [`init`]: #method.init
- #[must_use = "You must call init() to begin logging"]
- #[deprecated(
- since = "1.12.0",
- note = "Use [`env`](#method.env) instead. Will be removed in version 2.0.0."
- )]
- pub fn from_env() -> SimpleLogger {
- SimpleLogger::new()
- .with_level(log::LevelFilter::Error)
- .env()
- }
- /// Simulates env_logger behavior, which enables the user to choose log
- /// level by setting a `RUST_LOG` environment variable. This will use
- /// the default level set by [`with_level`] if `RUST_LOG` is not set or
- /// can't be parsed as a standard log level.
- ///
- /// [`with_level`]: #method.with_level
- #[must_use = "You must call init() to begin logging"]
- pub fn env(mut self) -> SimpleLogger {
- if let Ok(level) = std::env::var("RUST_LOG") {
- match level.to_lowercase().as_str() {
- "trace" => self.default_level = log::LevelFilter::Trace,
- "debug" => self.default_level = log::LevelFilter::Debug,
- "info" => self.default_level = log::LevelFilter::Info,
- "warn" => self.default_level = log::LevelFilter::Warn,
- "error" => self.default_level = log::LevelFilter::Error,
- _ => (),
- }
- };
- self
- }
- /// Set the 'default' log level.
- ///
- /// You can override the default level for specific modules and their sub-modules using [`with_module_level`]
- ///
- /// [`with_module_level`]: #method.with_module_level
- #[must_use = "You must call init() to begin logging"]
- pub fn with_level(mut self, level: LevelFilter) -> SimpleLogger {
- self.default_level = level;
- self
- }
- /// Override the log level for some specific modules.
- ///
- /// This sets the log level of a specific module and all its sub-modules.
- /// When both the level for a parent module as well as a child module are set,
- /// the more specific value is taken. If the log level for the same module is
- /// specified twice, the resulting log level is implementation defined.
- ///
- /// # Examples
- ///
- /// Silence an overly verbose crate:
- ///
- /// ```no_run
- /// use simple_logger::SimpleLogger;
- /// use log::LevelFilter;
- ///
- /// SimpleLogger::new().with_module_level("chatty_dependency", LevelFilter::Warn).init().unwrap();
- /// ```
- ///
- /// Disable logging for all dependencies:
- ///
- /// ```no_run
- /// use simple_logger::SimpleLogger;
- /// use log::LevelFilter;
- ///
- /// SimpleLogger::new()
- /// .with_level(LevelFilter::Off)
- /// .with_module_level("my_crate", LevelFilter::Info)
- /// .init()
- /// .unwrap();
- /// ```
- #[must_use = "You must call init() to begin logging"]
- pub fn with_module_level(mut self, target: &str, level: LevelFilter) -> SimpleLogger {
- self.module_levels.push((target.to_string(), level));
- /* Normally this is only called in `init` to avoid redundancy, but we can't initialize the logger in tests */
- #[cfg(test)]
- self.module_levels
- .sort_by_key(|(name, _level)| name.len().wrapping_neg());
- self
- }
- /// Override the log level for specific targets.
- #[must_use = "You must call init() to begin logging"]
- #[deprecated(
- since = "1.11.0",
- note = "Use [`with_module_level`](#method.with_module_level) instead. Will be removed in version 2.0.0."
- )]
- pub fn with_target_levels(
- mut self,
- target_levels: HashMap<String, LevelFilter>,
- ) -> SimpleLogger {
- self.module_levels = target_levels.into_iter().collect();
- /* Normally this is only called in `init` to avoid redundancy, but we can't initialize the logger in tests */
- #[cfg(test)]
- self.module_levels
- .sort_by_key(|(name, _level)| name.len().wrapping_neg());
- self
- }
- /// Control whether thread names (and IDs) are printed or not.
- ///
- /// This method is only available if the `threads` feature is enabled.
- /// Thread names are disabled by default.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "threads")]
- pub fn with_threads(mut self, threads: bool) -> SimpleLogger {
- self.threads = threads;
- self
- }
- /// Control whether timestamps are printed or not.
- ///
- /// Timestamps will be displayed in the local timezone.
- ///
- /// This method is only available if the `timestamps` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "timestamps")]
- #[deprecated(
- since = "1.16.0",
- note = "Use [`with_local_timestamps`] or [`with_utc_timestamps`] instead. Will be removed in version 2.0.0."
- )]
- pub fn with_timestamps(mut self, timestamps: bool) -> SimpleLogger {
- if timestamps {
- self.timestamps = Timestamps::Local
- } else {
- self.timestamps = Timestamps::None
- }
- self
- }
- /// Don't display any timestamps.
- ///
- /// This method is only available if the `timestamps` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "timestamps")]
- pub fn without_timestamps(mut self) -> SimpleLogger {
- self.timestamps = Timestamps::None;
- self
- }
- /// Display timestamps using the local timezone.
- ///
- /// This method is only available if the `timestamps` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "timestamps")]
- pub fn with_local_timestamps(mut self) -> SimpleLogger {
- self.timestamps = Timestamps::Local;
- self
- }
- /// Display timestamps using UTC.
- ///
- /// This method is only available if the `timestamps` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "timestamps")]
- pub fn with_utc_timestamps(mut self) -> SimpleLogger {
- self.timestamps = Timestamps::Utc;
- self
- }
- /// Display timestamps using a static UTC offset.
- ///
- /// This method is only available if the `timestamps` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "timestamps")]
- pub fn with_utc_offset(mut self, offset: UtcOffset) -> SimpleLogger {
- self.timestamps = Timestamps::UtcOffset(offset);
- self
- }
- /// Control whether messages are colored or not.
- ///
- /// This method is only available if the `colored` feature is enabled.
- #[must_use = "You must call init() to begin logging"]
- #[cfg(feature = "colored")]
- pub fn with_colors(mut self, colors: bool) -> SimpleLogger {
- self.colors = colors;
- self
- }
- /// 'Init' the actual logger, instantiate it and configure it,
- /// this method MUST be called in order for the logger to be effective.
- pub fn init(mut self) -> Result<(), SetLoggerError> {
- #[cfg(all(windows, feature = "colored"))]
- set_up_color_terminal();
- /* Sort all module levels from most specific to least specific. The length of the module
- * name is used instead of its actual depth to avoid module name parsing.
- */
- self.module_levels
- .sort_by_key(|(name, _level)| name.len().wrapping_neg());
- let max_level = self
- .module_levels
- .iter()
- .map(|(_name, level)| level)
- .copied()
- .max();
- let max_level = max_level
- .map(|lvl| lvl.max(self.default_level))
- .unwrap_or(self.default_level);
- log::set_max_level(max_level);
- log::set_boxed_logger(Box::new(self))?;
- Ok(())
- }
- }
- impl Default for SimpleLogger {
- /// See [this](struct.SimpleLogger.html#method.new)
- fn default() -> Self {
- SimpleLogger::new()
- }
- }
- impl Log for SimpleLogger {
- fn enabled(&self, metadata: &Metadata) -> bool {
- &metadata.level().to_level_filter()
- <= self
- .module_levels
- .iter()
- /* At this point the Vec is already sorted so that we can simply take
- * the first match
- */
- .find(|(name, _level)| metadata.target().starts_with(name))
- .map(|(_name, level)| level)
- .unwrap_or(&self.default_level)
- }
- fn log(&self, record: &Record) {
- if self.enabled(record.metadata()) {
- let level_string = {
- #[cfg(feature = "colored")]
- {
- if self.colors {
- match record.level() {
- Level::Error => record.level().to_string().red().to_string(),
- Level::Warn => record.level().to_string().yellow().to_string(),
- Level::Info => record.level().to_string().cyan().to_string(),
- Level::Debug => record.level().to_string().purple().to_string(),
- Level::Trace => record.level().to_string().normal().to_string(),
- }
- } else {
- record.level().to_string()
- }
- }
- #[cfg(not(feature = "colored"))]
- {
- record.level().to_string()
- }
- };
- let target = if !record.target().is_empty() {
- record.target()
- } else {
- record.module_path().unwrap_or_default()
- };
- let thread = {
- #[cfg(feature = "threads")]
- if self.threads {
- let thread = std::thread::current();
- format!("@{}", {
- #[cfg(feature = "nightly")]
- {
- thread.name().unwrap_or(&thread.id().as_u64().to_string())
- }
- #[cfg(not(feature = "nightly"))]
- {
- thread.name().unwrap_or("?")
- }
- })
- } else {
- "".to_string()
- }
- #[cfg(not(feature = "threads"))]
- ""
- };
- let timestamp = {
- #[cfg(feature = "timestamps")]
- match self.timestamps {
- Timestamps::None => "".to_string(),
- Timestamps::Local => format!("{} ", OffsetDateTime::now_local().expect(concat!(
- "Could not determine the UTC offset on this system. ",
- "Consider displaying UTC time instead. ",
- "Possible causes are that the time crate does not implement \"local_offset_at\" ",
- "on your system, or that you are running in a multi-threaded environment and ",
- "the time crate is returning \"None\" from \"local_offset_at\" to avoid unsafe ",
- "behaviour. See the time crate's documentation for more information. ",
- "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)"
- )).format(&TIMESTAMP_FORMAT_OFFSET).unwrap()),
- Timestamps::Utc => format!("{} ", OffsetDateTime::now_utc().format(&TIMESTAMP_FORMAT_UTC).unwrap()),
- Timestamps::UtcOffset(offset) => format!("{} ", OffsetDateTime::now_utc().to_offset(offset).format(&TIMESTAMP_FORMAT_UTC).unwrap()),
- }
- #[cfg(not(feature = "timestamps"))]
- ""
- };
- let message = format!(
- "{}{:<5} [{}{}] {}",
- timestamp,
- level_string,
- target,
- thread,
- record.args()
- );
- #[cfg(not(feature = "stderr"))]
- println!("{}", message);
- #[cfg(feature = "stderr")]
- eprintln!("{}", message);
- }
- }
- fn flush(&self) {}
- }
- #[cfg(all(windows, feature = "colored"))]
- fn set_up_color_terminal() {
- use atty::Stream;
- if atty::is(Stream::Stdout) {
- unsafe {
- use winapi::um::consoleapi::*;
- use winapi::um::handleapi::*;
- use winapi::um::processenv::*;
- use winapi::um::winbase::*;
- use winapi::um::wincon::*;
- let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
- if stdout == INVALID_HANDLE_VALUE {
- return;
- }
- let mut mode: winapi::shared::minwindef::DWORD = 0;
- if GetConsoleMode(stdout, &mut mode) == 0 {
- return;
- }
- SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
- }
- }
- }
- /// Initialise the logger with it's default configuration.
- ///
- /// Log messages will not be filtered.
- /// The `RUST_LOG` environment variable is not used.
- pub fn init() -> Result<(), SetLoggerError> {
- SimpleLogger::new().init()
- }
- /// Initialise the logger with it's default configuration.
- ///
- /// Log messages will not be filtered.
- /// The `RUST_LOG` environment variable is not used.
- ///
- /// This function is only available if the `timestamps` feature is enabled.
- #[cfg(feature = "timestamps")]
- pub fn init_utc() -> Result<(), SetLoggerError> {
- SimpleLogger::new().with_utc_timestamps().init()
- }
- /// Initialise the logger with the `RUST_LOG` environment variable.
- ///
- /// Log messages will be filtered based on the `RUST_LOG` environment variable.
- pub fn init_with_env() -> Result<(), SetLoggerError> {
- SimpleLogger::new().env().init()
- }
- /// Initialise the logger with a specific log level.
- ///
- /// Log messages below the given [`Level`] will be filtered.
- /// The `RUST_LOG` environment variable is not used.
- pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
- SimpleLogger::new()
- .with_level(level.to_level_filter())
- .init()
- }
- /// Use [`init_with_env`] instead.
- ///
- /// This does the same as [`init_with_env`] but unwraps the result.
- #[deprecated(
- since = "1.12.0",
- note = "Use [`init_with_env`] instead, which does not unwrap the result. Will be removed in version 2.0.0."
- )]
- pub fn init_by_env() {
- init_with_env().unwrap()
- }
- #[cfg(test)]
- mod test {
- use super::*;
- #[test]
- fn test_module_levels_allowlist() {
- let logger = SimpleLogger::new()
- .with_level(LevelFilter::Off)
- .with_module_level("my_crate", LevelFilter::Info);
- assert!(logger.enabled(&create_log("my_crate", Level::Info)));
- assert!(logger.enabled(&create_log("my_crate::module", Level::Info)));
- assert!(!logger.enabled(&create_log("my_crate::module", Level::Debug)));
- assert!(!logger.enabled(&create_log("not_my_crate", Level::Debug)));
- assert!(!logger.enabled(&create_log("not_my_crate::module", Level::Error)));
- }
- #[test]
- fn test_module_levels_denylist() {
- let logger = SimpleLogger::new()
- .with_level(LevelFilter::Debug)
- .with_module_level("my_crate", LevelFilter::Trace)
- .with_module_level("chatty_dependency", LevelFilter::Info);
- assert!(logger.enabled(&create_log("my_crate", Level::Info)));
- assert!(logger.enabled(&create_log("my_crate", Level::Trace)));
- assert!(logger.enabled(&create_log("my_crate::module", Level::Info)));
- assert!(logger.enabled(&create_log("my_crate::module", Level::Trace)));
- assert!(logger.enabled(&create_log("not_my_crate", Level::Debug)));
- assert!(!logger.enabled(&create_log("not_my_crate::module", Level::Trace)));
- assert!(logger.enabled(&create_log("chatty_dependency", Level::Info)));
- assert!(!logger.enabled(&create_log("chatty_dependency", Level::Debug)));
- assert!(!logger.enabled(&create_log("chatty_dependency::module", Level::Debug)));
- assert!(logger.enabled(&create_log("chatty_dependency::module", Level::Warn)));
- }
- #[test]
- #[cfg(feature = "timestamps")]
- fn test_timestamps_defaults() {
- let builder = SimpleLogger::new();
- assert!(builder.timestamps == Timestamps::Utc);
- }
- #[test]
- #[cfg(feature = "timestamps")]
- #[allow(deprecated)]
- fn test_with_timestamps() {
- let builder = SimpleLogger::new().with_timestamps(false);
- assert!(builder.timestamps == Timestamps::None);
- }
- #[test]
- #[cfg(feature = "timestamps")]
- fn test_with_utc_timestamps() {
- let builder = SimpleLogger::new().with_utc_timestamps();
- assert!(builder.timestamps == Timestamps::Utc);
- }
- #[test]
- #[cfg(feature = "timestamps")]
- fn test_with_local_timestamps() {
- let builder = SimpleLogger::new().with_local_timestamps();
- assert!(builder.timestamps == Timestamps::Local);
- }
- #[test]
- #[cfg(feature = "colored")]
- fn test_with_colors() {
- let mut builder = SimpleLogger::new();
- assert!(builder.colors == true);
- builder = builder.with_colors(false);
- assert!(builder.colors == false);
- }
- fn create_log(name: &str, level: Level) -> Metadata {
- let mut builder = Metadata::builder();
- builder.level(level);
- builder.target(name);
- builder.build()
- }
- }
|