diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..146c2e9 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,10 @@ +msrv = "1.60.0" # MSRV +warn-on-all-wildcard-imports = true +disallowed-methods = [ + { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, + { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, + { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, + { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, + { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, + { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, +] diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5cde165..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 -updates: -- package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..3fdb4a4 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,107 @@ +{ + schedule: [ + 'before 5am on the first day of the month', + ], + semanticCommits: 'enabled', + configMigration: true, + dependencyDashboard: true, + customManagers: [ + { + customType: 'regex', + fileMatch: [ + '^rust-toolchain\\.toml$', + 'Cargo.toml$', + 'clippy.toml$', + '\\.clippy.toml$', + '^\\.github/workflows/ci.yml$', + '^\\.github/workflows/rust-next.yml$', + ], + matchStrings: [ + 'MSRV.*?(?\\d+\\.\\d+(\\.\\d+)?)', + '(?\\d+\\.\\d+(\\.\\d+)?).*?MSRV', + ], + depNameTemplate: 'rust', + packageNameTemplate: 'rust-lang/rust', + datasourceTemplate: 'github-releases', + }, + ], + packageRules: [ + { + commitMessageTopic: 'MSRV', + matchManagers: [ + 'custom.regex', + ], + matchPackageNames: [ + 'rust', + ], + minimumReleaseAge: '840 days', // 20 releases * 6 weeks per release * 7 days per week + internalChecksFilter: 'strict', + extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version + schedule: [ + '* * * * *', + ], + }, + // Goals: + // - Keep version reqs low, ignoring compatible normal/build dependencies + // - Take advantage of latest dev-dependencies + // - Rollup safe upgrades to reduce CI runner load + // - Help keep number of versions down by always using latest breaking change + // - Have lockfile and manifest in-sync + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + enabled: false, + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + enabled: false, + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + automerge: true, + groupName: 'compatible (dev)', + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + automerge: true, + groupName: 'compatible (dev)', + }, + ], +} diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..6c1efa0 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,37 @@ +# These settings are synced to GitHub by https://probot.github.io/apps/settings/ + +repository: + description: "A logging implementation for `log` which is configured via an environment variable." + homepage: "https://docs.rs/env_logger" + topics: "rust logging" + has_issues: true + has_projects: false + has_wiki: false + has_downloads: false + default_branch: main + + # Preference: people do clean commits + allow_merge_commit: true + # Backup in case we need to clean up commits + allow_squash_merge: true + # Not really needed + allow_rebase_merge: false + + allow_auto_merge: true + delete_branch_on_merge: true + + squash_merge_commit_title: "PR_TITLE" + squash_merge_commit_message: "PR_BODY" + merge_commit_message: "PR_BODY" + +branches: + - name: main + protection: + required_pull_request_reviews: null + required_conversation_resolution: true + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: false + contexts: ["CI", "Lint Commits", "Spell Check with Typos"] + enforce_admins: false + restrictions: null diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..ccee1fe --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,49 @@ +name: Security audit + +permissions: + contents: read + +on: + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + push: + branches: + - main + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + security_audit: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + cargo_deny: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - bans licenses sources + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check ${{ matrix.checks }} + rust-version: stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2e8979..de41ed3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,114 +1,138 @@ -name: Continuous Integration - -on: - pull_request: - paths: - - "**.rs" - - "Cargo.toml" - - "Cargo.lock" - -jobs: - fmt: - name: Source formatting check - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - name: Check formatting - uses: actions-rs/cargo@v1 - with: - command: fmt - args: -- --check - - check: - name: Compilation check - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - rust: - - stable - - beta - - nightly - - 1.41.0 - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: check - - clippy: - name: Lint check - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: clippy - - - name: Run lints - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings - - ci-crate: - name: CI crate check - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - - - name: Run ci crate - uses: actions-rs/cargo@v1 - with: - command: run - args: -p ci - - crate-example: - name: Crate example check - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - - - name: Run crate example - uses: actions-rs/cargo@v1 - with: - command: run - args: --example default +name: CI + +permissions: + contents: read + +on: + pull_request: + push: + branches: + - main + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + ci: + permissions: + contents: none + name: CI + needs: [test, msrv, docs, rustfmt, clippy] + runs-on: ubuntu-latest + steps: + - name: Done + run: exit 0 + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable"] + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack + - name: Build + run: cargo test --no-run --workspace --all-features + - name: Test + run: cargo hack test --workspace --feature-powerset + - name: Run crate example + run: cargo run --example default + msrv: + name: "Check MSRV: 1.60.0" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.60" # MSRV + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack + - name: Check + run: cargo hack check --workspace --all-targets --feature-powerset + lockfile: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: "Is lockfile updated?" + run: cargo fetch --locked + docs: + name: Docs + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Check documentation + env: + RUSTDOCFLAGS: -D warnings + run: cargo doc --workspace --all-features --no-deps --document-private-items + rustfmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + # Not MSRV because its harder to jump between versions and people are + # more likely to have stable + toolchain: stable + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + security-events: write # to upload sarif results + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.60" # MSRV + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Install SARIF tools + run: cargo install clippy-sarif --version 0.3.4 --locked # Held back due to msrv + - name: Install SARIF tools + run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv + - name: Check + run: > + cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated + | clippy-sarif + | tee clippy-results.sarif + | sarif-fmt + continue-on-error: true + - name: Upload + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: clippy-results.sarif + wait-for-processing: true + - name: Report status + run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml new file mode 100644 index 0000000..0462558 --- /dev/null +++ b/.github/workflows/committed.yml @@ -0,0 +1,24 @@ +# Not run as part of pre-commit checks because they don't handle sending the correct commit +# range to `committed` +name: Lint Commits +on: [pull_request] + +permissions: + contents: read + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + committed: + name: Lint Commits + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Lint Commits + uses: crate-ci/committed@master diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index a135b86..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Continuous Integration - Docs - -on: - push: - branches: - - main - paths: - - "**.rs" - - "Cargo.toml" - - "Cargo.lock" - workflow_dispatch: - -jobs: - docs: - name: Generate crate documentation - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - - - name: Generate documentation - uses: actions-rs/cargo@v1 - env: - RUSTDOCFLAGS: "--enable-index-page -Zunstable-options" - with: - command: doc - args: --no-deps - - - name: Deploy documentation - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..9551407 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,23 @@ +name: pre-commit + +permissions: {} # none + +on: + pull_request: + push: + branches: [main] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + pre-commit: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml new file mode 100644 index 0000000..e9ae9a8 --- /dev/null +++ b/.github/workflows/rust-next.yml @@ -0,0 +1,61 @@ +name: rust-next + +permissions: + contents: read + +on: + schedule: + - cron: '5 5 5 * *' + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable", "beta"] + include: + - os: ubuntu-latest + rust: "nightly" + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack + - name: Build + run: cargo test --no-run --workspace --all-features + - name: Test + run: cargo hack test --workspace --feature-powerset + - name: Run crate example + run: cargo run --example default + latest: + name: "Check latest dependencies" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack + - name: Update dependencues + run: cargo update + - name: Build + run: cargo test --no-run --workspace --all-features + - name: Test + run: cargo hack test --workspace --feature-powerset + - name: Run crate example + run: cargo run --example default diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 0000000..12f7585 --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,21 @@ +name: Spelling + +permissions: + contents: read + +on: [pull_request] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + spelling: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Spell Check Repo + uses: crate-ci/typos@master diff --git a/.gitignore b/.gitignore index a01f99d..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -target/ -Cargo.lock -.emacs* -*~ +target diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..68db968 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + stages: [commit] + - id: check-json + stages: [commit] + - id: check-toml + stages: [commit] + - id: check-merge-conflict + stages: [commit] + - id: check-case-conflict + stages: [commit] + - id: detect-private-key + stages: [commit] + - repo: https://github.com/crate-ci/typos + rev: v1.16.20 + hooks: + - id: typos + stages: [commit] + - repo: https://github.com/crate-ci/committed + rev: v1.0.20 + hooks: + - id: committed + stages: [commit-msg] diff --git a/BUILD.gn b/BUILD.gn index 8e22ec0..b6f2620 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -19,20 +19,19 @@ ohos_cargo_crate("lib") { crate_root = "src/lib.rs" sources = ["src/lib.rs"] - edition = "2018" - cargo_pkg_version = "0.9.3" + edition = "2021" + cargo_pkg_version = "0.10.2" cargo_pkg_name = "env_logger" deps = [ - "//third_party/rust/crates/atty:lib", + "//third_party/rust/crates/is-terminal:lib", "//third_party/rust/crates/humantime:lib", "//third_party/rust/crates/log:lib", "//third_party/rust/crates/regex:lib", "//third_party/rust/crates/termcolor:lib", ] features = [ - "atty", + "auto-color", "humantime", "regex", - "termcolor", ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 266e24a..6b911e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,70 @@ -Changes to this crate are tracked via [GitHub Releases][releases]. +# Change Log +All notable changes to this project will be documented in this file. -[releases]: https://github.com/env-logger-rs/env_logger/releases +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [Unreleased] - ReleaseDate + +## [0.10.2] - 2024-01-18 + +### Performance + +- Avoid extra UTF-8 validation performed in some cases + +### Fixes + +- Ensure custom pipes/stdout get flushed +- Don't panic on broken pipes when `color` is disabled + +## [0.10.1] - 2023-11-10 + +### Performance + +- Avoid hashing directives and accessing RNG on startup + +### Documentation + +- Tweak `RUST_LOG` documentation + +## [0.10.0] - 2022-11-24 + +MSRV changed to 1.60 to hide optional dependencies + +### Fixes + +- Resolved soundness issue by switching from `atty` to `is-terminal` + +### Breaking Changes + +To open room for changing dependencies: +- Renamed `termcolor` feature to `color` +- Renamed `atty` feature to `auto-color` + +## [0.9.3] - 2022-11-07 + +- Fix a regression from v0.9.2 where env_logger would fail to compile with the termcolor feature turned off. + +## [0.9.2] - 2022-11-07 + +- Fix and un-deprecate Target::Pipe, which was basically not working at all before and deprecated in 0.9.1. + +## [0.9.0] -- 2022-07-14 + +### Breaking Changes + +- Default message format now prints the target instead of the module + +### Improvements + +- Added a method to print the module instead of the target + + +[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.10.2...HEAD +[0.10.2]: https://github.com/rust-cli/env_logger/compare/v0.10.1...v0.10.2 +[0.10.1]: https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.10.1 +[0.10.0]: https://github.com/rust-cli/env_logger/compare/v0.9.3...v0.10.0 +[0.9.3]: https://github.com/rust-cli/env_logger/compare/v0.9.2...v0.9.3 +[0.9.2]: https://github.com/rust-cli/env_logger/compare/v0.9.0...v0.9.2 +[0.9.0]: https://github.com/rust-cli/env_logger/compare/v0.8.4...v0.9.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4495c0e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing to env_logger + +Thanks for wanting to contribute! There are many ways to contribute and we +appreciate any level you're willing to do. + +## Feature Requests + +Need some new functionality to help? You can let us know by opening an +[issue][new issue]. It's helpful to look through [all issues][all issues] in +case its already being talked about. + +## Bug Reports + +Please let us know about what problems you run into, whether in behavior or +ergonomics of API. You can do this by opening an [issue][new issue]. It's +helpful to look through [all issues][all issues] in case its already being +talked about. + +## Pull Requests + +Looking for an idea? Check our [issues][issues]. If it's look more open ended, +it is probably best to post on the issue how you are thinking of resolving the +issue so you can get feedback early in the process. We want you to be +successful and it can be discouraging to find out a lot of re-work is needed. + +Already have an idea? It might be good to first [create an issue][new issue] +to propose it so we can make sure we are aligned and lower the risk of having +to re-work some of it and the discouragement that goes along with that. + +### Process + +When you first post a PR, we request that the the commit history get cleaned +up. We recommend avoiding this during the PR to make it easier to review how +feedback was handled. Once the commit is ready, we'll ask you to clean up the +commit history. Once you let us know this is done, we can move forward with +merging! If you are uncomfortable with these parts of git, let us know and we +can help. + +For commit messages, we use [Conventional](https://www.conventionalcommits.org) +style. If you already wrote your commits and don't feel comfortable changing +them, don't worry and go ahead and create your PR. We'll work with you on the +best route forward. You can check your branch locally with +[`committed`](https://github.com/crate-ci/committed). + +As a heads up, we'll be running your PR through the following gauntlet: +- warnings turned to compile errors +- `cargo test` +- `rustfmt` +- `clippy` +- `rustdoc` +- [`committed`](https://github.com/crate-ci/committed) + +## Releasing + +Pre-requisites +- Running `cargo login` +- A member of `rust-cli:Maintainers` +- Push permission to the repo +- [`cargo-release`](https://github.com/crate-ci/cargo-release/) + +When we're ready to release, a project owner should do the following +1. Update the changelog (see `cargo release changes` for ideas) +2. Determine what the next version is, according to semver +3. Run [`cargo release -x `](https://github.com/crate-ci/cargo-release) + +[issues]: https://github.com/rust-cli/env_logger/issues +[new issue]: https://github.com/rust-cli/env_logger/issues/new +[all issues]: https://github.com/rust-cli/env_logger/issues?utf8=%E2%9C%93&q=is%3Aissue diff --git a/Cargo.toml b/Cargo.toml index d44b8cd..08da79c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,54 @@ [package] name = "env_logger" -edition = "2018" -version = "0.9.3" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/env-logger-rs/env_logger/" -documentation = "https://docs.rs/env_logger" +version = "0.10.2" description = """ A logging implementation for `log` which is configured via an environment variable. """ +repository = "https://github.com/rust-cli/env_logger" categories = ["development-tools::debugging"] keywords = ["logging", "log", "logger"] -include = ["src/**/*", "tests", "LICENSE-*", "README.md", "CHANGELOG.md"] - -[workspace] -members = [ - "ci" +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.60.0" # MSRV +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", + "tests/**/*", ] +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = ["auto-color", "humantime", "regex"] +color = ["dep:termcolor"] +auto-color = ["dep:is-terminal", "color"] +humantime = ["dep:humantime"] +regex = ["dep:regex"] + [dependencies] log = { version = "0.4.8", features = ["std"] } regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } termcolor = { version = "1.1.1", optional = true } humantime = { version = "2.0.0", optional = true } -atty = { version = "0.2.5", optional = true } +is-terminal = { version = "0.4.0", optional = true } [[test]] name = "regexp_filter" @@ -41,6 +65,3 @@ harness = false [[test]] name = "init-twice-retains-filter" harness = false - -[features] -default = ["termcolor", "atty", "humantime", "regex"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 16fe87b..8f71f43 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -1,201 +1,202 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. + 1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright [yyyy] [name of copyright owner] + Copyright {yyyy} {name of copyright owner} -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index 31aa793..a2d0108 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,23 +1,19 @@ -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: +Copyright (c) Individual contributors -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. +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 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. +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. diff --git a/README.OpenSource b/README.OpenSource index 763ebd4..3b83b77 100644 --- a/README.OpenSource +++ b/README.OpenSource @@ -3,7 +3,7 @@ "Name": "env_logger", "License": "Apache License V2.0, MIT", "License File": "LICENSE-APACHE, LICENSE-MIT", - "Version Number": "0.9.3", + "Version Number": "v0.10.2", "Owner": "fangting12@huawei.com", "Upstream URL": "https://github.com/rust-cli/env_logger", "Description": "A Rust library that provides a flexible and customizable logging system." diff --git a/README.md b/README.md index 75add88..1457ecd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![crates.io](https://img.shields.io/crates/v/env_logger.svg)](https://crates.io/crates/env_logger) [![Documentation](https://docs.rs/env_logger/badge.svg)](https://docs.rs/env_logger) -[![Documentation](https://img.shields.io/badge/docs-main-blue.svg)](https://env-logger-rs.github.io/env_logger/env_logger/index.html) Implements a logger that can be configured via environment variables. @@ -16,17 +15,14 @@ Implements a logger that can be configured via environment variables. It must be added along with `log` to the project dependencies: -```toml -[dependencies] -log = "0.4.0" -env_logger = "0.9.0" +```console +$ cargo add log env_logger ``` `env_logger` must be initialized as early as possible in the project. After it's initialized, you can use the `log` macros to do actual logging. ```rust -#[macro_use] -extern crate log; +use log::info; fn main() { env_logger::init(); @@ -75,7 +71,7 @@ There is also a pseudo logging level, `off`, which may be specified to disable all logging for a given module or for the entire application. As with the logging levels, the letter case is not significant. -`env_logger` can be configured in other ways besides an environment variable. See [the examples](https://github.com/env-logger-rs/env_logger/tree/main/examples) for more approaches. +`env_logger` can be configured in other ways besides an environment variable. See [the examples](https://github.com/rust-cli/env_logger/tree/main/examples) for more approaches. ### In tests @@ -86,13 +82,10 @@ Tests can use the `env_logger` crate to see log messages generated during that t log = "0.4.0" [dev-dependencies] -env_logger = "0.9.0" +env_logger = "0.10.0" ``` ```rust -#[macro_use] -extern crate log; - fn add_one(num: i32) -> i32 { info!("add_one called with {}", num); num + 1 @@ -101,6 +94,7 @@ fn add_one(num: i32) -> i32 { #[cfg(test)] mod tests { use super::*; + use log::info; fn init() { let _ = env_logger::builder().is_test(true).try_init(); diff --git a/ci/Cargo.toml b/ci/Cargo.toml deleted file mode 100644 index a2c8df5..0000000 --- a/ci/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "ci" -edition = "2018" -version = "0.0.0" -publish = false - -[dependencies] diff --git a/ci/src/main.rs b/ci/src/main.rs deleted file mode 100644 index e7280f1..0000000 --- a/ci/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -mod permute; -mod task; - -fn main() { - let features = ["termcolor", "humantime", "atty", "regex"]; - - // Run a default build - if !task::test(Default::default()) { - panic!("default test execution failed"); - } - - // Run a build with no features - if !task::test(task::TestArgs { - default_features: false, - ..Default::default() - }) { - panic!("default test execution failed"); - } - - // Run a set of permutations - let failed = permute::all(&features) - .into_iter() - .filter(|features| { - !task::test(task::TestArgs { - features: features.clone(), - default_features: false, - lib_only: true, - }) - }) - .collect::>(); - - if !failed.is_empty() { - for failed in failed { - eprintln!("FAIL: {:?}", failed); - } - - panic!("test execution failed"); - } else { - println!("test execution succeeded"); - } -} diff --git a/ci/src/permute.rs b/ci/src/permute.rs deleted file mode 100644 index badff5b..0000000 --- a/ci/src/permute.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::collections::BTreeSet; - -pub fn all(input: &[T]) -> BTreeSet> -where - T: Ord + Eq + Clone, -{ - let mut permutations = BTreeSet::new(); - - if input.is_empty() { - return permutations; - } - - permutations.insert(input.iter().cloned().collect()); - - if input.len() > 1 { - for t in input { - let p = input - .iter() - .filter(|pt| *pt != t) - .cloned() - .collect::>(); - - for pt in all(&p) { - permutations.insert(pt); - } - } - } - - permutations -} diff --git a/ci/src/task.rs b/ci/src/task.rs deleted file mode 100644 index b674054..0000000 --- a/ci/src/task.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::collections::BTreeSet; -use std::process::{Command, Stdio}; - -pub type Feature = &'static str; - -pub struct TestArgs { - pub features: BTreeSet, - pub default_features: bool, - pub lib_only: bool, -} - -impl Default for TestArgs { - fn default() -> Self { - TestArgs { - features: BTreeSet::new(), - default_features: true, - lib_only: false, - } - } -} - -impl TestArgs { - fn features_string(&self) -> Option { - if self.features.is_empty() { - return None; - } - - let s = self.features.iter().fold(String::new(), |mut s, f| { - if !s.is_empty() { - s.push(' '); - } - s.push_str(f); - - s - }); - - Some(s) - } -} - -pub fn test(args: TestArgs) -> bool { - let features = args.features_string(); - - let mut command = Command::new("cargo"); - - command - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .arg("test") - .arg("--verbose"); - - if !args.default_features { - command.arg("--no-default-features"); - } - - if args.lib_only { - command.arg("--lib"); - } - - if let Some(features) = &features { - command.args(&["--features", features]); - } - - println!("running {:?}", command); - - let status = command.status().expect("Failed to execute command"); - - if !status.success() { - eprintln!( - "test execution failed for features: {}", - features.as_ref().map(AsRef::as_ref).unwrap_or("") - ); - false - } else { - true - } -} diff --git a/committed.toml b/committed.toml new file mode 100644 index 0000000..4211ae3 --- /dev/null +++ b/committed.toml @@ -0,0 +1,3 @@ +style="conventional" +ignore_author_re="(dependabot|renovate)" +merge_commit = false diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..21fa937 --- /dev/null +++ b/deny.toml @@ -0,0 +1,140 @@ +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +# +# e.g. "RUSTSEC-0000-0000", +ignore = [ +] + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "MIT-0", + "Apache-2.0", + "BSD-3-Clause", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", + "ISC", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "warn" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = [] diff --git a/examples/custom_default_format.rs b/examples/custom_default_format.rs index b04eb60..81a1ef4 100644 --- a/examples/custom_default_format.rs +++ b/examples/custom_default_format.rs @@ -17,8 +17,7 @@ $ export MY_LOG_STYLE=never If you want to control the logging output completely, see the `custom_logger` example. */ -#[macro_use] -extern crate log; +use log::info; use env_logger::{Builder, Env}; diff --git a/examples/custom_format.rs b/examples/custom_format.rs index d8585a5..cc16b33 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -17,7 +17,7 @@ $ export MY_LOG_STYLE=never If you want to control the logging output completely, see the `custom_logger` example. */ -#[cfg(all(feature = "termcolor", feature = "humantime"))] +#[cfg(all(feature = "color", feature = "humantime"))] fn main() { use env_logger::{fmt::Color, Builder, Env}; @@ -50,5 +50,5 @@ fn main() { log::info!("a log from `MyLogger`"); } -#[cfg(not(all(feature = "termcolor", feature = "humantime")))] +#[cfg(not(all(feature = "color", feature = "humantime")))] fn main() {} diff --git a/examples/custom_logger.rs b/examples/custom_logger.rs index ca3a4a9..e924fae 100644 --- a/examples/custom_logger.rs +++ b/examples/custom_logger.rs @@ -10,12 +10,9 @@ $ export MY_LOG_LEVEL='info' If you only want to change the way logs are formatted, look at the `custom_format` example. */ -#[macro_use] -extern crate log; - use env_logger::filter::{Builder, Filter}; -use log::{Log, Metadata, Record, SetLoggerError}; +use log::{info, Log, Metadata, Record, SetLoggerError}; const FILTER_ENV: &str = "MY_LOG_LEVEL"; diff --git a/examples/default.rs b/examples/default.rs index 67bb030..9af2a6e 100644 --- a/examples/default.rs +++ b/examples/default.rs @@ -15,8 +15,7 @@ $ export MY_LOG_STYLE=never ``` */ -#[macro_use] -extern crate log; +use log::{debug, error, info, trace, warn}; use env_logger::Env; diff --git a/examples/filters_from_code.rs b/examples/filters_from_code.rs index c877a44..b9e6258 100644 --- a/examples/filters_from_code.rs +++ b/examples/filters_from_code.rs @@ -2,12 +2,9 @@ Specify logging filters in code instead of using an environment variable. */ -#[macro_use] -extern crate log; - use env_logger::Builder; -use log::LevelFilter; +use log::{debug, error, info, trace, warn, LevelFilter}; fn main() { Builder::new().filter_level(LevelFilter::max()).init(); diff --git a/examples/in_tests.rs b/examples/in_tests.rs index c4487cc..1fff5c9 100644 --- a/examples/in_tests.rs +++ b/examples/in_tests.rs @@ -11,13 +11,12 @@ cargo test --example in_tests You should see the `it_does_not_work` test fail and include its log output. */ -#[cfg_attr(test, macro_use)] -extern crate log; - fn main() {} #[cfg(test)] mod tests { + use log::debug; + fn init_logger() { let _ = env_logger::builder() // Include all events in tests diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..e70fb75 --- /dev/null +++ b/release.toml @@ -0,0 +1,3 @@ +owners = ["github:rust-cli:Maintainers"] +dependent-version = "fix" +allow-branch = ["main"] diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 033080c..0000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.41.0 diff --git a/src/filter/mod.rs b/src/filter/mod.rs index a02d6bc..d4acf63 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -59,7 +59,6 @@ //! [`Filter::matches`]: struct.Filter.html#method.matches use log::{Level, LevelFilter, Metadata, Record}; -use std::collections::HashMap; use std::env; use std::fmt; use std::mem; @@ -72,18 +71,6 @@ mod inner; #[path = "string.rs"] mod inner; -/// A log filter. -/// -/// This struct can be used to determine whether or not a log record -/// should be written to the output. -/// Use the [`Builder`] type to parse and construct a `Filter`. -/// -/// [`Builder`]: struct.Builder.html -pub struct Filter { - directives: Vec, - filter: Option, -} - /// A builder for a log filter. /// /// It can be used to parse a set of directives from a string before building @@ -108,17 +95,151 @@ pub struct Filter { /// /// [`Filter`]: struct.Filter.html pub struct Builder { - directives: HashMap, LevelFilter>, + directives: Vec, filter: Option, built: bool, } +impl Builder { + /// Initializes the filter builder with defaults. + pub fn new() -> Builder { + Builder { + directives: Vec::new(), + filter: None, + built: false, + } + } + + /// Initializes the filter builder from an environment. + pub fn from_env(env: &str) -> Builder { + let mut builder = Builder::new(); + + if let Ok(s) = env::var(env) { + builder.parse(&s); + } + + builder + } + + /// Insert the directive replacing any directive with the same name. + fn insert_directive(&mut self, mut directive: Directive) { + if let Some(pos) = self + .directives + .iter() + .position(|d| d.name == directive.name) + { + mem::swap(&mut self.directives[pos], &mut directive); + } else { + self.directives.push(directive); + } + } + + /// Adds a directive to the filter for a specific module. + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter(Some(module), level) + } + + /// Adds a directive to the filter for all modules. + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter(None, level) + } + + /// Adds a directive to the filter. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.insert_directive(Directive { + name: module.map(|s| s.to_string()), + level, + }); + self + } + + /// Parses the directives string. + /// + /// See the [Enabling Logging] section for more details. + /// + /// [Enabling Logging]: ../index.html#enabling-logging + pub fn parse(&mut self, filters: &str) -> &mut Self { + let (directives, filter) = parse_spec(filters); + + self.filter = filter; + + for directive in directives { + self.insert_directive(directive); + } + self + } + + /// Build a log filter. + pub fn build(&mut self) -> Filter { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + let mut directives = Vec::new(); + if self.directives.is_empty() { + // Adds the default filter if none exist + directives.push(Directive { + name: None, + level: LevelFilter::Error, + }); + } else { + // Consume directives. + directives = mem::take(&mut self.directives); + // Sort the directives by length of their name, this allows a + // little more efficient lookup at runtime. + directives.sort_by(|a, b| { + let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); + let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); + alen.cmp(&blen) + }); + } + + Filter { + directives: mem::take(&mut directives), + filter: mem::take(&mut self.filter), + } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +impl fmt::Debug for Builder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.built { + f.debug_struct("Filter").field("built", &true).finish() + } else { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } + } +} + #[derive(Debug)] struct Directive { name: Option, level: LevelFilter, } +/// A log filter. +/// +/// This struct can be used to determine whether or not a log record +/// should be written to the output. +/// Use the [`Builder`] type to parse and construct a `Filter`. +/// +/// [`Builder`]: struct.Builder.html +pub struct Filter { + directives: Vec, + filter: Option, +} + impl Filter { /// Returns the maximum `LevelFilter` that this filter instance is /// configured to output. @@ -168,103 +289,6 @@ impl Filter { } } -impl Builder { - /// Initializes the filter builder with defaults. - pub fn new() -> Builder { - Builder { - directives: HashMap::new(), - filter: None, - built: false, - } - } - - /// Initializes the filter builder from an environment. - pub fn from_env(env: &str) -> Builder { - let mut builder = Builder::new(); - - if let Ok(s) = env::var(env) { - builder.parse(&s); - } - - builder - } - - /// Adds a directive to the filter for a specific module. - pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { - self.filter(Some(module), level) - } - - /// Adds a directive to the filter for all modules. - pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { - self.filter(None, level) - } - - /// Adds a directive to the filter. - /// - /// The given module (if any) will log at most the specified level provided. - /// If no module is provided then the filter will apply to all log messages. - pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { - self.directives.insert(module.map(|s| s.to_string()), level); - self - } - - /// Parses the directives string. - /// - /// See the [Enabling Logging] section for more details. - /// - /// [Enabling Logging]: ../index.html#enabling-logging - pub fn parse(&mut self, filters: &str) -> &mut Self { - let (directives, filter) = parse_spec(filters); - - self.filter = filter; - - for directive in directives { - self.directives.insert(directive.name, directive.level); - } - self - } - - /// Build a log filter. - pub fn build(&mut self) -> Filter { - assert!(!self.built, "attempt to re-use consumed builder"); - self.built = true; - - let mut directives = Vec::new(); - if self.directives.is_empty() { - // Adds the default filter if none exist - directives.push(Directive { - name: None, - level: LevelFilter::Error, - }); - } else { - // Consume map of directives. - let directives_map = mem::take(&mut self.directives); - directives = directives_map - .into_iter() - .map(|(name, level)| Directive { name, level }) - .collect(); - // Sort the directives by length of their name, this allows a - // little more efficient lookup at runtime. - directives.sort_by(|a, b| { - let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); - let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); - alen.cmp(&blen) - }); - } - - Filter { - directives: mem::take(&mut directives), - filter: mem::replace(&mut self.filter, None), - } - } -} - -impl Default for Builder { - fn default() -> Self { - Builder::new() - } -} - impl fmt::Debug for Filter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Filter") @@ -274,19 +298,6 @@ impl fmt::Debug for Filter { } } -impl fmt::Debug for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.built { - f.debug_struct("Filter").field("built", &true).finish() - } else { - f.debug_struct("Filter") - .field("filter", &self.filter) - .field("directives", &self.directives) - .finish() - } - } -} - /// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") /// and return a vector with log directives. fn parse_spec(spec: &str) -> (Vec, Option) { @@ -762,7 +773,7 @@ mod tests { #[test] fn parse_spec_blank_level_isolated_comma_only() { // The spec should contain zero or more comma-separated string slices, - // so a comma-only string should be interpretted as two empty strings + // so a comma-only string should be interpreted as two empty strings // (which should both be treated as invalid, so ignored). let (dirs, filter) = parse_spec(","); // should be ignored assert_eq!(dirs.len(), 0); @@ -772,7 +783,7 @@ mod tests { #[test] fn parse_spec_blank_level_isolated_comma_blank() { // The spec should contain zero or more comma-separated string slices, - // so this bogus spec should be interpretted as containing one empty + // so this bogus spec should be interpreted as containing one empty // string and one blank string. Both should both be treated as // invalid, so ignored. let (dirs, filter) = parse_spec(", "); // should be ignored @@ -783,7 +794,7 @@ mod tests { #[test] fn parse_spec_blank_level_isolated_blank_comma() { // The spec should contain zero or more comma-separated string slices, - // so this bogus spec should be interpretted as containing one blank + // so this bogus spec should be interpreted as containing one blank // string and one empty string. Both should both be treated as // invalid, so ignored. let (dirs, filter) = parse_spec(" ,"); // should be ignored diff --git a/src/fmt/humantime/extern_impl.rs b/src/fmt/humantime.rs similarity index 98% rename from src/fmt/humantime/extern_impl.rs rename to src/fmt/humantime.rs index bdf165c..582a631 100644 --- a/src/fmt/humantime/extern_impl.rs +++ b/src/fmt/humantime.rs @@ -7,10 +7,6 @@ use humantime::{ use crate::fmt::{Formatter, TimestampPrecision}; -pub(in crate::fmt) mod glob { - pub use super::*; -} - impl Formatter { /// Get a [`Timestamp`] for the current date and time in UTC. /// diff --git a/src/fmt/humantime/mod.rs b/src/fmt/humantime/mod.rs deleted file mode 100644 index ac23ae2..0000000 --- a/src/fmt/humantime/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* -This internal module contains the timestamp implementation. - -Its public API is available when the `humantime` crate is available. -*/ - -#[cfg_attr(feature = "humantime", path = "extern_impl.rs")] -#[cfg_attr(not(feature = "humantime"), path = "shim_impl.rs")] -mod imp; - -pub(in crate::fmt) use self::imp::*; diff --git a/src/fmt/humantime/shim_impl.rs b/src/fmt/humantime/shim_impl.rs deleted file mode 100644 index 906bf9e..0000000 --- a/src/fmt/humantime/shim_impl.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* -Timestamps aren't available when we don't have a `humantime` dependency. -*/ - -pub(in crate::fmt) mod glob {} diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 1677887..5f1d5c8 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -35,12 +35,21 @@ use std::io::prelude::*; use std::rc::Rc; use std::{fmt, io, mem}; +#[cfg(feature = "color")] +use log::Level; use log::Record; +#[cfg(feature = "humantime")] mod humantime; pub(crate) mod writer; -pub use self::humantime::glob::*; +#[cfg(feature = "color")] +mod style; +#[cfg(feature = "color")] +pub use style::{Color, Style, StyledValue}; + +#[cfg(feature = "humantime")] +pub use self::humantime::Timestamp; pub use self::writer::glob::*; use self::writer::{Buffer, Writer}; @@ -120,6 +129,62 @@ impl Formatter { } } +#[cfg(feature = "color")] +impl Formatter { + /// Begin a new [`Style`]. + /// + /// # Examples + /// + /// Create a bold, red colored style and use it to print the log level: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::fmt::Color; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut level_style = buf.style(); + /// + /// level_style.set_color(Color::Red).set_bold(true); + /// + /// writeln!(buf, "{}: {}", + /// level_style.value(record.level()), + /// record.args()) + /// }); + /// ``` + /// + /// [`Style`]: struct.Style.html + pub fn style(&self) -> Style { + Style { + buf: self.buf.clone(), + spec: termcolor::ColorSpec::new(), + } + } + + /// Get the default [`Style`] for the given level. + /// + /// The style can be used to print other values besides the level. + pub fn default_level_style(&self, level: Level) -> Style { + let mut level_style = self.style(); + match level { + Level::Trace => level_style.set_color(Color::Cyan), + Level::Debug => level_style.set_color(Color::Blue), + Level::Info => level_style.set_color(Color::Green), + Level::Warn => level_style.set_color(Color::Yellow), + Level::Error => level_style.set_color(Color::Red).set_bold(true), + }; + level_style + } + + /// Get a printable [`Style`] for the given level. + /// + /// The style can only be used to print the level. + pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { + self.default_level_style(level).into_value(level) + } +} + impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.borrow_mut().write(buf) @@ -149,21 +214,6 @@ pub(crate) struct Builder { built: bool, } -impl Default for Builder { - fn default() -> Self { - Builder { - format_timestamp: Some(Default::default()), - format_module_path: false, - format_target: true, - format_level: true, - format_indent: Some(4), - custom_format: None, - format_suffix: "\n", - built: false, - } - } -} - impl Builder { /// Convert the format into a callable function. /// @@ -202,9 +252,24 @@ impl Builder { } } -#[cfg(feature = "termcolor")] +impl Default for Builder { + fn default() -> Self { + Builder { + format_timestamp: Some(Default::default()), + format_module_path: false, + format_target: true, + format_level: true, + format_indent: Some(4), + custom_format: None, + format_suffix: "\n", + built: false, + } + } +} + +#[cfg(feature = "color")] type SubtleStyle = StyledValue<'static, &'static str>; -#[cfg(not(feature = "termcolor"))] +#[cfg(not(feature = "color"))] type SubtleStyle = &'static str; /// The default format. @@ -233,7 +298,7 @@ impl<'a> DefaultFormat<'a> { } fn subtle_style(&self, text: &'static str) -> SubtleStyle { - #[cfg(feature = "termcolor")] + #[cfg(feature = "color")] { self.buf .style() @@ -242,7 +307,7 @@ impl<'a> DefaultFormat<'a> { .clone() .into_value(text) } - #[cfg(not(feature = "termcolor"))] + #[cfg(not(feature = "color"))] { text } @@ -268,11 +333,11 @@ impl<'a> DefaultFormat<'a> { } let level = { - #[cfg(feature = "termcolor")] + #[cfg(feature = "color")] { self.buf.default_styled_level(record.level()) } - #[cfg(not(feature = "termcolor"))] + #[cfg(not(feature = "color"))] { record.level() } @@ -403,7 +468,7 @@ mod tests { fmt.write(&record).expect("failed to write record"); let buf = buf.borrow(); - String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") + String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") } fn write_target(target: &str, fmt: DefaultFormat) -> String { diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/style.rs similarity index 62% rename from src/fmt/writer/termcolor/extern_impl.rs rename to src/fmt/style.rs index fbe37a7..dd4463a 100644 --- a/src/fmt/writer/termcolor/extern_impl.rs +++ b/src/fmt/style.rs @@ -1,190 +1,9 @@ use std::borrow::Cow; use std::cell::RefCell; use std::fmt; -use std::io::{self, Write}; use std::rc::Rc; -use std::sync::Mutex; -use log::Level; -use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; - -use crate::fmt::{Formatter, WritableTarget, WriteStyle}; - -pub(in crate::fmt::writer) mod glob { - pub use super::*; -} - -impl Formatter { - /// Begin a new [`Style`]. - /// - /// # Examples - /// - /// Create a bold, red colored style and use it to print the log level: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut level_style = buf.style(); - /// - /// level_style.set_color(Color::Red).set_bold(true); - /// - /// writeln!(buf, "{}: {}", - /// level_style.value(record.level()), - /// record.args()) - /// }); - /// ``` - /// - /// [`Style`]: struct.Style.html - pub fn style(&self) -> Style { - Style { - buf: self.buf.clone(), - spec: ColorSpec::new(), - } - } - - /// Get the default [`Style`] for the given level. - /// - /// The style can be used to print other values besides the level. - pub fn default_level_style(&self, level: Level) -> Style { - let mut level_style = self.style(); - match level { - Level::Trace => level_style.set_color(Color::Cyan), - Level::Debug => level_style.set_color(Color::Blue), - Level::Info => level_style.set_color(Color::Green), - Level::Warn => level_style.set_color(Color::Yellow), - Level::Error => level_style.set_color(Color::Red).set_bold(true), - }; - level_style - } - - /// Get a printable [`Style`] for the given level. - /// - /// The style can only be used to print the level. - pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { - self.default_level_style(level).into_value(level) - } -} - -pub(in crate::fmt::writer) struct BufferWriter { - inner: termcolor::BufferWriter, - uncolored_target: Option, -} - -pub(in crate::fmt) struct Buffer { - inner: termcolor::Buffer, - has_uncolored_target: bool, -} - -impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { - BufferWriter { - inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), - uncolored_target: if is_test { - Some(WritableTarget::Stderr) - } else { - None - }, - } - } - - pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { - BufferWriter { - inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), - uncolored_target: if is_test { - Some(WritableTarget::Stdout) - } else { - None - }, - } - } - - pub(in crate::fmt::writer) fn pipe( - write_style: WriteStyle, - pipe: Box>, - ) -> Self { - BufferWriter { - // The inner Buffer is never printed from, but it is still needed to handle coloring and other formating - inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), - uncolored_target: Some(WritableTarget::Pipe(pipe)), - } - } - - pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { - Buffer { - inner: self.inner.buffer(), - has_uncolored_target: self.uncolored_target.is_some(), - } - } - - pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - if let Some(target) = &self.uncolored_target { - // This impl uses the `eprint` and `print` macros - // instead of `termcolor`'s buffer. - // This is so their output can be captured by `cargo test` - let log = String::from_utf8_lossy(buf.bytes()); - - match target { - WritableTarget::Stderr => eprint!("{}", log), - WritableTarget::Stdout => print!("{}", log), - WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?, - } - - Ok(()) - } else { - self.inner.print(&buf.inner) - } - } -} - -impl Buffer { - pub(in crate::fmt) fn clear(&mut self) { - self.inner.clear() - } - - pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - - pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } - - pub(in crate::fmt) fn bytes(&self) -> &[u8] { - self.inner.as_slice() - } - - fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { - // Ignore styles for test captured logs because they can't be printed - if !self.has_uncolored_target { - self.inner.set_color(spec) - } else { - Ok(()) - } - } - - fn reset(&mut self) -> io::Result<()> { - // Ignore styles for test captured logs because they can't be printed - if !self.has_uncolored_target { - self.inner.reset() - } else { - Ok(()) - } - } -} - -impl WriteStyle { - fn into_color_choice(self) -> ColorChoice { - match self { - WriteStyle::Always => ColorChoice::Always, - WriteStyle::Auto => ColorChoice::Auto, - WriteStyle::Never => ColorChoice::Never, - } - } -} +use super::Buffer; /// A set of styles to apply to the terminal output. /// @@ -240,18 +59,8 @@ impl WriteStyle { /// [`value`]: #method.value #[derive(Clone)] pub struct Style { - buf: Rc>, - spec: ColorSpec, -} - -/// A value that can be printed using the given styles. -/// -/// It is the result of calling [`Style::value`]. -/// -/// [`Style::value`]: struct.Style.html#method.value -pub struct StyledValue<'a, T> { - style: Cow<'a, Style>, - value: T, + pub(in crate::fmt) buf: Rc>, + pub(in crate::fmt) spec: termcolor::ColorSpec, } impl Style { @@ -426,6 +235,22 @@ impl Style { } } +impl fmt::Debug for Style { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Style").field("spec", &self.spec).finish() + } +} + +/// A value that can be printed using the given styles. +/// +/// It is the result of calling [`Style::value`]. +/// +/// [`Style::value`]: struct.Style.html#method.value +pub struct StyledValue<'a, T> { + style: Cow<'a, Style>, + value: T, +} + impl<'a, T> StyledValue<'a, T> { fn write_fmt(&self, f: F) -> fmt::Result where @@ -445,12 +270,6 @@ impl<'a, T> StyledValue<'a, T> { } } -impl fmt::Debug for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Style").field("spec", &self.spec).finish() - } -} - macro_rules! impl_styled_value_fmt { ($($fmt_trait:path),*) => { $( diff --git a/src/fmt/writer/atty.rs b/src/fmt/writer/atty.rs index 343539c..1a133ee 100644 --- a/src/fmt/writer/atty.rs +++ b/src/fmt/writer/atty.rs @@ -1,24 +1,25 @@ /* This internal module contains the terminal detection implementation. -If the `atty` crate is available then we use it to detect whether we're -attached to a particular TTY. If the `atty` crate is not available we -assume we're not attached to anything. This effectively prevents styles -from being printed. +If the `auto-color` feature is enabled then we detect whether we're attached to a particular TTY. +Otherwise, assume we're not attached to anything. This effectively prevents styles from being +printed. */ -#[cfg(feature = "atty")] +#[cfg(feature = "auto-color")] mod imp { + use is_terminal::IsTerminal; + pub(in crate::fmt) fn is_stdout() -> bool { - atty::is(atty::Stream::Stdout) + std::io::stdout().is_terminal() } pub(in crate::fmt) fn is_stderr() -> bool { - atty::is(atty::Stream::Stderr) + std::io::stderr().is_terminal() } } -#[cfg(not(feature = "atty"))] +#[cfg(not(feature = "auto-color"))] mod imp { pub(in crate::fmt) fn is_stdout() -> bool { false diff --git a/src/fmt/writer/termcolor/mod.rs b/src/fmt/writer/buffer/mod.rs similarity index 51% rename from src/fmt/writer/termcolor/mod.rs rename to src/fmt/writer/buffer/mod.rs index f3e6768..4e678b2 100644 --- a/src/fmt/writer/termcolor/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -5,8 +5,11 @@ Its public API is available when the `termcolor` crate is available. The terminal printing is shimmed when the `termcolor` crate is not available. */ -#[cfg_attr(feature = "termcolor", path = "extern_impl.rs")] -#[cfg_attr(not(feature = "termcolor"), path = "shim_impl.rs")] -mod imp; - -pub(in crate::fmt) use self::imp::*; +#[cfg(feature = "color")] +mod termcolor; +#[cfg(feature = "color")] +pub(in crate::fmt) use self::termcolor::*; +#[cfg(not(feature = "color"))] +mod plain; +#[cfg(not(feature = "color"))] +pub(in crate::fmt) use plain::*; diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs new file mode 100644 index 0000000..e6809b0 --- /dev/null +++ b/src/fmt/writer/buffer/plain.rs @@ -0,0 +1,68 @@ +use std::{io, sync::Mutex}; + +use crate::fmt::{WritableTarget, WriteStyle}; + +pub(in crate::fmt::writer) struct BufferWriter { + target: WritableTarget, +} + +impl BufferWriter { + pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStderr + } else { + WritableTarget::WriteStderr + }, + } + } + + pub(in crate::fmt::writer) fn stdout(is_test: bool, _write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStdout + } else { + WritableTarget::WriteStdout + }, + } + } + + pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { + BufferWriter { + target: WritableTarget::Pipe(pipe), + } + } + + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + WriteStyle::Never + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer(Vec::new()) + } + + pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + self.target.print(buf) + } +} + +pub(in crate::fmt) struct Buffer(Vec); + +impl Buffer { + pub(in crate::fmt) fn clear(&mut self) { + self.0.clear(); + } + + pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend(buf); + Ok(buf.len()) + } + + pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { + &self.0 + } +} diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs new file mode 100644 index 0000000..d3090a1 --- /dev/null +++ b/src/fmt/writer/buffer/termcolor.rs @@ -0,0 +1,108 @@ +use std::io::{self, Write}; +use std::sync::Mutex; + +use termcolor::{self, ColorSpec, WriteColor}; + +use crate::fmt::{WritableTarget, WriteStyle}; + +pub(in crate::fmt::writer) struct BufferWriter { + inner: termcolor::BufferWriter, + uncolored_target: Option, + write_style: WriteStyle, +} + +impl BufferWriter { + pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), + uncolored_target: if is_test { + Some(WritableTarget::PrintStderr) + } else { + None + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), + uncolored_target: if is_test { + Some(WritableTarget::PrintStdout) + } else { + None + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { + let write_style = WriteStyle::Never; + BufferWriter { + // The inner Buffer is never printed from, but it is still needed to handle coloring and other formatting + inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), + uncolored_target: Some(WritableTarget::Pipe(pipe)), + write_style, + } + } + + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer { + inner: self.inner.buffer(), + has_uncolored_target: self.uncolored_target.is_some(), + } + } + + pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + if let Some(target) = &self.uncolored_target { + target.print(buf) + } else { + self.inner.print(&buf.inner) + } + } +} + +pub(in crate::fmt) struct Buffer { + inner: termcolor::Buffer, + has_uncolored_target: bool, +} + +impl Buffer { + pub(in crate::fmt) fn clear(&mut self) { + self.inner.clear() + } + + pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } + + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { + self.inner.as_slice() + } + + pub(in crate::fmt) fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + // Ignore styles for test captured logs because they can't be printed + if !self.has_uncolored_target { + self.inner.set_color(spec) + } else { + Ok(()) + } + } + + pub(in crate::fmt) fn reset(&mut self) -> io::Result<()> { + // Ignore styles for test captured logs because they can't be printed + if !self.has_uncolored_target { + self.inner.reset() + } else { + Ok(()) + } + } +} diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 2b56772..41466b9 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,16 +1,15 @@ mod atty; -mod termcolor; +mod buffer; use self::atty::{is_stderr, is_stdout}; -use self::termcolor::BufferWriter; +use self::buffer::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { - pub use super::termcolor::glob::*; pub use super::*; } -pub(super) use self::termcolor::Buffer; +pub(super) use self::buffer::Buffer; /// Log target, either `stdout`, `stderr` or a custom pipe. #[non_exhaustive] @@ -47,27 +46,49 @@ impl fmt::Debug for Target { /// /// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. pub(super) enum WritableTarget { - /// Logs will be sent to standard output. - Stdout, - /// Logs will be sent to standard error. - Stderr, + /// Logs will be written to standard output. + #[allow(dead_code)] + WriteStdout, + /// Logs will be printed to standard output. + PrintStdout, + /// Logs will be written to standard error. + #[allow(dead_code)] + WriteStderr, + /// Logs will be printed to standard error. + PrintStderr, /// Logs will be sent to a custom pipe. Pipe(Box>), } -impl From for WritableTarget { - fn from(target: Target) -> Self { - match target { - Target::Stdout => Self::Stdout, - Target::Stderr => Self::Stderr, - Target::Pipe(pipe) => Self::Pipe(Box::new(Mutex::new(pipe))), - } - } -} +impl WritableTarget { + fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; -impl Default for WritableTarget { - fn default() -> Self { - Self::from(Target::default()) + let buf = buf.as_bytes(); + match self { + WritableTarget::WriteStdout => { + let stream = std::io::stdout(); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), + WritableTarget::WriteStderr => { + let stream = std::io::stderr(); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), + // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. + WritableTarget::Pipe(pipe) => { + let mut stream = pipe.lock().unwrap(); + stream.write_all(buf)?; + stream.flush()?; + } + } + + Ok(()) } } @@ -77,8 +98,10 @@ impl fmt::Debug for WritableTarget { f, "{}", match self { - Self::Stdout => "stdout", - Self::Stderr => "stderr", + Self::WriteStdout => "stdout", + Self::PrintStdout => "stdout", + Self::WriteStderr => "stderr", + Self::PrintStderr => "stderr", Self::Pipe(_) => "pipe", } ) @@ -101,15 +124,25 @@ impl Default for WriteStyle { } } +#[cfg(feature = "color")] +impl WriteStyle { + fn into_color_choice(self) -> ::termcolor::ColorChoice { + match self { + WriteStyle::Always => ::termcolor::ColorChoice::Always, + WriteStyle::Auto => ::termcolor::ColorChoice::Auto, + WriteStyle::Never => ::termcolor::ColorChoice::Never, + } + } +} + /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, - write_style: WriteStyle, } impl Writer { pub fn write_style(&self) -> WriteStyle { - self.write_style + self.inner.write_style() } pub(super) fn buffer(&self) -> Buffer { @@ -121,12 +154,18 @@ impl Writer { } } +impl fmt::Debug for Writer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Writer").finish() + } +} + /// A builder for a terminal writer. /// /// The target and style choice can be configured before building. #[derive(Debug)] pub(crate) struct Builder { - target: WritableTarget, + target: Target, write_style: WriteStyle, is_test: bool, built: bool, @@ -145,7 +184,7 @@ impl Builder { /// Set the target to write to. pub(crate) fn target(&mut self, target: Target) -> &mut Self { - self.target = target.into(); + self.target = target; self } @@ -165,6 +204,7 @@ impl Builder { } /// Whether or not to capture logs for `cargo test`. + #[allow(clippy::wrong_self_convention)] pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { self.is_test = is_test; self @@ -178,9 +218,9 @@ impl Builder { let color_choice = match self.write_style { WriteStyle::Auto => { if match &self.target { - WritableTarget::Stderr => is_stderr(), - WritableTarget::Stdout => is_stdout(), - WritableTarget::Pipe(_) => false, + Target::Stderr => is_stderr(), + Target::Stdout => is_stdout(), + Target::Pipe(_) => false, } { WriteStyle::Auto } else { @@ -189,17 +229,19 @@ impl Builder { } color_choice => color_choice, }; - - let writer = match mem::take(&mut self.target) { - WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice), - WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice), - WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe), + let color_choice = if self.is_test { + WriteStyle::Never + } else { + color_choice }; - Writer { - inner: writer, - write_style: self.write_style, - } + let writer = match mem::take(&mut self.target) { + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), + Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe))), + }; + + Writer { inner: writer } } } @@ -209,12 +251,6 @@ impl Default for Builder { } } -impl fmt::Debug for Writer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Writer").finish() - } -} - fn parse_write_style(spec: &str) -> WriteStyle { match spec { "auto" => WriteStyle::Auto, diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/termcolor/shim_impl.rs deleted file mode 100644 index 0705770..0000000 --- a/src/fmt/writer/termcolor/shim_impl.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::{io, sync::Mutex}; - -use crate::fmt::{WritableTarget, WriteStyle}; - -pub(in crate::fmt::writer) mod glob {} - -pub(in crate::fmt::writer) struct BufferWriter { - target: WritableTarget, -} - -pub(in crate::fmt) struct Buffer(Vec); - -impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { - BufferWriter { - target: WritableTarget::Stderr, - } - } - - pub(in crate::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self { - BufferWriter { - target: WritableTarget::Stdout, - } - } - - pub(in crate::fmt::writer) fn pipe( - _write_style: WriteStyle, - pipe: Box>, - ) -> Self { - BufferWriter { - target: WritableTarget::Pipe(pipe), - } - } - - pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { - Buffer(Vec::new()) - } - - pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - // This impl uses the `eprint` and `print` macros - // instead of using the streams directly. - // This is so their output can be captured by `cargo test`. - match &self.target { - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, - WritableTarget::Stdout => print!("{}", String::from_utf8_lossy(&buf.0)), - WritableTarget::Stderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), - } - - Ok(()) - } -} - -impl Buffer { - pub(in crate::fmt) fn clear(&mut self) { - self.0.clear(); - } - - pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend(buf); - Ok(buf.len()) - } - - pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - - #[cfg(test)] - pub(in crate::fmt) fn bytes(&self) -> &[u8] { - &self.0 - } -} diff --git a/src/lib.rs b/src/lib.rs index 642b731..cdc2bad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,78 +85,67 @@ //! //! ## Enabling logging //! -//! Log levels are controlled on a per-module basis, and **by default all -//! logging is disabled except for the `error` level**. -//! -//! Logging is controlled via the **`RUST_LOG`** environment variable. The -//! value of this environment variable is a comma-separated list of *logging -//! directives*. A logging directive is of the form: +//! **By default all logging is disabled except for the `error` level** //! +//! The **`RUST_LOG`** environment variable controls logging with the syntax: //! ```text -//! example::log::target=level +//! RUST_LOG=[target][=][level][,...] +//! ``` +//! Or in other words, its a comma-separated list of directives. +//! Directives can filter by **target**, by **level**, or both (using `=`). +//! +//! For example, +//! ```text +//! RUST_LOG=data=debug,hardware=debug //! ``` //! -//! The log target is typically equal to the path of the module the message -//! in question originated from, though it can be overriden. -//! +//! **target** is typically the path of the module the message +//! in question originated from, though it can be overridden. //! The path is rooted in the name of the crate it was compiled for, so if //! your program is in a file called, for example, `hello.rs`, the path would -//! simply be be `hello`. +//! simply be `hello`. //! //! Furthermore, the log can be filtered using prefix-search based on the -//! specified log target. A value of, for example, `RUST_LOG=example`, would -//! match all of the messages with targets: +//! specified log target. //! -//! * `example` -//! * `example::test` -//! * `example::test::module::submodule` -//! * `examples::and_more_examples` +//! For example, `RUST_LOG=example` would match the following targets: +//! - `example` +//! - `example::test` +//! - `example::test::module::submodule` +//! - `examples::and_more_examples` //! //! When providing the crate name or a module path, explicitly specifying the //! log level is optional. If omitted, all logging for the item will be //! enabled. //! -//! The names of the log levels that may be specified correspond to the -//! variations of the [`log::Level`][level-enum] enum from the `log` -//! crate. They are: +//! **level** is the maximum [`log::Level`][level-enum] to be shown and includes: +//! - `error` +//! - `warn` +//! - `info` +//! - `debug` +//! - `trace` +//! - `off` (pseudo level to disable all logging for the target) //! -//! * `error` -//! * `warn` -//! * `info` -//! * `debug` -//! * `trace` -//! -//! There is also a pseudo logging level, `off`, which may be specified to -//! disable all logging for a given module or for the entire application. As -//! with the logging levels, the letter case is not significant[^fn-off]. -//! -//! [^fn-off]: Similar to the universe of log level names, the `off` pseudo -//! log level feature is also provided by the underlying `log` crate. -//! -//! The letter case is not significant for the logging level names; e.g., +//! Logging level names are case-insensitive; e.g., //! `debug`, `DEBUG`, and `dEbuG` all represent the same logging level. For //! consistency, our convention is to use the lower case names. Where our docs //! do use other forms, they do so in the context of specific examples, so you //! won't be surprised if you see similar usage in the wild. //! -//! As the log level for a module is optional, the module to enable logging for -//! is also optional. **If only a level is provided, then the global log -//! level for all modules is set to this value.** -//! //! Some examples of valid values of `RUST_LOG` are: //! -//! * `hello` turns on all logging for the 'hello' module -//! * `trace` turns on all logging for the application, regardless of its name -//! * `TRACE` turns on all logging for the application, regardless of its name (same as previous) -//! * `info` turns on all info logging -//! * `INFO` turns on all info logging (same as previous) -//! * `hello=debug` turns on debug logging for 'hello' -//! * `hello=DEBUG` turns on debug logging for 'hello' (same as previous) -//! * `hello,std::option` turns on hello, and std's option logging -//! * `error,hello=warn` turn on global error logging and also warn for hello -//! * `error,hello=off` turn on global error logging, but turn off logging for hello -//! * `off` turns off all logging for the application -//! * `OFF` turns off all logging for the application (same as previous) +//! - `RUST_LOG=hello` turns on all logging for the `hello` module +//! - `RUST_LOG=trace` turns on all logging for the application, regardless of its name +//! - `RUST_LOG=TRACE` turns on all logging for the application, regardless of its name (same as previous) +//! - `RUST_LOG=info` turns on all info logging +//! - `RUST_LOG=INFO` turns on all info logging (same as previous) +//! - `RUST_LOG=hello=debug` turns on debug logging for `hello` +//! - `RUST_LOG=hello=DEBUG` turns on debug logging for `hello` (same as previous) +//! - `RUST_LOG=hello,std::option` turns on `hello`, and std's option logging +//! - `RUST_LOG=error,hello=warn` turn on global error logging and also warn for `hello` +//! - `RUST_LOG=error,hello=off` turn on global error logging, but turn off logging for `hello` +//! - `RUST_LOG=off` turns off all logging for the application +//! - `RUST_LOG=OFF` turns off all logging for the application (same as previous) //! //! ## Filtering results //! @@ -184,9 +173,10 @@ //! The [`Builder::is_test`] method can be used in unit tests to ensure logs will be captured: //! //! ``` -//! # #[macro_use] extern crate log; //! #[cfg(test)] //! mod tests { +//! use log::info; +//! //! fn init() { //! let _ = env_logger::builder().is_test(true).try_init(); //! } @@ -239,7 +229,7 @@ //! ### Using a custom format //! //! Custom formats can be provided as closures to the [`Builder`]. -//! These closures take a [`Formatter`] and `log::Record` as arguments: +//! These closures take a [`Formatter`][crate::fmt::Formatter] and `log::Record` as arguments: //! //! ``` //! use std::io::Write; @@ -266,9 +256,9 @@ //! env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); //! ``` //! -//! [gh-repo-examples]: https://github.com/env-logger-rs/env_logger/tree/main/examples +//! [gh-repo-examples]: https://github.com/rust-cli/env_logger/tree/main/examples //! [level-enum]: https://docs.rs/log/latest/log/enum.Level.html -//! [log-crate-url]: https://docs.rs/log/ +//! [log-crate-url]: https://docs.rs/log //! [`Builder`]: struct.Builder.html //! [`Builder::is_test`]: struct.Builder.html#method.is_test //! [`Env`]: struct.Env.html @@ -278,1034 +268,17 @@ html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", html_favicon_url = "https://www.rust-lang.org/static/images/favicon.ico" )] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] // When compiled for the rustc compiler itself we want to make sure that this is // an unstable crate #![cfg_attr(rustbuild, feature(staged_api, rustc_private))] #![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))] #![deny(missing_debug_implementations, missing_docs)] -use std::{borrow::Cow, cell::RefCell, env, io}; - -use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; +mod logger; pub mod filter; pub mod fmt; pub use self::fmt::glob::*; - -use self::filter::Filter; -use self::fmt::writer::{self, Writer}; -use self::fmt::{FormatFn, Formatter}; - -/// The default name for the environment variable to read filters from. -pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; - -/// The default name for the environment variable to read style preferences from. -pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; - -/// Set of environment variables to configure from. -/// -/// # Default environment variables -/// -/// By default, the `Env` will read the following environment variables: -/// -/// - `RUST_LOG`: the level filter -/// - `RUST_LOG_STYLE`: whether or not to print styles with records. -/// -/// These sources can be configured using the builder methods on `Env`. -#[derive(Debug)] -pub struct Env<'a> { - filter: Var<'a>, - write_style: Var<'a>, -} - -#[derive(Debug)] -struct Var<'a> { - name: Cow<'a, str>, - default: Option>, -} - -/// The env logger. -/// -/// This struct implements the `Log` trait from the [`log` crate][log-crate-url], -/// which allows it to act as a logger. -/// -/// The [`init()`], [`try_init()`], [`Builder::init()`] and [`Builder::try_init()`] -/// methods will each construct a `Logger` and immediately initialize it as the -/// default global logger. -/// -/// If you'd instead need access to the constructed `Logger`, you can use -/// the associated [`Builder`] and install it with the -/// [`log` crate][log-crate-url] directly. -/// -/// [log-crate-url]: https://docs.rs/log/ -/// [`init()`]: fn.init.html -/// [`try_init()`]: fn.try_init.html -/// [`Builder::init()`]: struct.Builder.html#method.init -/// [`Builder::try_init()`]: struct.Builder.html#method.try_init -/// [`Builder`]: struct.Builder.html -pub struct Logger { - writer: Writer, - filter: Filter, - format: FormatFn, -} - -/// `Builder` acts as builder for initializing a `Logger`. -/// -/// It can be used to customize the log format, change the environment variable used -/// to provide the logging directives and also set the default log level filter. -/// -/// # Examples -/// -/// ``` -/// # #[macro_use] extern crate log; -/// # use std::io::Write; -/// use env_logger::Builder; -/// use log::LevelFilter; -/// -/// let mut builder = Builder::from_default_env(); -/// -/// builder -/// .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) -/// .filter(None, LevelFilter::Info) -/// .init(); -/// -/// error!("error message"); -/// info!("info message"); -/// ``` -#[derive(Default)] -pub struct Builder { - filter: filter::Builder, - writer: writer::Builder, - format: fmt::Builder, - built: bool, -} - -impl Builder { - /// Initializes the log builder with defaults. - /// - /// **NOTE:** This method won't read from any environment variables. - /// Use the [`filter`] and [`write_style`] methods to configure the builder - /// or use [`from_env`] or [`from_default_env`] instead. - /// - /// # Examples - /// - /// Create a new builder and configure filters and style: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::{Builder, WriteStyle}; - /// - /// let mut builder = Builder::new(); - /// - /// builder - /// .filter(None, LevelFilter::Info) - /// .write_style(WriteStyle::Always) - /// .init(); - /// ``` - /// - /// [`filter`]: #method.filter - /// [`write_style`]: #method.write_style - /// [`from_env`]: #method.from_env - /// [`from_default_env`]: #method.from_default_env - pub fn new() -> Builder { - Default::default() - } - - /// Initializes the log builder from the environment. - /// - /// The variables used to read configuration from can be tweaked before - /// passing in. - /// - /// # Examples - /// - /// Initialise a logger reading the log filter from an environment variable - /// called `MY_LOG`: - /// - /// ``` - /// use env_logger::Builder; - /// - /// let mut builder = Builder::from_env("MY_LOG"); - /// builder.init(); - /// ``` - /// - /// Initialise a logger using the `MY_LOG` variable for filtering and - /// `MY_LOG_STYLE` for whether or not to write styles: - /// - /// ``` - /// use env_logger::{Builder, Env}; - /// - /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); - /// - /// let mut builder = Builder::from_env(env); - /// builder.init(); - /// ``` - pub fn from_env<'a, E>(env: E) -> Self - where - E: Into>, - { - let mut builder = Builder::new(); - builder.parse_env(env); - builder - } - - /// Applies the configuration from the environment. - /// - /// This function allows a builder to be configured with default parameters, - /// to be then overridden by the environment. - /// - /// # Examples - /// - /// Initialise a logger with filter level `Off`, then override the log - /// filter from an environment variable called `MY_LOG`: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_env("MY_LOG"); - /// builder.init(); - /// ``` - /// - /// Initialise a logger with filter level `Off`, then use the `MY_LOG` - /// variable to override filtering and `MY_LOG_STYLE` to override whether - /// or not to write styles: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::{Builder, Env}; - /// - /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); - /// - /// let mut builder = Builder::new(); - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_env(env); - /// builder.init(); - /// ``` - pub fn parse_env<'a, E>(&mut self, env: E) -> &mut Self - where - E: Into>, - { - let env = env.into(); - - if let Some(s) = env.get_filter() { - self.parse_filters(&s); - } - - if let Some(s) = env.get_write_style() { - self.parse_write_style(&s); - } - - self - } - - /// Initializes the log builder from the environment using default variable names. - /// - /// This method is a convenient way to call `from_env(Env::default())` without - /// having to use the `Env` type explicitly. The builder will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Initialise a logger using the default environment variables: - /// - /// ``` - /// use env_logger::Builder; - /// - /// let mut builder = Builder::from_default_env(); - /// builder.init(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn from_default_env() -> Self { - Self::from_env(Env::default()) - } - - /// Applies the configuration from the environment using default variable names. - /// - /// This method is a convenient way to call `parse_env(Env::default())` without - /// having to use the `Env` type explicitly. The builder will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Initialise a logger with filter level `Off`, then configure it using the - /// default environment variables: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_default_env(); - /// builder.init(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn parse_default_env(&mut self) -> &mut Self { - self.parse_env(Env::default()) - } - - /// Sets the format function for formatting the log output. - /// - /// This function is called on each record logged and should format the - /// log record and output it to the given [`Formatter`]. - /// - /// The format function is expected to output the string directly to the - /// `Formatter` so that implementations can use the [`std::fmt`] macros - /// to format and output without intermediate heap allocations. The default - /// `env_logger` formatter takes advantage of this. - /// - /// # Examples - /// - /// Use a custom format to write only the log message: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// - /// builder.format(|buf, record| writeln!(buf, "{}", record.args())); - /// ``` - /// - /// [`Formatter`]: fmt/struct.Formatter.html - /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html - /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html - pub fn format(&mut self, format: F) -> &mut Self - where - F: Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send, - { - self.format.custom_format = Some(Box::new(format)); - self - } - - /// Use the default format. - /// - /// This method will clear any custom format set on the builder. - pub fn default_format(&mut self) -> &mut Self { - self.format = Default::default(); - self - } - - /// Whether or not to write the level in the default format. - pub fn format_level(&mut self, write: bool) -> &mut Self { - self.format.format_level = write; - self - } - - /// Whether or not to write the module path in the default format. - pub fn format_module_path(&mut self, write: bool) -> &mut Self { - self.format.format_module_path = write; - self - } - - /// Whether or not to write the target in the default format. - pub fn format_target(&mut self, write: bool) -> &mut Self { - self.format.format_target = write; - self - } - - /// Configures the amount of spaces to use to indent multiline log records. - /// A value of `None` disables any kind of indentation. - pub fn format_indent(&mut self, indent: Option) -> &mut Self { - self.format.format_indent = indent; - self - } - - /// Configures if timestamp should be included and in what precision. - pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { - self.format.format_timestamp = timestamp; - self - } - - /// Configures the timestamp to use second precision. - pub fn format_timestamp_secs(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Seconds)) - } - - /// Configures the timestamp to use millisecond precision. - pub fn format_timestamp_millis(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Millis)) - } - - /// Configures the timestamp to use microsecond precision. - pub fn format_timestamp_micros(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Micros)) - } - - /// Configures the timestamp to use nanosecond precision. - pub fn format_timestamp_nanos(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Nanos)) - } - - /// Configures the end of line suffix. - pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { - self.format.format_suffix = suffix; - self - } - - /// Adds a directive to the filter for a specific module. - /// - /// # Examples - /// - /// Only include messages for info and above for logs in `path::to::module`: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_module("path::to::module", LevelFilter::Info); - /// ``` - pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { - self.filter.filter_module(module, level); - self - } - - /// Adds a directive to the filter for all modules. - /// - /// # Examples - /// - /// Only include messages for info and above for logs globally: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_level(LevelFilter::Info); - /// ``` - pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { - self.filter.filter_level(level); - self - } - - /// Adds filters to the logger. - /// - /// The given module (if any) will log at most the specified level provided. - /// If no module is provided then the filter will apply to all log messages. - /// - /// # Examples - /// - /// Only include messages for info and above for logs in `path::to::module`: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter(Some("path::to::module"), LevelFilter::Info); - /// ``` - pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { - self.filter.filter(module, level); - self - } - - /// Parses the directives string in the same form as the `RUST_LOG` - /// environment variable. - /// - /// See the module documentation for more details. - pub fn parse_filters(&mut self, filters: &str) -> &mut Self { - self.filter.parse(filters); - self - } - - /// Sets the target for the log output. - /// - /// Env logger can log to either stdout, stderr or a custom pipe. The default is stderr. - /// - /// The custom pipe can be used to send the log messages to a custom sink (for example a file). - /// Do note that direct writes to a file can become a bottleneck due to IO operation times. - /// - /// # Examples - /// - /// Write log message to `stdout`: - /// - /// ``` - /// use env_logger::{Builder, Target}; - /// - /// let mut builder = Builder::new(); - /// - /// builder.target(Target::Stdout); - /// ``` - pub fn target(&mut self, target: fmt::Target) -> &mut Self { - self.writer.target(target); - self - } - - /// Sets whether or not styles will be written. - /// - /// This can be useful in environments that don't support control characters - /// for setting colors. - /// - /// # Examples - /// - /// Never attempt to write styles: - /// - /// ``` - /// use env_logger::{Builder, WriteStyle}; - /// - /// let mut builder = Builder::new(); - /// - /// builder.write_style(WriteStyle::Never); - /// ``` - pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { - self.writer.write_style(write_style); - self - } - - /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` - /// environment variable. - /// - /// See the module documentation for more details. - pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { - self.writer.parse_write_style(write_style); - self - } - - /// Sets whether or not the logger will be used in unit tests. - /// - /// If `is_test` is `true` then the logger will allow the testing framework to - /// capture log records rather than printing them to the terminal directly. - pub fn is_test(&mut self, is_test: bool) -> &mut Self { - self.writer.is_test(is_test); - self - } - - /// Initializes the global logger with the built env logger. - /// - /// This should be called early in the execution of a Rust program. Any log - /// events that occur before initialization will be ignored. - /// - /// # Errors - /// - /// This function will fail if it is called more than once, or if another - /// library has already initialized a global logger. - pub fn try_init(&mut self) -> Result<(), SetLoggerError> { - let logger = self.build(); - - let max_level = logger.filter(); - let r = log::set_boxed_logger(Box::new(logger)); - - if r.is_ok() { - log::set_max_level(max_level); - } - - r - } - - /// Initializes the global logger with the built env logger. - /// - /// This should be called early in the execution of a Rust program. Any log - /// events that occur before initialization will be ignored. - /// - /// # Panics - /// - /// This function will panic if it is called more than once, or if another - /// library has already initialized a global logger. - pub fn init(&mut self) { - self.try_init() - .expect("Builder::init should not be called after logger initialized"); - } - - /// Build an env logger. - /// - /// The returned logger implements the `Log` trait and can be installed manually - /// or nested within another logger. - pub fn build(&mut self) -> Logger { - assert!(!self.built, "attempt to re-use consumed builder"); - self.built = true; - - Logger { - writer: self.writer.build(), - filter: self.filter.build(), - format: self.format.build(), - } - } -} - -impl Logger { - /// Creates the logger from the environment. - /// - /// The variables used to read configuration from can be tweaked before - /// passing in. - /// - /// # Examples - /// - /// Create a logger reading the log filter from an environment variable - /// called `MY_LOG`: - /// - /// ``` - /// use env_logger::Logger; - /// - /// let logger = Logger::from_env("MY_LOG"); - /// ``` - /// - /// Create a logger using the `MY_LOG` variable for filtering and - /// `MY_LOG_STYLE` for whether or not to write styles: - /// - /// ``` - /// use env_logger::{Logger, Env}; - /// - /// let env = Env::new().filter_or("MY_LOG", "info").write_style_or("MY_LOG_STYLE", "always"); - /// - /// let logger = Logger::from_env(env); - /// ``` - pub fn from_env<'a, E>(env: E) -> Self - where - E: Into>, - { - Builder::from_env(env).build() - } - - /// Creates the logger from the environment using default variable names. - /// - /// This method is a convenient way to call `from_env(Env::default())` without - /// having to use the `Env` type explicitly. The logger will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Creates a logger using the default environment variables: - /// - /// ``` - /// use env_logger::Logger; - /// - /// let logger = Logger::from_default_env(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn from_default_env() -> Self { - Builder::from_default_env().build() - } - - /// Returns the maximum `LevelFilter` that this env logger instance is - /// configured to output. - pub fn filter(&self) -> LevelFilter { - self.filter.filter() - } - - /// Checks if this record matches the configured filter. - pub fn matches(&self, record: &Record) -> bool { - self.filter.matches(record) - } -} - -impl Log for Logger { - fn enabled(&self, metadata: &Metadata) -> bool { - self.filter.enabled(metadata) - } - - fn log(&self, record: &Record) { - if self.matches(record) { - // Log records are written to a thread-local buffer before being printed - // to the terminal. We clear these buffers afterwards, but they aren't shrinked - // so will always at least have capacity for the largest log record formatted - // on that thread. - // - // If multiple `Logger`s are used by the same threads then the thread-local - // formatter might have different color support. If this is the case the - // formatter and its buffer are discarded and recreated. - - thread_local! { - static FORMATTER: RefCell> = RefCell::new(None); - } - - let print = |formatter: &mut Formatter, record: &Record| { - let _ = - (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); - - // Always clear the buffer afterwards - formatter.clear(); - }; - - let printed = FORMATTER - .try_with(|tl_buf| { - match tl_buf.try_borrow_mut() { - // There are no active borrows of the buffer - Ok(mut tl_buf) => match *tl_buf { - // We have a previously set formatter - Some(ref mut formatter) => { - // Check the buffer style. If it's different from the logger's - // style then drop the buffer and recreate it. - if formatter.write_style() != self.writer.write_style() { - *formatter = Formatter::new(&self.writer); - } - - print(formatter, record); - } - // We don't have a previously set formatter - None => { - let mut formatter = Formatter::new(&self.writer); - print(&mut formatter, record); - - *tl_buf = Some(formatter); - } - }, - // There's already an active borrow of the buffer (due to re-entrancy) - Err(_) => { - print(&mut Formatter::new(&self.writer), record); - } - } - }) - .is_ok(); - - if !printed { - // The thread-local storage was not available (because its - // destructor has already run). Create a new single-use - // Formatter on the stack for this call. - print(&mut Formatter::new(&self.writer), record); - } - } - } - - fn flush(&self) {} -} - -impl<'a> Env<'a> { - /// Get a default set of environment variables. - pub fn new() -> Self { - Self::default() - } - - /// Specify an environment variable to read the filter from. - pub fn filter(mut self, filter_env: E) -> Self - where - E: Into>, - { - self.filter = Var::new(filter_env); - - self - } - - /// Specify an environment variable to read the filter from. - /// - /// If the variable is not set, the default value will be used. - pub fn filter_or(mut self, filter_env: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - self.filter = Var::new_with_default(filter_env, default); - - self - } - - /// Use the default environment variable to read the filter from. - /// - /// If the variable is not set, the default value will be used. - pub fn default_filter_or(mut self, default: V) -> Self - where - V: Into>, - { - self.filter = Var::new_with_default(DEFAULT_FILTER_ENV, default); - - self - } - - fn get_filter(&self) -> Option { - self.filter.get() - } - - /// Specify an environment variable to read the style from. - pub fn write_style(mut self, write_style_env: E) -> Self - where - E: Into>, - { - self.write_style = Var::new(write_style_env); - - self - } - - /// Specify an environment variable to read the style from. - /// - /// If the variable is not set, the default value will be used. - pub fn write_style_or(mut self, write_style_env: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - self.write_style = Var::new_with_default(write_style_env, default); - - self - } - - /// Use the default environment variable to read the style from. - /// - /// If the variable is not set, the default value will be used. - pub fn default_write_style_or(mut self, default: V) -> Self - where - V: Into>, - { - self.write_style = Var::new_with_default(DEFAULT_WRITE_STYLE_ENV, default); - - self - } - - fn get_write_style(&self) -> Option { - self.write_style.get() - } -} - -impl<'a> Var<'a> { - fn new(name: E) -> Self - where - E: Into>, - { - Var { - name: name.into(), - default: None, - } - } - - fn new_with_default(name: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - Var { - name: name.into(), - default: Some(default.into()), - } - } - - fn get(&self) -> Option { - env::var(&*self.name) - .ok() - .or_else(|| self.default.to_owned().map(|v| v.into_owned())) - } -} - -impl<'a, T> From for Env<'a> -where - T: Into>, -{ - fn from(filter_env: T) -> Self { - Env::default().filter(filter_env.into()) - } -} - -impl<'a> Default for Env<'a> { - fn default() -> Self { - Env { - filter: Var::new(DEFAULT_FILTER_ENV), - write_style: Var::new(DEFAULT_WRITE_STYLE_ENV), - } - } -} - -mod std_fmt_impls { - use super::*; - use std::fmt; - - impl fmt::Debug for Logger { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Logger") - .field("filter", &self.filter) - .finish() - } - } - - impl fmt::Debug for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.built { - f.debug_struct("Logger").field("built", &true).finish() - } else { - f.debug_struct("Logger") - .field("filter", &self.filter) - .field("writer", &self.writer) - .finish() - } - } - } -} - -/// Attempts to initialize the global logger with an env logger. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Errors -/// -/// This function will fail if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn try_init() -> Result<(), SetLoggerError> { - try_init_from_env(Env::default()) -} - -/// Initializes the global logger with an env logger. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Panics -/// -/// This function will panic if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn init() { - try_init().expect("env_logger::init should not be called after logger initialized"); -} - -/// Attempts to initialize the global logger with an env logger from the given -/// environment variables. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Examples -/// -/// Initialise a logger using the `MY_LOG` environment variable for filters -/// and `MY_LOG_STYLE` for writing colors: -/// -/// ``` -/// use env_logger::{Builder, Env}; -/// -/// # fn run() -> Result<(), Box<::std::error::Error>> { -/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); -/// -/// env_logger::try_init_from_env(env)?; -/// -/// Ok(()) -/// # } -/// # run().unwrap(); -/// ``` -/// -/// # Errors -/// -/// This function will fail if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn try_init_from_env<'a, E>(env: E) -> Result<(), SetLoggerError> -where - E: Into>, -{ - let mut builder = Builder::from_env(env); - - builder.try_init() -} - -/// Initializes the global logger with an env logger from the given environment -/// variables. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Examples -/// -/// Initialise a logger using the `MY_LOG` environment variable for filters -/// and `MY_LOG_STYLE` for writing colors: -/// -/// ``` -/// use env_logger::{Builder, Env}; -/// -/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); -/// -/// env_logger::init_from_env(env); -/// ``` -/// -/// # Panics -/// -/// This function will panic if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn init_from_env<'a, E>(env: E) -where - E: Into>, -{ - try_init_from_env(env) - .expect("env_logger::init_from_env should not be called after logger initialized"); -} - -/// Create a new builder with the default environment variables. -/// -/// The builder can be configured before being initialized. -/// This is a convenient way of calling [`Builder::from_default_env`]. -/// -/// [`Builder::from_default_env`]: struct.Builder.html#method.from_default_env -pub fn builder() -> Builder { - Builder::from_default_env() -} - -/// Create a builder from the given environment variables. -/// -/// The builder can be configured before being initialized. -#[deprecated( - since = "0.8.0", - note = "Prefer `env_logger::Builder::from_env()` instead." -)] -pub fn from_env<'a, E>(env: E) -> Builder -where - E: Into>, -{ - Builder::from_env(env) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn env_get_filter_reads_from_var_if_set() { - env::set_var("env_get_filter_reads_from_var_if_set", "from var"); - - let env = Env::new().filter_or("env_get_filter_reads_from_var_if_set", "from default"); - - assert_eq!(Some("from var".to_owned()), env.get_filter()); - } - - #[test] - fn env_get_filter_reads_from_default_if_var_not_set() { - env::remove_var("env_get_filter_reads_from_default_if_var_not_set"); - - let env = Env::new().filter_or( - "env_get_filter_reads_from_default_if_var_not_set", - "from default", - ); - - assert_eq!(Some("from default".to_owned()), env.get_filter()); - } - - #[test] - fn env_get_write_style_reads_from_var_if_set() { - env::set_var("env_get_write_style_reads_from_var_if_set", "from var"); - - let env = - Env::new().write_style_or("env_get_write_style_reads_from_var_if_set", "from default"); - - assert_eq!(Some("from var".to_owned()), env.get_write_style()); - } - - #[test] - fn env_get_write_style_reads_from_default_if_var_not_set() { - env::remove_var("env_get_write_style_reads_from_default_if_var_not_set"); - - let env = Env::new().write_style_or( - "env_get_write_style_reads_from_default_if_var_not_set", - "from default", - ); - - assert_eq!(Some("from default".to_owned()), env.get_write_style()); - } - - #[test] - fn builder_parse_env_overrides_existing_filters() { - env::set_var( - "builder_parse_default_env_overrides_existing_filters", - "debug", - ); - let env = Env::new().filter("builder_parse_default_env_overrides_existing_filters"); - - let mut builder = Builder::new(); - builder.filter_level(LevelFilter::Trace); - // Overrides global level to debug - builder.parse_env(env); - - assert_eq!(builder.filter.build().filter(), LevelFilter::Debug); - } -} +pub use self::logger::*; diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..6c8a00d --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,1016 @@ +use std::{borrow::Cow, cell::RefCell, env, io}; + +use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; + +use crate::filter; +use crate::filter::Filter; +use crate::fmt; +use crate::fmt::writer::{self, Writer}; +use crate::fmt::{FormatFn, Formatter}; + +/// The default name for the environment variable to read filters from. +pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; + +/// The default name for the environment variable to read style preferences from. +pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; + +/// `Builder` acts as builder for initializing a `Logger`. +/// +/// It can be used to customize the log format, change the environment variable used +/// to provide the logging directives and also set the default log level filter. +/// +/// # Examples +/// +/// ``` +/// # use std::io::Write; +/// use env_logger::Builder; +/// use log::{LevelFilter, error, info}; +/// +/// let mut builder = Builder::from_default_env(); +/// +/// builder +/// .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) +/// .filter(None, LevelFilter::Info) +/// .init(); +/// +/// error!("error message"); +/// info!("info message"); +/// ``` +#[derive(Default)] +pub struct Builder { + filter: filter::Builder, + writer: writer::Builder, + format: fmt::Builder, + built: bool, +} + +impl Builder { + /// Initializes the log builder with defaults. + /// + /// **NOTE:** This method won't read from any environment variables. + /// Use the [`filter`] and [`write_style`] methods to configure the builder + /// or use [`from_env`] or [`from_default_env`] instead. + /// + /// # Examples + /// + /// Create a new builder and configure filters and style: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder + /// .filter(None, LevelFilter::Info) + /// .write_style(WriteStyle::Always) + /// .init(); + /// ``` + /// + /// [`filter`]: #method.filter + /// [`write_style`]: #method.write_style + /// [`from_env`]: #method.from_env + /// [`from_default_env`]: #method.from_default_env + pub fn new() -> Builder { + Default::default() + } + + /// Initializes the log builder from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Initialise a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::from_env(env); + /// builder.init(); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + let mut builder = Builder::new(); + builder.parse_env(env); + builder + } + + /// Applies the configuration from the environment. + /// + /// This function allows a builder to be configured with default parameters, + /// to be then overridden by the environment. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then override the log + /// filter from an environment variable called `MY_LOG`: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger with filter level `Off`, then use the `MY_LOG` + /// variable to override filtering and `MY_LOG_STYLE` to override whether + /// or not to write styles: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env(env); + /// builder.init(); + /// ``` + pub fn parse_env<'a, E>(&mut self, env: E) -> &mut Self + where + E: Into>, + { + let env = env.into(); + + if let Some(s) = env.get_filter() { + self.parse_filters(&s); + } + + if let Some(s) = env.get_write_style() { + self.parse_write_style(&s); + } + + self + } + + /// Initializes the log builder from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Self::from_env(Env::default()) + } + + /// Applies the configuration from the environment using default variable names. + /// + /// This method is a convenient way to call `parse_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then configure it using the + /// default environment variables: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn parse_default_env(&mut self) -> &mut Self { + self.parse_env(Env::default()) + } + + /// Sets the format function for formatting the log output. + /// + /// This function is called on each record logged and should format the + /// log record and output it to the given [`Formatter`]. + /// + /// The format function is expected to output the string directly to the + /// `Formatter` so that implementations can use the [`std::fmt`] macros + /// to format and output without intermediate heap allocations. The default + /// `env_logger` formatter takes advantage of this. + /// + /// # Examples + /// + /// Use a custom format to write only the log message: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.format(|buf, record| writeln!(buf, "{}", record.args())); + /// ``` + /// + /// [`Formatter`]: fmt/struct.Formatter.html + /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html + /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html + pub fn format(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send, + { + self.format.custom_format = Some(Box::new(format)); + self + } + + /// Use the default format. + /// + /// This method will clear any custom format set on the builder. + pub fn default_format(&mut self) -> &mut Self { + self.format = Default::default(); + self + } + + /// Whether or not to write the level in the default format. + pub fn format_level(&mut self, write: bool) -> &mut Self { + self.format.format_level = write; + self + } + + /// Whether or not to write the module path in the default format. + pub fn format_module_path(&mut self, write: bool) -> &mut Self { + self.format.format_module_path = write; + self + } + + /// Whether or not to write the target in the default format. + pub fn format_target(&mut self, write: bool) -> &mut Self { + self.format.format_target = write; + self + } + + /// Configures the amount of spaces to use to indent multiline log records. + /// A value of `None` disables any kind of indentation. + pub fn format_indent(&mut self, indent: Option) -> &mut Self { + self.format.format_indent = indent; + self + } + + /// Configures if timestamp should be included and in what precision. + pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { + self.format.format_timestamp = timestamp; + self + } + + /// Configures the timestamp to use second precision. + pub fn format_timestamp_secs(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Seconds)) + } + + /// Configures the timestamp to use millisecond precision. + pub fn format_timestamp_millis(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Millis)) + } + + /// Configures the timestamp to use microsecond precision. + pub fn format_timestamp_micros(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Micros)) + } + + /// Configures the timestamp to use nanosecond precision. + pub fn format_timestamp_nanos(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Nanos)) + } + + /// Configures the end of line suffix. + pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { + self.format.format_suffix = suffix; + self + } + + /// Adds a directive to the filter for a specific module. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_module("path::to::module", LevelFilter::Info); + /// ``` + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter.filter_module(module, level); + self + } + + /// Adds a directive to the filter for all modules. + /// + /// # Examples + /// + /// Only include messages for info and above for logs globally: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Info); + /// ``` + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter.filter_level(level); + self + } + + /// Adds filters to the logger. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter(Some("path::to::module"), LevelFilter::Info); + /// ``` + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.filter.filter(module, level); + self + } + + /// Parses the directives string in the same form as the `RUST_LOG` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_filters(&mut self, filters: &str) -> &mut Self { + self.filter.parse(filters); + self + } + + /// Sets the target for the log output. + /// + /// Env logger can log to either stdout, stderr or a custom pipe. The default is stderr. + /// + /// The custom pipe can be used to send the log messages to a custom sink (for example a file). + /// Do note that direct writes to a file can become a bottleneck due to IO operation times. + /// + /// # Examples + /// + /// Write log message to `stdout`: + /// + /// ``` + /// use env_logger::{Builder, Target}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.target(Target::Stdout); + /// ``` + pub fn target(&mut self, target: fmt::Target) -> &mut Self { + self.writer.target(target); + self + } + + /// Sets whether or not styles will be written. + /// + /// This can be useful in environments that don't support control characters + /// for setting colors. + /// + /// # Examples + /// + /// Never attempt to write styles: + /// + /// ``` + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.write_style(WriteStyle::Never); + /// ``` + pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { + self.writer.write_style(write_style); + self + } + + /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { + self.writer.parse_write_style(write_style); + self + } + + /// Sets whether or not the logger will be used in unit tests. + /// + /// If `is_test` is `true` then the logger will allow the testing framework to + /// capture log records rather than printing them to the terminal directly. + pub fn is_test(&mut self, is_test: bool) -> &mut Self { + self.writer.is_test(is_test); + self + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Errors + /// + /// This function will fail if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn try_init(&mut self) -> Result<(), SetLoggerError> { + let logger = self.build(); + + let max_level = logger.filter(); + let r = log::set_boxed_logger(Box::new(logger)); + + if r.is_ok() { + log::set_max_level(max_level); + } + + r + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Panics + /// + /// This function will panic if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn init(&mut self) { + self.try_init() + .expect("Builder::init should not be called after logger initialized"); + } + + /// Build an env logger. + /// + /// The returned logger implements the `Log` trait and can be installed manually + /// or nested within another logger. + pub fn build(&mut self) -> Logger { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + Logger { + writer: self.writer.build(), + filter: self.filter.build(), + format: self.format.build(), + } + } +} + +impl std::fmt::Debug for Builder { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.built { + f.debug_struct("Logger").field("built", &true).finish() + } else { + f.debug_struct("Logger") + .field("filter", &self.filter) + .field("writer", &self.writer) + .finish() + } + } +} + +/// The env logger. +/// +/// This struct implements the `Log` trait from the [`log` crate][log-crate-url], +/// which allows it to act as a logger. +/// +/// The [`init()`], [`try_init()`], [`Builder::init()`] and [`Builder::try_init()`] +/// methods will each construct a `Logger` and immediately initialize it as the +/// default global logger. +/// +/// If you'd instead need access to the constructed `Logger`, you can use +/// the associated [`Builder`] and install it with the +/// [`log` crate][log-crate-url] directly. +/// +/// [log-crate-url]: https://docs.rs/log +/// [`init()`]: fn.init.html +/// [`try_init()`]: fn.try_init.html +/// [`Builder::init()`]: struct.Builder.html#method.init +/// [`Builder::try_init()`]: struct.Builder.html#method.try_init +/// [`Builder`]: struct.Builder.html +pub struct Logger { + writer: Writer, + filter: Filter, + format: FormatFn, +} + +impl Logger { + /// Creates the logger from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Create a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_env("MY_LOG"); + /// ``` + /// + /// Create a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Logger, Env}; + /// + /// let env = Env::new().filter_or("MY_LOG", "info").write_style_or("MY_LOG_STYLE", "always"); + /// + /// let logger = Logger::from_env(env); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + Builder::from_env(env).build() + } + + /// Creates the logger from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The logger will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Creates a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_default_env(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Builder::from_default_env().build() + } + + /// Returns the maximum `LevelFilter` that this env logger instance is + /// configured to output. + pub fn filter(&self) -> LevelFilter { + self.filter.filter() + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record) -> bool { + self.filter.matches(record) + } +} + +impl Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.filter.enabled(metadata) + } + + fn log(&self, record: &Record) { + if self.matches(record) { + // Log records are written to a thread-local buffer before being printed + // to the terminal. We clear these buffers afterwards, but they aren't shrunk + // so will always at least have capacity for the largest log record formatted + // on that thread. + // + // If multiple `Logger`s are used by the same threads then the thread-local + // formatter might have different color support. If this is the case the + // formatter and its buffer are discarded and recreated. + + thread_local! { + static FORMATTER: RefCell> = RefCell::new(None); + } + + let print = |formatter: &mut Formatter, record: &Record| { + let _ = + (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); + + // Always clear the buffer afterwards + formatter.clear(); + }; + + let printed = FORMATTER + .try_with(|tl_buf| { + match tl_buf.try_borrow_mut() { + // There are no active borrows of the buffer + Ok(mut tl_buf) => match *tl_buf { + // We have a previously set formatter + Some(ref mut formatter) => { + // Check the buffer style. If it's different from the logger's + // style then drop the buffer and recreate it. + if formatter.write_style() != self.writer.write_style() { + *formatter = Formatter::new(&self.writer); + } + + print(formatter, record); + } + // We don't have a previously set formatter + None => { + let mut formatter = Formatter::new(&self.writer); + print(&mut formatter, record); + + *tl_buf = Some(formatter); + } + }, + // There's already an active borrow of the buffer (due to re-entrancy) + Err(_) => { + print(&mut Formatter::new(&self.writer), record); + } + } + }) + .is_ok(); + + if !printed { + // The thread-local storage was not available (because its + // destructor has already run). Create a new single-use + // Formatter on the stack for this call. + print(&mut Formatter::new(&self.writer), record); + } + } + } + + fn flush(&self) {} +} + +impl std::fmt::Debug for Logger { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Logger") + .field("filter", &self.filter) + .finish() + } +} + +/// Set of environment variables to configure from. +/// +/// # Default environment variables +/// +/// By default, the `Env` will read the following environment variables: +/// +/// - `RUST_LOG`: the level filter +/// - `RUST_LOG_STYLE`: whether or not to print styles with records. +/// +/// These sources can be configured using the builder methods on `Env`. +#[derive(Debug)] +pub struct Env<'a> { + filter: Var<'a>, + write_style: Var<'a>, +} + +impl<'a> Env<'a> { + /// Get a default set of environment variables. + pub fn new() -> Self { + Self::default() + } + + /// Specify an environment variable to read the filter from. + pub fn filter(mut self, filter_env: E) -> Self + where + E: Into>, + { + self.filter = Var::new(filter_env); + + self + } + + /// Specify an environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn filter_or(mut self, filter_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.filter = Var::new_with_default(filter_env, default); + + self + } + + /// Use the default environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_filter_or(mut self, default: V) -> Self + where + V: Into>, + { + self.filter = Var::new_with_default(DEFAULT_FILTER_ENV, default); + + self + } + + fn get_filter(&self) -> Option { + self.filter.get() + } + + /// Specify an environment variable to read the style from. + pub fn write_style(mut self, write_style_env: E) -> Self + where + E: Into>, + { + self.write_style = Var::new(write_style_env); + + self + } + + /// Specify an environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn write_style_or(mut self, write_style_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.write_style = Var::new_with_default(write_style_env, default); + + self + } + + /// Use the default environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_write_style_or(mut self, default: V) -> Self + where + V: Into>, + { + self.write_style = Var::new_with_default(DEFAULT_WRITE_STYLE_ENV, default); + + self + } + + fn get_write_style(&self) -> Option { + self.write_style.get() + } +} + +impl<'a, T> From for Env<'a> +where + T: Into>, +{ + fn from(filter_env: T) -> Self { + Env::default().filter(filter_env.into()) + } +} + +impl<'a> Default for Env<'a> { + fn default() -> Self { + Env { + filter: Var::new(DEFAULT_FILTER_ENV), + write_style: Var::new(DEFAULT_WRITE_STYLE_ENV), + } + } +} + +#[derive(Debug)] +struct Var<'a> { + name: Cow<'a, str>, + default: Option>, +} + +impl<'a> Var<'a> { + fn new(name: E) -> Self + where + E: Into>, + { + Var { + name: name.into(), + default: None, + } + } + + fn new_with_default(name: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + Var { + name: name.into(), + default: Some(default.into()), + } + } + + fn get(&self) -> Option { + env::var(&*self.name) + .ok() + .or_else(|| self.default.to_owned().map(|v| v.into_owned())) + } +} + +/// Attempts to initialize the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init() -> Result<(), SetLoggerError> { + try_init_from_env(Env::default()) +} + +/// Initializes the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init() { + try_init().expect("env_logger::init should not be called after logger initialized"); +} + +/// Attempts to initialize the global logger with an env logger from the given +/// environment variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// # fn run() -> Result<(), Box> { +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::try_init_from_env(env)?; +/// +/// Ok(()) +/// # } +/// # run().unwrap(); +/// ``` +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init_from_env<'a, E>(env: E) -> Result<(), SetLoggerError> +where + E: Into>, +{ + let mut builder = Builder::from_env(env); + + builder.try_init() +} + +/// Initializes the global logger with an env logger from the given environment +/// variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::init_from_env(env); +/// ``` +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init_from_env<'a, E>(env: E) +where + E: Into>, +{ + try_init_from_env(env) + .expect("env_logger::init_from_env should not be called after logger initialized"); +} + +/// Create a new builder with the default environment variables. +/// +/// The builder can be configured before being initialized. +/// This is a convenient way of calling [`Builder::from_default_env`]. +/// +/// [`Builder::from_default_env`]: struct.Builder.html#method.from_default_env +pub fn builder() -> Builder { + Builder::from_default_env() +} + +/// Create a builder from the given environment variables. +/// +/// The builder can be configured before being initialized. +#[deprecated( + since = "0.8.0", + note = "Prefer `env_logger::Builder::from_env()` instead." +)] +pub fn from_env<'a, E>(env: E) -> Builder +where + E: Into>, +{ + Builder::from_env(env) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn env_get_filter_reads_from_var_if_set() { + env::set_var("env_get_filter_reads_from_var_if_set", "from var"); + + let env = Env::new().filter_or("env_get_filter_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_filter_reads_from_default_if_var_not_set() { + env::remove_var("env_get_filter_reads_from_default_if_var_not_set"); + + let env = Env::new().filter_or( + "env_get_filter_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_write_style_reads_from_var_if_set() { + env::set_var("env_get_write_style_reads_from_var_if_set", "from var"); + + let env = + Env::new().write_style_or("env_get_write_style_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_write_style()); + } + + #[test] + fn env_get_write_style_reads_from_default_if_var_not_set() { + env::remove_var("env_get_write_style_reads_from_default_if_var_not_set"); + + let env = Env::new().write_style_or( + "env_get_write_style_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_write_style()); + } + + #[test] + fn builder_parse_env_overrides_existing_filters() { + env::set_var( + "builder_parse_default_env_overrides_existing_filters", + "debug", + ); + let env = Env::new().filter("builder_parse_default_env_overrides_existing_filters"); + + let mut builder = Builder::new(); + builder.filter_level(LevelFilter::Trace); + // Overrides global level to debug + builder.parse_env(env); + + assert_eq!(builder.filter.build().filter(), LevelFilter::Debug); + } +}