main 5f64b678d25a cached
21 files
37.8 KB
9.3k tokens
61 symbols
1 requests
Download .txt
Repository: borntyping/rust-simple_logger
Branch: main
Commit: 5f64b678d25a
Files: 21
Total size: 37.8 KB

Directory structure:
gitextract_ieyaasaz/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── colors.rs
│   ├── flush.rs
│   ├── init.rs
│   ├── init_with_env.rs
│   ├── init_with_level.rs
│   ├── init_with_target_level.rs
│   ├── stderr.rs
│   ├── threads.rs
│   ├── timestamps_format.rs
│   ├── timestamps_local.rs
│   ├── timestamps_none.rs
│   ├── timestamps_utc.rs
│   ├── timestamps_utc_offset.rs
│   └── wrap.rs
├── rustfmt.toml
└── src/
    └── lib.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
---
name: ci

on:
  pull_request:
  push:
    branches:
      - main
  release:
    types:
      - created

env:
  # Just a reassurance to mitigate sudden network connection problems
  CARGO_NET_RETRY: 10
  RUSTUP_MAX_RETRIES: 10

  CARGO_INCREMENTAL: 0
  RUST_BACKTRACE: full

  # We don't need any debug symbols on ci, this also speeds up builds a bunch
  RUSTFLAGS: --deny warnings -Cdebuginfo=0
  RUSTDOCFLAGS: --deny warnings

jobs:
  rust-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          profile: minimal
          components: rustfmt, clippy

      - run: cargo clippy --workspace
      - run: cargo fmt --all -- --check

  rust-test:
    runs-on: ${{ matrix.os }}

    continue-on-error: ${{ matrix.toolchain != 'stable' }}

    strategy:
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
          - macos-latest
        toolchain:
          - stable
        features:
          - "colors"
          - "threads"
          - "timestamps"
          - "stderr"
        exclude:
          - os: macos-latest
            toolchain: stable
            features: "timestamps"
        include:
          - os: ubuntu-latest
            toolchain: beta
            features: "colors,threads,timestamps"
          - os: ubuntu-latest
            toolchain: nightly
            features: "colors,threads,timestamps,nightly"

    steps:
      - uses: actions/checkout@v5
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ matrix.toolchain }}
          profile: minimal
      - run: cargo +${{ matrix.toolchain }} build --no-default-features --features ${{ matrix.features }}
      - run: cargo +${{ matrix.toolchain }} test --no-default-features --features ${{ matrix.features }}

  rust-publish-crates:
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    needs:
      - rust-lint
      - rust-test
    environment: release
    permissions:
      id-token: write
    steps:
      - id: checkout
        uses: actions/checkout@v5
      - id: auth
        uses: rust-lang/crates-io-auth-action@v1
      - id: publish
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: "${{ steps.auth.outputs.token }}"


================================================
FILE: .gitignore
================================================
target
Cargo.lock
.idea/


================================================
FILE: Cargo.toml
================================================
[package]
name = "simple_logger"
version = "5.2.0"
license = "MIT"
authors = ["Sam Clements <sam@borntyping.co.uk>"]
description = "A logger that prints all messages with a readable output format"
repository = "https://github.com/borntyping/rust-simple_logger"
edition = "2018"

[features]
default = ["colors", "timestamps"]
colors = ["colored"]
threads = []
timestamps = ["time"]
nightly = []
stderr = []

[dependencies]
log = { version = "^0.4.28", features = ["std"] }
time = { version = "^0.3.47", features = ["formatting", "local-offset", "macros"], optional = true }
colored = { version = "^3.0.0", optional = true }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "^0.61.2", features = ["Win32_System_Console", "Win32_Foundation"] }

[[example]]
name = "colors"
required-features = ["colors"]

[[example]]
name = "stderr"
required-features = ["colors", "stderr"]

[[example]]
name = "threads"
required-features = ["threads"]

[[example]]
name = "timestamps_local"
required-features = ["timestamps"]

[[example]]
name = "timestamps_none"
required-features = ["timestamps"]

[[example]]
name = "timestamps_utc"
required-features = ["timestamps"]

[[example]]
name = "timestamps_utc_offset"
required-features = ["timestamps"]

[[example]]
name = "timestamps_format"
required-features = ["timestamps"]


================================================
FILE: LICENSE
================================================
Copyright 2015-2021 Sam Clements

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.


