[
  {
    "path": ".github/workflows/ci.yml",
    "content": "---\nname: ci\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n  release:\n    types:\n      - created\n\nenv:\n  # Just a reassurance to mitigate sudden network connection problems\n  CARGO_NET_RETRY: 10\n  RUSTUP_MAX_RETRIES: 10\n\n  CARGO_INCREMENTAL: 0\n  RUST_BACKTRACE: full\n\n  # We don't need any debug symbols on ci, this also speeds up builds a bunch\n  RUSTFLAGS: --deny warnings -Cdebuginfo=0\n  RUSTDOCFLAGS: --deny warnings\n\njobs:\n  rust-lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions-rs/toolchain@v1\n        with:\n          toolchain: stable\n          profile: minimal\n          components: rustfmt, clippy\n\n      - run: cargo clippy --workspace\n      - run: cargo fmt --all -- --check\n\n  rust-test:\n    runs-on: ${{ matrix.os }}\n\n    continue-on-error: ${{ matrix.toolchain != 'stable' }}\n\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - windows-latest\n          - macos-latest\n        toolchain:\n          - stable\n        features:\n          - \"colors\"\n          - \"threads\"\n          - \"timestamps\"\n          - \"stderr\"\n        exclude:\n          - os: macos-latest\n            toolchain: stable\n            features: \"timestamps\"\n        include:\n          - os: ubuntu-latest\n            toolchain: beta\n            features: \"colors,threads,timestamps\"\n          - os: ubuntu-latest\n            toolchain: nightly\n            features: \"colors,threads,timestamps,nightly\"\n\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions-rs/toolchain@v1\n        with:\n          toolchain: ${{ matrix.toolchain }}\n          profile: minimal\n      - run: cargo +${{ matrix.toolchain }} build --no-default-features --features ${{ matrix.features }}\n      - run: cargo +${{ matrix.toolchain }} test --no-default-features --features ${{ matrix.features }}\n\n  rust-publish-crates:\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n    needs:\n      - rust-lint\n      - rust-test\n    environment: release\n    permissions:\n      id-token: write\n    steps:\n      - id: checkout\n        uses: actions/checkout@v5\n      - id: auth\n        uses: rust-lang/crates-io-auth-action@v1\n      - id: publish\n        run: cargo publish\n        env:\n          CARGO_REGISTRY_TOKEN: \"${{ steps.auth.outputs.token }}\"\n"
  },
  {
    "path": ".gitignore",
    "content": "target\nCargo.lock\n.idea/\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"simple_logger\"\nversion = \"5.2.0\"\nlicense = \"MIT\"\nauthors = [\"Sam Clements <sam@borntyping.co.uk>\"]\ndescription = \"A logger that prints all messages with a readable output format\"\nrepository = \"https://github.com/borntyping/rust-simple_logger\"\nedition = \"2018\"\n\n[features]\ndefault = [\"colors\", \"timestamps\"]\ncolors = [\"colored\"]\nthreads = []\ntimestamps = [\"time\"]\nnightly = []\nstderr = []\n\n[dependencies]\nlog = { version = \"^0.4.28\", features = [\"std\"] }\ntime = { version = \"^0.3.47\", features = [\"formatting\", \"local-offset\", \"macros\"], optional = true }\ncolored = { version = \"^3.0.0\", optional = true }\n\n[target.'cfg(windows)'.dependencies]\nwindows-sys = { version = \"^0.61.2\", features = [\"Win32_System_Console\", \"Win32_Foundation\"] }\n\n[[example]]\nname = \"colors\"\nrequired-features = [\"colors\"]\n\n[[example]]\nname = \"stderr\"\nrequired-features = [\"colors\", \"stderr\"]\n\n[[example]]\nname = \"threads\"\nrequired-features = [\"threads\"]\n\n[[example]]\nname = \"timestamps_local\"\nrequired-features = [\"timestamps\"]\n\n[[example]]\nname = \"timestamps_none\"\nrequired-features = [\"timestamps\"]\n\n[[example]]\nname = \"timestamps_utc\"\nrequired-features = [\"timestamps\"]\n\n[[example]]\nname = \"timestamps_utc_offset\"\nrequired-features = [\"timestamps\"]\n\n[[example]]\nname = \"timestamps_format\"\nrequired-features = [\"timestamps\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2015-2021 Sam Clements\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# simple_logger [![](https://img.shields.io/github/tag/borntyping/rust-simple_logger.svg)](https://github.com/borntyping/rust-simple_logger/tags)\n\nA logger that prints all messages with a readable output format.\n\nThe output format is based on the format used by [Supervisord](https://github.com/Supervisor/supervisor), with timestamps in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format.\n\nThe format and timezone used for timestamps can be customised, simple colours based on log level can be enabled, thread metadata can be included, and output can be toggled between STDOUT and STDERR. \n\n* [Source on GitHub](https://github.com/borntyping/rust-simple_logger)\n* [Packages on Crates.io](https://crates.io/crates/simple_logger)\n* [Documentation on Docs.rs](https://docs.rs/simple_logger)\n\nNotices\n-------\n\n### Project status\n\nI wrote the initial version of this library in 2015, and haven't written Rust professionally since then.\nI consider this as close as I'll ever get to a \"finished\" project and don't plan on adding any more features to it.\n\nIt is still maintained and I try and merge pull requests for fixes, improvements, and features; though I generally turn down pull requests for large or complex features that go outside the library's aim to be a simple logger.\nIf you need more, the `log` module has a list of [available logging implementations](https://docs.rs/log/latest/log/#available-logging-implementations), or you could consider forking `simple_logger` and building on top of it.\n\n### Breaking changes\n\n- **Version 2.0.0 changes the default from displaying timestamps in the local timezone to displaying timestamps in UTC.** See issue [#52](https://github.com/borntyping/rust-simple_logger/issues/52) for more information.\n\nUsage\n-----\n\n```rust\nuse simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n```\n\nThis outputs:\n\n```txt\n2022-01-19T17:27:07.013874956Z WARN [logging_example] This is an example message.\n```\n\nYou can run the above example with:\n\n```sh\ncargo run --example init\n```\n\n### Optional features\n\nThe `colors` and `timestamps` features are enabled by default. You can remove these\nfeatures and their respective dependencies by disabling all features in your\n`Cargo.toml`.\n\n```toml\n[dependencies.simple_logger]\ndefault-features = false\n```\n\nTo include the `timestamps` feature, but not the `colors` feature:\n\n```toml\n[dependencies.simple_logger]\ndefault-features = false\nfeatures = [\"timestamps\"]\n```\n\nTo include the `colors` feature, but not the `timestamps` feature:\n\n```toml\n[dependencies.simple_logger]\ndefault-features = false\nfeatures = [\"colors\"]\n```\n\nTo include thread metadata use the `threads` and `nightly` features:\n\n```toml\n[dependencies.simple_logger]\nfeatures = [\"threads\", \"nightly\"]\n```\n\nTo direct logging output to `stderr` use the `stderr` feature:\n\n```toml\n[dependencies.simple_logger]\nfeatures = [\"stderr\"]\n```\n\nMultiple features can be combined.\n\n```toml\n[dependencies.simple_logger]\nfeatures = [\"colors\", \"threads\", \"timestamps\", \"nightly\", \"stderr\"]\n```\n\n### Wrapping with another logger\n\nYou might want to wrap this logger to do your own processing before handing events to a SimpleLogger instance. Instead\nof calling `init()` which calls `log::set_max_level` and `log::set_boxed_logger`, you can call those functions directly\ngiving you the chance to wrap or adjust the logger. See [wrap.rs](examples/wrap.rs) for a more detailed example.\n\n### Console setup\n\nThe `SimpleLogger.init()` function attempts to configure colours support as best it can in various situations:\n\n- On Windows, it will enable colour output. _See `set_up_windows_color_terminal()`._\n- When using the `colors` *and* `stderr` features, it will instruct the `colored` library to display colors if STDERR\n  is a terminal (instead of checking if STDOUT is a terminal). _See `use_stderr_for_colors()`._\n\nLicence\n-------\n\n`simple_logger` is licenced under the [MIT Licence](http://opensource.org/licenses/MIT).\n\nAuthors\n-------\n\nWritten by [Sam Clements](sam@borntyping.co.uk).\n"
  },
  {
    "path": "examples/colors.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_colors(true).init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/flush.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n\n    log::logger().flush();\n}\n"
  },
  {
    "path": "examples/init.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/init_with_env.rs",
    "content": "//! Set the `RUST_LOG` environment variable and run this example to see the output change.\n//!\n//! Valid values are `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, and `TRACE`.\n//!\n//! ```shell\n//! RUST_LOG=WARN cargo run --example init_with_env\n//! ```\n//!\n//! It should also work if the environment variable is not set:\n//!\n//! ```shell\n//! cargo run --example init_with_env\n//! ```\nuse simple_logger;\n\nfn main() {\n    simple_logger::init_with_env().unwrap();\n\n    log::trace!(\"This is an example message.\");\n    log::debug!(\"This is an example message.\");\n    log::info!(\"This is an example message.\");\n    log::warn!(\"This is an example message.\");\n    log::error!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/init_with_level.rs",
    "content": "use log::LevelFilter;\nuse simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_level(LevelFilter::Warn).init().unwrap();\n\n    log::warn!(\"This will be logged.\");\n    log::info!(\"This will NOT be logged.\");\n}\n"
  },
  {
    "path": "examples/init_with_target_level.rs",
    "content": "use log::LevelFilter;\nuse simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new()\n        .with_level(LevelFilter::Info)\n        .with_module_level(\"init_with_target_level\", LevelFilter::Off)\n        .init()\n        .unwrap();\n\n    log::info!(\"This will NOT be logged. (Target disabled)\");\n}\n"
  },
  {
    "path": "examples/stderr.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_colors(true).init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/threads.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_threads(true).init().unwrap();\n\n    log::info!(\"Main thread logs here.\");\n\n    // If the \"nightly\" feature is enabled, the output will include thread ids.\n    for _ in 1..=5 {\n        std::thread::spawn(|| {\n            log::info!(\"Unnamed thread logs here.\");\n        })\n        .join()\n        .unwrap();\n    }\n\n    std::thread::Builder::new()\n        .name(\"named_thread\".to_string())\n        .spawn(|| {\n            log::info!(\"Named thread logs here.\");\n        })\n        .unwrap()\n        .join()\n        .unwrap();\n}\n"
  },
  {
    "path": "examples/timestamps_format.rs",
    "content": "use simple_logger::SimpleLogger;\nuse time::macros::format_description;\n\nfn main() {\n    SimpleLogger::new()\n        .env()\n        .with_timestamp_format(format_description!(\"[year]-[month]-[day] [hour]:[minute]:[second]\"))\n        .init()\n        .unwrap();\n\n    log::warn!(\"This is an example message with custom timestamp format.\");\n}\n"
  },
  {
    "path": "examples/timestamps_local.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_local_timestamps().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/timestamps_none.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().without_timestamps().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/timestamps_utc.rs",
    "content": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_utc_timestamps().init().unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "examples/timestamps_utc_offset.rs",
    "content": "use simple_logger::SimpleLogger;\nuse time::UtcOffset;\n\nfn main() {\n    SimpleLogger::new()\n        .with_utc_offset(UtcOffset::from_hms(14, 0, 0).unwrap())\n        .init()\n        .unwrap();\n\n    log::warn!(\"This is an example message using a static UTC offset.\");\n    log::info!(\"Daylight savings or other timezone changes will not be respected.\");\n}\n"
  },
  {
    "path": "examples/wrap.rs",
    "content": "use log::{Log, Metadata, Record};\nuse simple_logger::SimpleLogger;\n\nstruct WrapperLogger {\n    simple_logger: SimpleLogger,\n}\n\nimpl Log for WrapperLogger {\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        self.simple_logger.enabled(metadata)\n    }\n\n    fn log(&self, record: &Record) {\n        self.simple_logger.log(record)\n    }\n\n    fn flush(&self) {\n        self.simple_logger.flush()\n    }\n}\n\nfn main() {\n    let simple_logger = SimpleLogger::new();\n    log::set_max_level(simple_logger.max_level());\n\n    let wrapper_logger = WrapperLogger { simple_logger };\n    log::set_boxed_logger(Box::new(wrapper_logger)).unwrap();\n\n    log::warn!(\"This is an example message.\");\n}\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 120\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! A logger that prints all messages with a simple, readable output format.\n//!\n//! Optional features include timestamps, colored output and logging to stderr.\n//!\n//! ```rust\n//! simple_logger::SimpleLogger::new().env().init().unwrap();\n//!\n//! log::warn!(\"This is an example message.\");\n//! ```\n//!\n//! Some shortcuts are available for common use cases.\n//!\n//! Just initialize logging without any configuration:\n//!\n//! ```rust\n//! simple_logger::init().unwrap();\n//! ```\n//!\n//! Set the log level from the `RUST_LOG` environment variable:\n//!\n//! ```rust\n//! simple_logger::init_with_env().unwrap();\n//! ```\n//!\n//! Hardcode a default log level:\n//!\n//! ```rust\n//! simple_logger::init_with_level(log::Level::Warn).unwrap();\n//! ```\n\n#![cfg_attr(feature = \"nightly\", feature(thread_id_value))]\n\n#[cfg(feature = \"colors\")]\nuse colored::*;\nuse log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};\nuse std::{collections::HashMap, str::FromStr};\n#[cfg(feature = \"timestamps\")]\nuse time::{format_description::FormatItem, OffsetDateTime, UtcOffset};\n\n#[cfg(feature = \"timestamps\")]\nconst TIMESTAMP_FORMAT_OFFSET: &[FormatItem] = time::macros::format_description!(\n    \"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3][offset_hour sign:mandatory]:[offset_minute]\"\n);\n\n#[cfg(feature = \"timestamps\")]\nconst TIMESTAMP_FORMAT_UTC: &[FormatItem] =\n    time::macros::format_description!(\"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z\");\n\n#[cfg(feature = \"timestamps\")]\n#[derive(PartialEq)]\nenum Timestamps {\n    None,\n    Local,\n    Utc,\n    UtcOffset(UtcOffset),\n}\n\n/// Implements [`Log`] and a set of simple builder methods for configuration.\n///\n/// Use the various \"builder\" methods on this struct to configure the logger,\n/// then call [`init`] to configure the [`log`] crate.\npub struct SimpleLogger {\n    /// The default logging level\n    default_level: LevelFilter,\n\n    /// The specific logging level for each module\n    ///\n    /// This is used to override the default value for some specific modules.\n    ///\n    /// This must be sorted from most-specific to least-specific, so that [`enabled`](#method.enabled) can scan the\n    /// vector for the first match to give us the desired log level for a module.\n    module_levels: Vec<(String, LevelFilter)>,\n\n    /// Whether to include thread names (and IDs) or not\n    ///\n    /// This field is only available if the `threads` feature is enabled.\n    #[cfg(feature = \"threads\")]\n    threads: bool,\n\n    /// Control how timestamps are displayed.\n    ///\n    /// This field is only available if the `timestamps` feature is enabled.\n    #[cfg(feature = \"timestamps\")]\n    timestamps: Timestamps,\n    #[cfg(feature = \"timestamps\")]\n    timestamps_format: Option<&'static [FormatItem<'static>]>,\n\n    /// Whether to use color output or not.\n    ///\n    /// This field is only available if the `color` feature is enabled.\n    #[cfg(feature = \"colors\")]\n    colors: bool,\n}\n\nimpl SimpleLogger {\n    /// Initializes the global logger with a SimpleLogger instance with\n    /// default log level set to `Level::Trace`.\n    ///\n    /// ```no_run\n    /// use simple_logger::SimpleLogger;\n    /// SimpleLogger::new().env().init().unwrap();\n    /// log::warn!(\"This is an example message.\");\n    /// ```\n    ///\n    /// [`init`]: #method.init\n    #[must_use = \"You must call init() to begin logging\"]\n    pub fn new() -> SimpleLogger {\n        SimpleLogger {\n            default_level: LevelFilter::Trace,\n            module_levels: Vec::new(),\n\n            #[cfg(feature = \"threads\")]\n            threads: false,\n\n            #[cfg(feature = \"timestamps\")]\n            timestamps: Timestamps::Utc,\n\n            #[cfg(feature = \"timestamps\")]\n            timestamps_format: None,\n\n            #[cfg(feature = \"colors\")]\n            colors: true,\n        }\n    }\n\n    /// Initializes the global logger with log level read from `RUST_LOG` environment\n    /// variable value. Deprecated in favor of `env()`.\n    ///\n    /// You may use the various builder-style methods on this type to configure\n    /// the logger, and you must call [`init`] in order to start logging messages.\n    ///\n    /// ```no_run\n    /// use simple_logger::SimpleLogger;\n    /// SimpleLogger::from_env().init().unwrap();\n    /// log::warn!(\"This is an example message.\");\n    /// ```\n    ///\n    /// [`init`]: #method.init\n    #[must_use = \"You must call init() to begin logging\"]\n    #[deprecated(\n        since = \"1.12.0\",\n        note = \"Use [`env`](#method.env) instead. Will be removed in version 2.0.0.\"\n    )]\n    pub fn from_env() -> SimpleLogger {\n        SimpleLogger::new().with_level(log::LevelFilter::Error).env()\n    }\n\n    /// Enables the user to choose log level by setting `RUST_LOG=<level>`\n    /// environment variable. This will use the default level set by\n    /// [`with_level`] if `RUST_LOG` is not set or can't be parsed as a\n    /// standard log level.\n    ///\n    /// This must be called after [`with_level`]. If called before\n    /// [`with_level`], it will have no effect.\n    ///\n    /// [`with_level`]: #method.with_level\n    #[must_use = \"You must call init() to begin logging\"]\n    pub fn env(mut self) -> SimpleLogger {\n        self.default_level = std::env::var(\"RUST_LOG\")\n            .ok()\n            .as_deref()\n            .map(log::LevelFilter::from_str)\n            .and_then(Result::ok)\n            .unwrap_or(self.default_level);\n\n        self\n    }\n\n    /// Set the 'default' log level.\n    ///\n    /// You can override the default level for specific modules and their sub-modules using [`with_module_level`]\n    ///\n    /// This must be called before [`env`]. If called after [`env`], it will override the value loaded from the environment.\n    ///\n    /// [`env`]: #method.env\n    /// [`with_module_level`]: #method.with_module_level\n    #[must_use = \"You must call init() to begin logging\"]\n    pub fn with_level(mut self, level: LevelFilter) -> SimpleLogger {\n        self.default_level = level;\n        self\n    }\n\n    /// Override the log level for some specific modules.\n    ///\n    /// This sets the log level of a specific module and all its sub-modules.\n    /// When both the level for a parent module as well as a child module are set,\n    /// the more specific value is taken. If the log level for the same module is\n    /// specified twice, the resulting log level is implementation defined.\n    ///\n    /// # Examples\n    ///\n    /// Silence an overly verbose crate:\n    ///\n    /// ```no_run\n    /// use simple_logger::SimpleLogger;\n    /// use log::LevelFilter;\n    ///\n    /// SimpleLogger::new().with_module_level(\"chatty_dependency\", LevelFilter::Warn).init().unwrap();\n    /// ```\n    ///\n    /// Disable logging for all dependencies:\n    ///\n    /// ```no_run\n    /// use simple_logger::SimpleLogger;\n    /// use log::LevelFilter;\n    ///\n    /// SimpleLogger::new()\n    ///     .with_level(LevelFilter::Off)\n    ///     .with_module_level(\"my_crate\", LevelFilter::Info)\n    ///     .init()\n    ///     .unwrap();\n    /// ```\n    //\n    // This method *must* sort `module_levels` for the [`enabled`](#method.enabled) method to work correctly.\n    #[must_use = \"You must call init() to begin logging\"]\n    pub fn with_module_level(mut self, target: &str, level: LevelFilter) -> SimpleLogger {\n        self.module_levels.push((target.to_string(), level));\n        self.module_levels\n            .sort_by_key(|(name, _level)| name.len().wrapping_neg());\n        self\n    }\n\n    /// Override the log level for specific targets.\n    // This method *must* sort `module_levels` for the [`enabled`](#method.enabled) method to work correctly.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[deprecated(\n        since = \"1.11.0\",\n        note = \"Use [`with_module_level`](#method.with_module_level) instead. Will be removed in version 2.0.0.\"\n    )]\n    pub fn with_target_levels(mut self, target_levels: HashMap<String, LevelFilter>) -> SimpleLogger {\n        self.module_levels = target_levels.into_iter().collect();\n        self.module_levels\n            .sort_by_key(|(name, _level)| name.len().wrapping_neg());\n        self\n    }\n\n    /// Control whether thread names (and IDs) are printed or not.\n    ///\n    /// This method is only available if the `threads` feature is enabled.\n    /// Thread names are disabled by default.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"threads\")]\n    pub fn with_threads(mut self, threads: bool) -> SimpleLogger {\n        self.threads = threads;\n        self\n    }\n\n    /// Control whether timestamps are printed or not.\n    ///\n    /// Timestamps will be displayed in the local timezone.\n    ///\n    /// This method is only available if the `timestamps` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    #[deprecated(\n        since = \"1.16.0\",\n        note = \"Use [`with_local_timestamps`] or [`with_utc_timestamps`] instead. Will be removed in version 2.0.0.\"\n    )]\n    pub fn with_timestamps(mut self, timestamps: bool) -> SimpleLogger {\n        if timestamps {\n            self.timestamps = Timestamps::Local\n        } else {\n            self.timestamps = Timestamps::None\n        }\n        self\n    }\n\n    /// Control the format used for timestamps.\n    ///\n    /// Without this, a default format is used depending on the timestamps type.\n    ///\n    /// The syntax for the format_description macro can be found in the\n    /// [`time` crate book](https://time-rs.github.io/book/api/format-description.html).\n    ///\n    /// ```\n    /// simple_logger::SimpleLogger::new()\n    ///  .with_level(log::LevelFilter::Debug)\n    ///  .env()\n    ///  .with_timestamp_format(time::macros::format_description!(\"[year]-[month]-[day] [hour]:[minute]:[second]\"))\n    ///  .init()\n    ///  .unwrap();\n    /// ```\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    pub fn with_timestamp_format(mut self, format: &'static [FormatItem<'static>]) -> SimpleLogger {\n        self.timestamps_format = Some(format);\n        self\n    }\n\n    /// Don't display any timestamps.\n    ///\n    /// This method is only available if the `timestamps` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    pub fn without_timestamps(mut self) -> SimpleLogger {\n        self.timestamps = Timestamps::None;\n        self\n    }\n\n    /// Display timestamps using the local timezone.\n    ///\n    /// This method is only available if the `timestamps` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    pub fn with_local_timestamps(mut self) -> SimpleLogger {\n        self.timestamps = Timestamps::Local;\n        self\n    }\n\n    /// Display timestamps using UTC.\n    ///\n    /// This method is only available if the `timestamps` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    pub fn with_utc_timestamps(mut self) -> SimpleLogger {\n        self.timestamps = Timestamps::Utc;\n        self\n    }\n\n    /// Display timestamps using a static UTC offset.\n    ///\n    /// This method is only available if the `timestamps` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"timestamps\")]\n    pub fn with_utc_offset(mut self, offset: UtcOffset) -> SimpleLogger {\n        self.timestamps = Timestamps::UtcOffset(offset);\n        self\n    }\n\n    /// Control whether messages are colored or not.\n    ///\n    /// This method is only available if the `colored` feature is enabled.\n    #[must_use = \"You must call init() to begin logging\"]\n    #[cfg(feature = \"colors\")]\n    pub fn with_colors(mut self, colors: bool) -> SimpleLogger {\n        self.colors = colors;\n        self\n    }\n\n    /// Configure the logger\n    pub fn max_level(&self) -> LevelFilter {\n        let max_level = self.module_levels.iter().map(|(_name, level)| level).copied().max();\n        max_level\n            .map(|lvl| lvl.max(self.default_level))\n            .unwrap_or(self.default_level)\n    }\n\n    /// 'Init' the actual logger and instantiate it,\n    /// this method MUST be called in order for the logger to be effective.\n    pub fn init(self) -> Result<(), SetLoggerError> {\n        #[cfg(all(windows, feature = \"colored\"))]\n        set_up_windows_color_terminal();\n\n        #[cfg(all(feature = \"colored\", feature = \"stderr\"))]\n        use_stderr_for_colors();\n\n        log::set_max_level(self.max_level());\n        log::set_boxed_logger(Box::new(self))\n    }\n}\n\nimpl Default for SimpleLogger {\n    /// See [this](struct.SimpleLogger.html#method.new)\n    fn default() -> Self {\n        SimpleLogger::new()\n    }\n}\n\nimpl Log for SimpleLogger {\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        &metadata.level().to_level_filter()\n            <= self\n                .module_levels\n                .iter()\n                /* At this point the Vec is already sorted so that we can simply take\n                 * the first match\n                 */\n                .find(|(name, _level)| metadata.target().starts_with(name))\n                .map(|(_name, level)| level)\n                .unwrap_or(&self.default_level)\n    }\n\n    fn log(&self, record: &Record) {\n        if self.enabled(record.metadata()) {\n            let level_string = {\n                #[cfg(feature = \"colors\")]\n                {\n                    if self.colors {\n                        match record.level() {\n                            Level::Error => format!(\"{:<5}\", record.level().to_string()).red().to_string(),\n                            Level::Warn => format!(\"{:<5}\", record.level().to_string()).yellow().to_string(),\n                            Level::Info => format!(\"{:<5}\", record.level().to_string()).cyan().to_string(),\n                            Level::Debug => format!(\"{:<5}\", record.level().to_string()).purple().to_string(),\n                            Level::Trace => format!(\"{:<5}\", record.level().to_string()).normal().to_string(),\n                        }\n                    } else {\n                        format!(\"{:<5}\", record.level().to_string())\n                    }\n                }\n                #[cfg(not(feature = \"colors\"))]\n                {\n                    format!(\"{:<5}\", record.level().to_string())\n                }\n            };\n\n            let target = if !record.target().is_empty() {\n                record.target()\n            } else {\n                record.module_path().unwrap_or_default()\n            };\n\n            let thread = {\n                #[cfg(feature = \"threads\")]\n                if self.threads {\n                    let thread = std::thread::current();\n\n                    format!(\"@{}\", {\n                        #[cfg(feature = \"nightly\")]\n                        {\n                            thread.name().unwrap_or(&thread.id().as_u64().to_string())\n                        }\n\n                        #[cfg(not(feature = \"nightly\"))]\n                        {\n                            thread.name().unwrap_or(\"?\")\n                        }\n                    })\n                } else {\n                    \"\".to_string()\n                }\n\n                #[cfg(not(feature = \"threads\"))]\n                \"\"\n            };\n\n            let timestamp = {\n                #[cfg(feature = \"timestamps\")]\n                match self.timestamps {\n                    Timestamps::None => \"\".to_string(),\n                    Timestamps::Local => format!(\n                        \"{} \",\n                        OffsetDateTime::now_local()\n                            .expect(concat!(\n                                \"Could not determine the UTC offset on this system. \",\n                                \"Consider displaying UTC time instead. \",\n                                \"Possible causes are that the time crate does not implement \\\"local_offset_at\\\" \",\n                                \"on your system, or that you are running in a multi-threaded environment and \",\n                                \"the time crate is returning \\\"None\\\" from \\\"local_offset_at\\\" to avoid unsafe \",\n                                \"behaviour. See the time crate's documentation for more information. \",\n                                \"(https://time-rs.github.io/internal-api/time/index.html#feature-flags)\"\n                            ))\n                            .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET))\n                            .unwrap()\n                    ),\n                    Timestamps::Utc => format!(\n                        \"{} \",\n                        OffsetDateTime::now_utc()\n                            .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_UTC))\n                            .unwrap()\n                    ),\n                    Timestamps::UtcOffset(offset) => format!(\n                        \"{} \",\n                        OffsetDateTime::now_utc()\n                            .to_offset(offset)\n                            .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET))\n                            .unwrap()\n                    ),\n                }\n\n                #[cfg(not(feature = \"timestamps\"))]\n                \"\"\n            };\n\n            let message = format!(\"{}{} [{}{}] {}\", timestamp, level_string, target, thread, record.args());\n\n            #[cfg(not(feature = \"stderr\"))]\n            println!(\"{}\", message);\n\n            #[cfg(feature = \"stderr\")]\n            eprintln!(\"{}\", message);\n        }\n    }\n\n    fn flush(&self) {}\n}\n\n/// Configure the console to display colours.\n///\n/// This is only needed on Windows when using the 'colors' feature.\n/// It doesn't currently handle combining the 'colors' and 'stderr' features.\n#[cfg(all(windows, feature = \"colors\"))]\npub fn set_up_windows_color_terminal() {\n    use std::io::{stdout, IsTerminal};\n\n    if stdout().is_terminal() {\n        unsafe {\n            use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;\n            use windows_sys::Win32::System::Console::{\n                GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING,\n                STD_OUTPUT_HANDLE,\n            };\n\n            let stdout = GetStdHandle(STD_OUTPUT_HANDLE);\n\n            if stdout == INVALID_HANDLE_VALUE {\n                return;\n            }\n\n            let mut mode: CONSOLE_MODE = 0;\n\n            if GetConsoleMode(stdout, &mut mode) == 0 {\n                return;\n            }\n\n            SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);\n        }\n    }\n}\n\n/// The colored crate will disable colors when STDOUT is not a terminal. This method overrides this\n/// behaviour to check the status of STDERR instead.\n#[cfg(all(feature = \"colored\", feature = \"stderr\"))]\nfn use_stderr_for_colors() {\n    use std::io::{stderr, IsTerminal};\n\n    colored::control::set_override(stderr().is_terminal());\n}\n\n/// Initialise the logger with its default configuration.\n///\n/// Log messages will not be filtered.\n/// The `RUST_LOG` environment variable is not used.\npub fn init() -> Result<(), SetLoggerError> {\n    SimpleLogger::new().init()\n}\n\n/// Initialise the logger with its default configuration.\n///\n/// Log messages will not be filtered.\n/// The `RUST_LOG` environment variable is not used.\n///\n/// This function is only available if the `timestamps` feature is enabled.\n#[cfg(feature = \"timestamps\")]\npub fn init_utc() -> Result<(), SetLoggerError> {\n    SimpleLogger::new().with_utc_timestamps().init()\n}\n\n/// Initialise the logger with the `RUST_LOG` environment variable.\n///\n/// Log messages will be filtered based on the `RUST_LOG` environment variable.\n///\n/// This will use the default level ([`LevelFilter::Trace`]) if the `RUST_LOG`\n/// environment variable is not set or can't be parsed as a standard log level\n/// (see [`LevelFilter::from_str`] and [`log::LOG_LEVEL_NAMES`] for valid\n/// names).\npub fn init_with_env() -> Result<(), SetLoggerError> {\n    SimpleLogger::new().env().init()\n}\n\n/// Initialise the logger with a specific log level.\n///\n/// Log messages below the given [`Level`] will be filtered.\n/// The `RUST_LOG` environment variable is not used.\npub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {\n    SimpleLogger::new().with_level(level.to_level_filter()).init()\n}\n\n/// Use [`init_with_env`] instead.\n///\n/// This does the same as [`init_with_env`] but unwraps the result.\n#[deprecated(\n    since = \"1.12.0\",\n    note = \"Use [`init_with_env`] instead, which does not unwrap the result. Will be removed in version 2.0.0.\"\n)]\npub fn init_by_env() {\n    init_with_env().unwrap()\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_module_levels_allowlist() {\n        let logger = SimpleLogger::new()\n            .with_level(LevelFilter::Off)\n            .with_module_level(\"my_crate\", LevelFilter::Info);\n\n        assert!(logger.enabled(&create_log(\"my_crate\", Level::Info)));\n        assert!(logger.enabled(&create_log(\"my_crate::module\", Level::Info)));\n        assert!(!logger.enabled(&create_log(\"my_crate::module\", Level::Debug)));\n        assert!(!logger.enabled(&create_log(\"not_my_crate\", Level::Debug)));\n        assert!(!logger.enabled(&create_log(\"not_my_crate::module\", Level::Error)));\n    }\n\n    #[test]\n    fn test_module_levels_denylist() {\n        let logger = SimpleLogger::new()\n            .with_level(LevelFilter::Debug)\n            .with_module_level(\"my_crate\", LevelFilter::Trace)\n            .with_module_level(\"chatty_dependency\", LevelFilter::Info);\n\n        assert!(logger.enabled(&create_log(\"my_crate\", Level::Info)));\n        assert!(logger.enabled(&create_log(\"my_crate\", Level::Trace)));\n        assert!(logger.enabled(&create_log(\"my_crate::module\", Level::Info)));\n        assert!(logger.enabled(&create_log(\"my_crate::module\", Level::Trace)));\n        assert!(logger.enabled(&create_log(\"not_my_crate\", Level::Debug)));\n        assert!(!logger.enabled(&create_log(\"not_my_crate::module\", Level::Trace)));\n        assert!(logger.enabled(&create_log(\"chatty_dependency\", Level::Info)));\n        assert!(!logger.enabled(&create_log(\"chatty_dependency\", Level::Debug)));\n        assert!(!logger.enabled(&create_log(\"chatty_dependency::module\", Level::Debug)));\n        assert!(logger.enabled(&create_log(\"chatty_dependency::module\", Level::Warn)));\n    }\n\n    /// Test that enabled() looks for the most specific target.\n    #[test]\n    fn test_module_levels() {\n        let logger = SimpleLogger::new()\n            .with_level(LevelFilter::Off)\n            .with_module_level(\"a\", LevelFilter::Off)\n            .with_module_level(\"a::b::c\", LevelFilter::Off)\n            .with_module_level(\"a::b\", LevelFilter::Info);\n\n        assert_eq!(logger.enabled(&create_log(\"a\", Level::Info)), false);\n        assert_eq!(logger.enabled(&create_log(\"a::b\", Level::Info)), true);\n        assert_eq!(logger.enabled(&create_log(\"a::b::c\", Level::Info)), false);\n    }\n\n    #[test]\n    fn test_max_level() {\n        let builder = SimpleLogger::new();\n        assert_eq!(builder.max_level(), LevelFilter::Trace);\n    }\n\n    #[test]\n    #[cfg(feature = \"timestamps\")]\n    fn test_timestamps_defaults() {\n        let builder = SimpleLogger::new();\n        assert!(builder.timestamps == Timestamps::Utc);\n    }\n\n    #[test]\n    #[cfg(feature = \"timestamps\")]\n    #[allow(deprecated)]\n    fn test_with_timestamps() {\n        let builder = SimpleLogger::new().with_timestamps(false);\n        assert!(builder.timestamps == Timestamps::None);\n    }\n\n    #[test]\n    #[cfg(feature = \"timestamps\")]\n    fn test_with_utc_timestamps() {\n        let builder = SimpleLogger::new().with_utc_timestamps();\n        assert!(builder.timestamps == Timestamps::Utc);\n    }\n\n    #[test]\n    #[cfg(feature = \"timestamps\")]\n    fn test_with_local_timestamps() {\n        let builder = SimpleLogger::new().with_local_timestamps();\n        assert!(builder.timestamps == Timestamps::Local);\n    }\n\n    #[test]\n    #[cfg(feature = \"timestamps\")]\n    #[allow(deprecated)]\n    fn test_with_timestamps_format() {\n        let builder =\n            SimpleLogger::new().with_timestamp_format(time::macros::format_description!(\"[hour]:[minute]:[second]\"));\n        assert!(builder.timestamps_format.is_some());\n    }\n\n    #[test]\n    #[cfg(feature = \"colored\")]\n    fn test_with_colors() {\n        let mut builder = SimpleLogger::new();\n        assert!(builder.colors == true);\n\n        builder = builder.with_colors(false);\n        assert!(builder.colors == false);\n    }\n\n    /// > And, without sorting, this would lead to all serde_json logs being treated as if they were configured to\n    /// > Error level instead of Trace (since to determine the logging level for target, the code finds first match in\n    /// > module_levels by a string prefix).\n    #[test]\n    fn test_issue_90() {\n        let logger = SimpleLogger::new()\n            .with_level(LevelFilter::Off)\n            .with_module_level(\"serde\", LevelFilter::Error)\n            .with_module_level(\"serde_json\", LevelFilter::Trace);\n\n        assert_eq!(logger.enabled(&create_log(\"serde\", Level::Trace)), false);\n        assert_eq!(logger.enabled(&create_log(\"serde_json\", Level::Trace)), true);\n    }\n\n    fn create_log(name: &str, level: Level) -> Metadata<'_> {\n        let mut builder = Metadata::builder();\n        builder.level(level);\n        builder.target(name);\n        builder.build()\n    }\n}\n"
  }
]