================================================
FILE: README.md
================================================
# simple_logger [![](https://img.shields.io/github/tag/borntyping/rust-simple_logger.svg)](https://github.com/borntyping/rust-simple_logger/tags)

A logger that prints all messages with a readable output format.

The 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.

The 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. 

* [Source on GitHub](https://github.com/borntyping/rust-simple_logger)
* [Packages on Crates.io](https://crates.io/crates/simple_logger)
* [Documentation on Docs.rs](https://docs.rs/simple_logger)

Notices
-------

### Project status

I wrote the initial version of this library in 2015, and haven't written Rust professionally since then.
I consider this as close as I'll ever get to a "finished" project and don't plan on adding any more features to it.

It 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.
If 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.

### Breaking changes

- **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.

Usage
-----

```rust
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().init().unwrap();

    log::warn!("This is an example message.");
}
```

This outputs:

```txt
2022-01-19T17:27:07.013874956Z WARN [logging_example] This is an example message.
```

You can run the above example with:

```sh
cargo run --example init
```

### Optional features

The `colors` and `timestamps` features are enabled by default. You can remove these
features and their respective dependencies by disabling all features in your
`Cargo.toml`.

```toml
[dependencies.simple_logger]
default-features = false
```

To include the `timestamps` feature, but not the `colors` feature:

```toml
[dependencies.simple_logger]
default-features = false
features = ["timestamps"]
```

To include the `colors` feature, but not the `timestamps` feature:

```toml
[dependencies.simple_logger]
default-features = false
features = ["colors"]
```

To include thread metadata use the `threads` and `nightly` features:

```toml
[dependencies.simple_logger]
features = ["threads", "nightly"]
```

To direct logging output to `stderr` use the `stderr` feature:

```toml
[dependencies.simple_logger]
features = ["stderr"]
```

Multiple features can be combined.

```toml
[dependencies.simple_logger]
features = ["colors", "threads", "timestamps", "nightly", "stderr"]
```

### Wrapping with another logger

You might want to wrap this logger to do your own processing before handing events to a SimpleLogger instance. Instead
of calling `init()` which calls `log::set_max_level` and `log::set_boxed_logger`, you can call those functions directly
giving you the chance to wrap or adjust the logger. See [wrap.rs](examples/wrap.rs) for a more detailed example.

### Console setup

The `SimpleLogger.init()` function attempts to configure colours support as best it can in various situations:

- On Windows, it will enable colour output. _See `set_up_windows_color_terminal()`._
- When using the `colors` *and* `stderr` features, it will instruct the `colored` library to display colors if STDERR
  is a terminal (instead of checking if STDOUT is a terminal). _See `use_stderr_for_colors()`._

Licence
-------

`simple_logger` is licenced under the [MIT Licence](http://opensource.org/licenses/MIT).

Authors
-------

Written by [Sam Clements](sam@borntyping.co.uk).


================================================
FILE: examples/colors.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_colors(true).init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/flush.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().init().unwrap();

    log::warn!("This is an example message.");

    log::logger().flush();
}


================================================
FILE: examples/init.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/init_with_env.rs
================================================
//! Set the `RUST_LOG` environment variable and run this example to see the output change.
//!
//! Valid values are `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, and `TRACE`.
//!
//! ```shell
//! RUST_LOG=WARN cargo run --example init_with_env
//! ```
//!
//! It should also work if the environment variable is not set:
//!
//! ```shell
//! cargo run --example init_with_env
//! ```
use simple_logger;

fn main() {
    simple_logger::init_with_env().unwrap();

    log::trace!("This is an example message.");
    log::debug!("This is an example message.");
    log::info!("This is an example message.");
    log::warn!("This is an example message.");
    log::error!("This is an example message.");
}


================================================
FILE: examples/init_with_level.rs
================================================
use log::LevelFilter;
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_level(LevelFilter::Warn).init().unwrap();

    log::warn!("This will be logged.");
    log::info!("This will NOT be logged.");
}


================================================
FILE: examples/init_with_target_level.rs
================================================
use log::LevelFilter;
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new()
        .with_level(LevelFilter::Info)
        .with_module_level("init_with_target_level", LevelFilter::Off)
        .init()
        .unwrap();

    log::info!("This will NOT be logged. (Target disabled)");
}


================================================
FILE: examples/stderr.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_colors(true).init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/threads.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_threads(true).init().unwrap();

    log::info!("Main thread logs here.");

    // If the "nightly" feature is enabled, the output will include thread ids.
    for _ in 1..=5 {
        std::thread::spawn(|| {
            log::info!("Unnamed thread logs here.");
        })
        .join()
        .unwrap();
    }

    std::thread::Builder::new()
        .name("named_thread".to_string())
        .spawn(|| {
            log::info!("Named thread logs here.");
        })
        .unwrap()
        .join()
        .unwrap();
}


================================================
FILE: examples/timestamps_format.rs
================================================
use simple_logger::SimpleLogger;
use time::macros::format_description;

fn main() {
    SimpleLogger::new()
        .env()
        .with_timestamp_format(format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"))
        .init()
        .unwrap();

    log::warn!("This is an example message with custom timestamp format.");
}


================================================
FILE: examples/timestamps_local.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_local_timestamps().init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/timestamps_none.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().without_timestamps().init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/timestamps_utc.rs
================================================
use simple_logger::SimpleLogger;

fn main() {
    SimpleLogger::new().with_utc_timestamps().init().unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: examples/timestamps_utc_offset.rs
================================================
use simple_logger::SimpleLogger;
use time::UtcOffset;

fn main() {
    SimpleLogger::new()
        .with_utc_offset(UtcOffset::from_hms(14, 0, 0).unwrap())
        .init()
        .unwrap();

    log::warn!("This is an example message using a static UTC offset.");
    log::info!("Daylight savings or other timezone changes will not be respected.");
}


================================================
FILE: examples/wrap.rs
================================================
use log::{Log, Metadata, Record};
use simple_logger::SimpleLogger;

struct WrapperLogger {
    simple_logger: SimpleLogger,
}

impl Log for WrapperLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.simple_logger.enabled(metadata)
    }

    fn log(&self, record: &Record) {
        self.simple_logger.log(record)
    }

    fn flush(&self) {
        self.simple_logger.flush()
    }
}

fn main() {
    let simple_logger = SimpleLogger::new();
    log::set_max_level(simple_logger.max_level());

    let wrapper_logger = WrapperLogger { simple_logger };
    log::set_boxed_logger(Box::new(wrapper_logger)).unwrap();

    log::warn!("This is an example message.");
}


================================================
FILE: rustfmt.toml
================================================
max_width = 120


================================================
FILE: src/lib.rs
================================================
//! A logger that prints all messages with a simple, readable output format.
//!
//! Optional features include timestamps, colored output and logging to stderr.
//!
//! ```rust
//! 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:
//!
//! ```rust
//! simple_logger::init().unwrap();
//! ```
//!
//! Set the log level from the `RUST_LOG` environment variable:
//!
//! ```rust
//! simple_logger::init_with_env().unwrap();
//! ```
//!
//! Hardcode a default log level:
//!
//! ```rust
//! simple_logger::init_with_level(log::Level::Warn).unwrap();
//! ```

#![cfg_attr(feature = "nightly", feature(thread_id_value))]

#[cfg(feature = "colors")]
use colored::*;
use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
use std::{collections::HashMap, str::FromStr};
#[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.
    ///
    /// This must be sorted from most-specific to least-specific, so that [`enabled`](#method.enabled) can scan the
    /// vector for the first match to give us the desired log level for a module.
    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,
    #[cfg(feature = "timestamps")]
    timestamps_format: Option<&'static [FormatItem<'static>]>,

    /// Whether to use color output or not.
    ///
    /// This field is only available if the `color` feature is enabled.
    #[cfg(feature = "colors")]
    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 = "timestamps")]
            timestamps_format: None,

            #[cfg(feature = "colors")]
            colors: true,
        }
    }

    /// Initializes the global logger with log level read from `RUST_LOG` environment
    /// variable value. Deprecated in favor of `env()`.
    ///
    /// 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()
    }

    /// Enables the user to choose log level by setting `RUST_LOG=<level>`
    /// 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.
    ///
    /// This must be called after [`with_level`]. If called before
    /// [`with_level`], it will have no effect.
    ///
    /// [`with_level`]: #method.with_level
    #[must_use = "You must call init() to begin logging"]
    pub fn env(mut self) -> SimpleLogger {
        self.default_level = std::env::var("RUST_LOG")
            .ok()
            .as_deref()
            .map(log::LevelFilter::from_str)
            .and_then(Result::ok)
            .unwrap_or(self.default_level);

        self
    }

    /// Set the 'default' log level.
    ///
    /// You can override the default level for specific modules and their sub-modules using [`with_module_level`]
    ///
    /// This must be called before [`env`]. If called after [`env`], it will override the value loaded from the environment.
    ///
    /// [`env`]: #method.env
    /// [`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();
    /// ```
    //
    // This method *must* sort `module_levels` for the [`enabled`](#method.enabled) method to work correctly.
    #[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));
        self.module_levels
            .sort_by_key(|(name, _level)| name.len().wrapping_neg());
        self
    }

    /// Override the log level for specific targets.
    // This method *must* sort `module_levels` for the [`enabled`](#method.enabled) method to work correctly.
    #[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();
        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
    }

    /// Control the format used for timestamps.
    ///
    /// Without this, a default format is used depending on the timestamps type.
    ///
    /// The syntax for the format_description macro can be found in the
    /// [`time` crate book](https://time-rs.github.io/book/api/format-description.html).
    ///
    /// ```
    /// simple_logger::SimpleLogger::new()
    ///  .with_level(log::LevelFilter::Debug)
    ///  .env()
    ///  .with_timestamp_format(time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"))
    ///  .init()
    ///  .unwrap();
    /// ```
    #[must_use = "You must call init() to begin logging"]
    #[cfg(feature = "timestamps")]
    pub fn with_timestamp_format(mut self, format: &'static [FormatItem<'static>]) -> SimpleLogger {
        self.timestamps_format = Some(format);
        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 = "colors")]
    pub fn with_colors(mut self, colors: bool) -> SimpleLogger {
        self.colors = colors;
        self
    }

    /// Configure the logger
    pub fn max_level(&self) -> LevelFilter {
        let max_level = self.module_levels.iter().map(|(_name, level)| level).copied().max();
        max_level
            .map(|lvl| lvl.max(self.default_level))
            .unwrap_or(self.default_level)
    }

    /// 'Init' the actual logger and instantiate it,
    /// this method MUST be called in order for the logger to be effective.
    pub fn init(self) -> Result<(), SetLoggerError> {
        #[cfg(all(windows, feature = "colored"))]
        set_up_windows_color_terminal();

        #[cfg(all(feature = "colored", feature = "stderr"))]
        use_stderr_for_colors();

        log::set_max_level(self.max_level());
        log::set_boxed_logger(Box::new(self))
    }
}

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 = "colors")]
                {
                    if self.colors {
                        match record.level() {
                            Level::Error => format!("{:<5}", record.level().to_string()).red().to_string(),
                            Level::Warn => format!("{:<5}", record.level().to_string()).yellow().to_string(),
                            Level::Info => format!("{:<5}", record.level().to_string()).cyan().to_string(),
                            Level::Debug => format!("{:<5}", record.level().to_string()).purple().to_string(),
                            Level::Trace => format!("{:<5}", record.level().to_string()).normal().to_string(),
                        }
                    } else {
                        format!("{:<5}", record.level().to_string())
                    }
                }
                #[cfg(not(feature = "colors"))]
                {
                    format!("{:<5}", 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(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET))
                            .unwrap()
                    ),
                    Timestamps::Utc => format!(
                        "{} ",
                        OffsetDateTime::now_utc()
                            .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_UTC))
                            .unwrap()
                    ),
                    Timestamps::UtcOffset(offset) => format!(
                        "{} ",
                        OffsetDateTime::now_utc()
                            .to_offset(offset)
                            .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET))
                            .unwrap()
                    ),
                }

                #[cfg(not(feature = "timestamps"))]
                ""
            };

            let message = format!("{}{} [{}{}] {}", timestamp, level_string, target, thread, record.args());

            #[cfg(not(feature = "stderr"))]
            println!("{}", message);

            #[cfg(feature = "stderr")]
            eprintln!("{}", message);
        }
    }

    fn flush(&self) {}
}

/// Configure the console to display colours.
///
/// This is only needed on Windows when using the 'colors' feature.
/// It doesn't currently handle combining the 'colors' and 'stderr' features.
#[cfg(all(windows, feature = "colors"))]
pub fn set_up_windows_color_terminal() {
    use std::io::{stdout, IsTerminal};

    if stdout().is_terminal() {
        unsafe {
            use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
            use windows_sys::Win32::System::Console::{
                GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
                STD_OUTPUT_HANDLE,
            };

            let stdout = GetStdHandle(STD_OUTPUT_HANDLE);

            if stdout == INVALID_HANDLE_VALUE {
                return;
            }

            let mut mode: CONSOLE_MODE = 0;

            if GetConsoleMode(stdout, &mut mode) == 0 {
                return;
            }

            SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
        }
    }
}

/// The colored crate will disable colors when STDOUT is not a terminal. This method overrides this
/// behaviour to check the status of STDERR instead.
#[cfg(all(feature = "colored", feature = "stderr"))]
fn use_stderr_for_colors() {
    use std::io::{stderr, IsTerminal};

    colored::control::set_override(stderr().is_terminal());
}

/// Initialise the logger with its 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 its 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.
///
/// This will use the default level ([`LevelFilter::Trace`]) if the `RUST_LOG`
/// environment variable is not set or can't be parsed as a standard log level
/// (see [`LevelFilter::from_str`] and [`log::LOG_LEVEL_NAMES`] for valid
/// names).
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 that enabled() looks for the most specific target.
    #[test]
    fn test_module_levels() {
        let logger = SimpleLogger::new()
            .with_level(LevelFilter::Off)
            .with_module_level("a", LevelFilter::Off)
            .with_module_level("a::b::c", LevelFilter::Off)
            .with_module_level("a::b", LevelFilter::Info);

        assert_eq!(logger.enabled(&create_log("a", Level::Info)), false);
        assert_eq!(logger.enabled(&create_log("a::b", Level::Info)), true);
        assert_eq!(logger.enabled(&create_log("a::b::c", Level::Info)), false);
    }

    #[test]
    fn test_max_level() {
        let builder = SimpleLogger::new();
        assert_eq!(builder.max_level(), LevelFilter::Trace);
    }

    #[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 = "timestamps")]
    #[allow(deprecated)]
    fn test_with_timestamps_format() {
        let builder =
            SimpleLogger::new().with_timestamp_format(time::macros::format_description!("[hour]:[minute]:[second]"));
        assert!(builder.timestamps_format.is_some());
    }

    #[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);
    }

    /// > And, without sorting, this would lead to all serde_json logs being treated as if they were configured to
    /// > Error level instead of Trace (since to determine the logging level for target, the code finds first match in
    /// > module_levels by a string prefix).
    #[test]
    fn test_issue_90() {
        let logger = SimpleLogger::new()
            .with_level(LevelFilter::Off)
            .with_module_level("serde", LevelFilter::Error)
            .with_module_level("serde_json", LevelFilter::Trace);

        assert_eq!(logger.enabled(&create_log("serde", Level::Trace)), false);
        assert_eq!(logger.enabled(&create_log("serde_json", Level::Trace)), true);
    }

    fn create_log(name: &str, level: Level) -> Metadata<'_> {
        let mut builder = Metadata::builder();
        builder.level(level);
        builder.target(name);
        builder.build()
    }
}
Download .txt
gitextract_ieyaasaz/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── colors.rs
│   ├── flush.rs
│   ├── init.rs
│   ├── init_with_env.rs
│   ├── init_with_level.rs
│   ├── init_with_target_level.rs
│   ├── stderr.rs
│   ├── threads.rs
│   ├── timestamps_format.rs
│   ├── timestamps_local.rs
│   ├── timestamps_none.rs
│   ├── timestamps_utc.rs
│   ├── timestamps_utc_offset.rs
│   └── wrap.rs
├── rustfmt.toml
└── src/
    └── lib.rs
Download .txt
SYMBOL INDEX (61 symbols across 15 files)

FILE: examples/colors.rs
  function main (line 3) | fn main() {

FILE: examples/flush.rs
  function main (line 3) | fn main() {

FILE: examples/init.rs
  function main (line 3) | fn main() {

FILE: examples/init_with_env.rs
  function main (line 16) | fn main() {

FILE: examples/init_with_level.rs
  function main (line 4) | fn main() {

FILE: examples/init_with_target_level.rs
  function main (line 4) | fn main() {

FILE: examples/stderr.rs
  function main (line 3) | fn main() {

FILE: examples/threads.rs
  function main (line 3) | fn main() {

FILE: examples/timestamps_format.rs
  function main (line 4) | fn main() {

FILE: examples/timestamps_local.rs
  function main (line 3) | fn main() {

FILE: examples/timestamps_none.rs
  function main (line 3) | fn main() {

FILE: examples/timestamps_utc.rs
  function main (line 3) | fn main() {

FILE: examples/timestamps_utc_offset.rs
  function main (line 4) | fn main() {

FILE: examples/wrap.rs
  type WrapperLogger (line 4) | struct WrapperLogger {
  method enabled (line 9) | fn enabled(&self, metadata: &Metadata) -> bool {
  method log (line 13) | fn log(&self, record: &Record) {
  method flush (line 17) | fn flush(&self) {
  function main (line 22) | fn main() {

FILE: src/lib.rs
  constant TIMESTAMP_FORMAT_OFFSET (line 41) | const TIMESTAMP_FORMAT_OFFSET: &[FormatItem] = time::macros::format_desc...
  constant TIMESTAMP_FORMAT_UTC (line 46) | const TIMESTAMP_FORMAT_UTC: &[FormatItem] =
  type Timestamps (line 51) | enum Timestamps {
  type SimpleLogger (line 62) | pub struct SimpleLogger {
    method new (line 107) | pub fn new() -> SimpleLogger {
    method from_env (line 144) | pub fn from_env() -> SimpleLogger {
    method env (line 158) | pub fn env(mut self) -> SimpleLogger {
    method with_level (line 178) | pub fn with_level(mut self, level: LevelFilter) -> SimpleLogger {
    method with_module_level (line 216) | pub fn with_module_level(mut self, target: &str, level: LevelFilter) -...
    method with_target_levels (line 230) | pub fn with_target_levels(mut self, target_levels: HashMap<String, Lev...
    method with_threads (line 243) | pub fn with_threads(mut self, threads: bool) -> SimpleLogger {
    method with_timestamps (line 259) | pub fn with_timestamps(mut self, timestamps: bool) -> SimpleLogger {
    method with_timestamp_format (line 285) | pub fn with_timestamp_format(mut self, format: &'static [FormatItem<'s...
    method without_timestamps (line 295) | pub fn without_timestamps(mut self) -> SimpleLogger {
    method with_local_timestamps (line 305) | pub fn with_local_timestamps(mut self) -> SimpleLogger {
    method with_utc_timestamps (line 315) | pub fn with_utc_timestamps(mut self) -> SimpleLogger {
    method with_utc_offset (line 325) | pub fn with_utc_offset(mut self, offset: UtcOffset) -> SimpleLogger {
    method with_colors (line 335) | pub fn with_colors(mut self, colors: bool) -> SimpleLogger {
    method max_level (line 341) | pub fn max_level(&self) -> LevelFilter {
    method init (line 350) | pub fn init(self) -> Result<(), SetLoggerError> {
  method default (line 364) | fn default() -> Self {
  method enabled (line 370) | fn enabled(&self, metadata: &Metadata) -> bool {
  method log (line 383) | fn log(&self, record: &Record) {
  method flush (line 484) | fn flush(&self) {}
  function set_up_windows_color_terminal (line 492) | pub fn set_up_windows_color_terminal() {
  function use_stderr_for_colors (line 523) | fn use_stderr_for_colors() {
  function init (line 533) | pub fn init() -> Result<(), SetLoggerError> {
  function init_utc (line 544) | pub fn init_utc() -> Result<(), SetLoggerError> {
  function init_with_env (line 556) | pub fn init_with_env() -> Result<(), SetLoggerError> {
  function init_with_level (line 564) | pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
  function init_by_env (line 575) | pub fn init_by_env() {
  function test_module_levels_allowlist (line 584) | fn test_module_levels_allowlist() {
  function test_module_levels_denylist (line 597) | fn test_module_levels_denylist() {
  function test_module_levels (line 617) | fn test_module_levels() {
  function test_max_level (line 630) | fn test_max_level() {
  function test_timestamps_defaults (line 637) | fn test_timestamps_defaults() {
  function test_with_timestamps (line 645) | fn test_with_timestamps() {
  function test_with_utc_timestamps (line 652) | fn test_with_utc_timestamps() {
  function test_with_local_timestamps (line 659) | fn test_with_local_timestamps() {
  function test_with_timestamps_format (line 667) | fn test_with_timestamps_format() {
  function test_with_colors (line 675) | fn test_with_colors() {
  function test_issue_90 (line 687) | fn test_issue_90() {
  function create_log (line 697) | fn create_log(name: &str, level: Level) -> Metadata<'_> {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (41K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2316,
    "preview": "---\nname: ci\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n  release:\n    types:\n      - created\n\nenv:\n  # Jus"
  },
  {
    "path": ".gitignore",
    "chars": 25,
    "preview": "target\nCargo.lock\n.idea/\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 1321,
    "preview": "[package]\nname = \"simple_logger\"\nversion = \"5.2.0\"\nlicense = \"MIT\"\nauthors = [\"Sam Clements <sam@borntyping.co.uk>\"]\ndes"
  },
  {
    "path": "LICENSE",
    "chars": 1057,
    "preview": "Copyright 2015-2021 Sam Clements\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this s"
  },
  {
    "path": "README.md",
    "chars": 4099,
    "preview": "# simple_logger [![](https://img.shields.io/github/tag/borntyping/rust-simple_logger.svg)](https://github.com/borntyping"
  },
  {
    "path": "examples/colors.rs",
    "chars": 155,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_colors(true).init().unwrap();\n\n    log::warn!"
  },
  {
    "path": "examples/flush.rs",
    "chars": 165,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().init().unwrap();\n\n    log::warn!(\"This is an examp"
  },
  {
    "path": "examples/init.rs",
    "chars": 137,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().init().unwrap();\n\n    log::warn!(\"This is an examp"
  },
  {
    "path": "examples/init_with_env.rs",
    "chars": 696,
    "preview": "//! Set the `RUST_LOG` environment variable and run this example to see the output change.\n//!\n//! Valid values are `OFF"
  },
  {
    "path": "examples/init_with_level.rs",
    "chars": 226,
    "preview": "use log::LevelFilter;\nuse simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_level(LevelFilter::Warn"
  },
  {
    "path": "examples/init_with_target_level.rs",
    "chars": 302,
    "preview": "use log::LevelFilter;\nuse simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new()\n        .with_level(LevelFil"
  },
  {
    "path": "examples/stderr.rs",
    "chars": 155,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_colors(true).init().unwrap();\n\n    log::warn!"
  },
  {
    "path": "examples/threads.rs",
    "chars": 600,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_threads(true).init().unwrap();\n\n    log::info"
  },
  {
    "path": "examples/timestamps_format.rs",
    "chars": 338,
    "preview": "use simple_logger::SimpleLogger;\nuse time::macros::format_description;\n\nfn main() {\n    SimpleLogger::new()\n        .env"
  },
  {
    "path": "examples/timestamps_local.rs",
    "chars": 161,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_local_timestamps().init().unwrap();\n\n    log:"
  },
  {
    "path": "examples/timestamps_none.rs",
    "chars": 158,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().without_timestamps().init().unwrap();\n\n    log::wa"
  },
  {
    "path": "examples/timestamps_utc.rs",
    "chars": 159,
    "preview": "use simple_logger::SimpleLogger;\n\nfn main() {\n    SimpleLogger::new().with_utc_timestamps().init().unwrap();\n\n    log::w"
  },
  {
    "path": "examples/timestamps_utc_offset.rs",
    "chars": 352,
    "preview": "use simple_logger::SimpleLogger;\nuse time::UtcOffset;\n\nfn main() {\n    SimpleLogger::new()\n        .with_utc_offset(UtcO"
  },
  {
    "path": "examples/wrap.rs",
    "chars": 689,
    "preview": "use log::{Log, Metadata, Record};\nuse simple_logger::SimpleLogger;\n\nstruct WrapperLogger {\n    simple_logger: SimpleLogg"
  },
  {
    "path": "rustfmt.toml",
    "chars": 16,
    "preview": "max_width = 120\n"
  },
  {
    "path": "src/lib.rs",
    "chars": 25578,
    "preview": "//! A logger that prints all messages with a simple, readable output format.\n//!\n//! Optional features include timestamp"
  }
]

About this extraction

This page contains the full source code of the borntyping/rust-simple_logger GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (37.8 KB), approximately 9.3k tokens, and a symbol index with 61 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!