diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d96b24c1d..3f5988fbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,16 @@ name: CI on: [push, pull_request] +# Ensure only read permission is granted +permissions: + contents: read + jobs: test: name: Test runs-on: ${{ matrix.os }} strategy: matrix: - build: [stable, beta, nightly, macos, win32, win64, mingw] include: - build: stable os: ubuntu-latest @@ -23,61 +26,109 @@ jobs: rust: stable - build: win32 os: windows-latest - rust: stable-i686 + rust: stable-i686-pc-windows-msvc - build: win64 os: windows-latest - rust: stable-x86_64 + rust: stable-x86_64-pc-windows-msvc - build: mingw os: windows-latest - rust: stable-x86_64-gnu + rust: stable-x86_64-pc-windows-gnu steps: - - uses: actions/checkout@master - - name: Install Rust (rustup) + - uses: actions/checkout@v4 + - name: Install toolchain run: | rustup update ${{ matrix.rust }} --no-self-update rustup default ${{ matrix.rust }} - - run: cargo test --verbose - - run: cargo test --verbose --features serde - - run: cargo test --verbose --features std - - run: cargo test --verbose --features kv_unstable - - run: cargo test --verbose --features "kv_unstable std" - - run: cargo test --verbose --features "kv_unstable_sval" + cargo +stable install cargo-hack --locked + - run: cargo hack test --feature-powerset --exclude-features max_level_off,max_level_error,max_level_warn,max_level_info,max_level_debug,max_level_trace,release_max_level_off,release_max_level_error,release_max_level_warn,release_max_level_info,release_max_level_debug,release_max_level_trace - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml --release - rustfmt: - name: Rustfmt + check: + name: Check Format and Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Install Rust + - uses: actions/checkout@v4 + - name: Install toolchain run: | rustup update stable --no-self-update rustup default stable - rustup component add rustfmt + rustup component add clippy rustfmt - run: cargo fmt -- --check + - run: cargo fmt --manifest-path test_max_level_features/Cargo.toml -- --check + - run: cargo clippy --verbose + - run: cargo clippy --verbose --manifest-path test_max_level_features/Cargo.toml + + doc: + name: Check Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + run: | + rustup update stable --no-self-update + rustup default stable + rustup component add rust-docs + - name: Run rustdoc + env: + RUSTDOCFLAGS: "-D warnings" + run: cargo doc --verbose --features std,serde,sval,sval_ref,value-bag,kv,kv_std,kv_sval,kv_serde + + features: + name: Feature check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + run: | + rustup update nightly --no-self-update + rustup default nightly + - run: cargo build --verbose -Z avoid-dev-deps --features kv + - run: cargo build --verbose -Z avoid-dev-deps --features "kv std" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_sval" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_serde" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_std" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_sval kv_serde" + + minimalv: + name: Minimal versions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + run: | + rustup update nightly --no-self-update + rustup default nightly + - run: cargo build --verbose -Z minimal-versions --features kv + - run: cargo build --verbose -Z minimal-versions --features "kv std" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_sval" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_serde" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_std" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_sval kv_serde" msrv: name: MSRV runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Install Rust + - uses: actions/checkout@v4 + - name: Install toolchain run: | - rustup update 1.31.0 --no-self-update - rustup default 1.31.0 - - run: cargo build --verbose - - run: cargo build --verbose --features serde - - run: cargo build --verbose --features std + rustup update 1.61.0 --no-self-update + rustup default 1.61.0 + cargo +stable install cargo-hack --locked + - run: cargo hack test --feature-powerset --exclude-features max_level_off,max_level_error,max_level_warn,max_level_info,max_level_debug,max_level_trace,release_max_level_off,release_max_level_error,release_max_level_warn,release_max_level_info,release_max_level_debug,release_max_level_trace + - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml + - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml --release embedded: name: Embedded runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Install Rust + - uses: actions/checkout@v4 + - name: Install toolchain run: | rustup update stable --no-self-update rustup default stable - - run: rustup target add thumbv6m-none-eabi + - run: rustup target add thumbv6m-none-eabi riscv32imc-unknown-none-elf - run: cargo build --verbose --target=thumbv6m-none-eabi + - run: cargo build --verbose --target=riscv32imc-unknown-none-elf diff --git a/.gitignore b/.gitignore index 2c96eb1b6..150301459 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ Cargo.lock +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa8be0c5..48f669334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,213 @@ ## [Unreleased] +## [0.4.28] - 2025-09-02 + +## What's Changed +* ci: drop really old trick and ensure MSRV for all feature combo by @tisonkun in https://github.com/rust-lang/log/pull/676 +* Chore: delete compare_exchange method for AtomicUsize on platforms without atomics by @HaoliangXu in https://github.com/rust-lang/log/pull/690 +* Add `increment_severity()` and `decrement_severity()` methods for `Level` and `LevelFilter` by @nebkor in https://github.com/rust-lang/log/pull/692 + +## New Contributors +* @xixishidibei made their first contribution in https://github.com/rust-lang/log/pull/677 +* @ZylosLumen made their first contribution in https://github.com/rust-lang/log/pull/688 +* @HaoliangXu made their first contribution in https://github.com/rust-lang/log/pull/690 +* @nebkor made their first contribution in https://github.com/rust-lang/log/pull/692 + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.27...0.4.28 + +### Notable Changes +* MSRV is bumped to 1.61.0 in https://github.com/rust-lang/log/pull/676 + +## [0.4.27] - 2025-03-24 + +### What's Changed +* A few minor lint fixes by @nyurik in https://github.com/rust-lang/log/pull/671 +* Enable clippy support for format-like macros by @nyurik in https://github.com/rust-lang/log/pull/665 +* Add an optional logger param by @tisonkun in https://github.com/rust-lang/log/pull/664 +* Pass global logger by value, supplied logger by ref by @KodrAus in https://github.com/rust-lang/log/pull/673 + + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.26...0.4.27 + + +## [0.4.26] - 2025-02-18 + +### What's Changed +* Derive `Clone` for `kv::Value` by @SpriteOvO in https://github.com/rust-lang/log/pull/668 +* Add `spdlog-rs` link to crate doc by @SpriteOvO in https://github.com/rust-lang/log/pull/669 + + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.25...0.4.26 + +## [0.4.25] - 2025-01-14 + +### What's Changed +* Revert loosening of kv cargo features by @KodrAus in https://github.com/rust-lang/log/pull/662 + + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.24...0.4.25 + +## [0.4.24] - 2025-01-11 + +### What's Changed +* Fix up kv feature activation by @KodrAus in https://github.com/rust-lang/log/pull/659 + + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.23...0.4.24 + +## [0.4.23] - 2025-01-10 (yanked) + +### What's Changed +* Fix some typos by @Kleinmarb in https://github.com/rust-lang/log/pull/637 +* Add logforth to implementation by @tisonkun in https://github.com/rust-lang/log/pull/638 +* Add `spdlog-rs` link to README by @SpriteOvO in https://github.com/rust-lang/log/pull/639 +* Add correct lifetime to kv::Value::to_borrowed_str by @stevenroose in https://github.com/rust-lang/log/pull/643 +* docs: Add logforth as an impl by @tisonkun in https://github.com/rust-lang/log/pull/642 +* Add clang_log implementation by @DDAN-17 in https://github.com/rust-lang/log/pull/646 +* Bind lifetimes of &str returned from Key by the lifetime of 'k rather than the lifetime of the Key struct by @gbbosak in https://github.com/rust-lang/log/pull/648 +* Fix up key lifetimes and add method to try get a borrowed key by @KodrAus in https://github.com/rust-lang/log/pull/653 +* Add Ftail implementation by @tjardoo in https://github.com/rust-lang/log/pull/652 + +### New Contributors +* @Kleinmarb made their first contribution in https://github.com/rust-lang/log/pull/637 +* @tisonkun made their first contribution in https://github.com/rust-lang/log/pull/638 +* @SpriteOvO made their first contribution in https://github.com/rust-lang/log/pull/639 +* @stevenroose made their first contribution in https://github.com/rust-lang/log/pull/643 +* @DDAN-17 made their first contribution in https://github.com/rust-lang/log/pull/646 +* @gbbosak made their first contribution in https://github.com/rust-lang/log/pull/648 +* @tjardoo made their first contribution in https://github.com/rust-lang/log/pull/652 + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.22...0.4.23 + +## [0.4.22] - 2024-06-27 + +### What's Changed +* Add some clarifications to the library docs by @KodrAus in https://github.com/rust-lang/log/pull/620 +* Add links to `colog` crate by @chrivers in https://github.com/rust-lang/log/pull/621 +* adding line_number test + updating some testing infrastructure by @DIvkov575 in https://github.com/rust-lang/log/pull/619 +* Clarify the actual set of functions that can race in _racy variants by @KodrAus in https://github.com/rust-lang/log/pull/623 +* Replace deprecated std::sync::atomic::spin_loop_hint() by @Catamantaloedis in https://github.com/rust-lang/log/pull/625 +* Check usage of max_level features by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/627 +* Remove unneeded import by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/628 +* Loosen orderings for logger initialization in https://github.com/rust-lang/log/pull/632. Originally by @pwoolcoc in https://github.com/rust-lang/log/pull/599 +* Use Location::caller() for file and line info in https://github.com/rust-lang/log/pull/633. Originally by @Cassy343 in https://github.com/rust-lang/log/pull/520 + +### New Contributors +* @chrivers made their first contribution in https://github.com/rust-lang/log/pull/621 +* @DIvkov575 made their first contribution in https://github.com/rust-lang/log/pull/619 +* @Catamantaloedis made their first contribution in https://github.com/rust-lang/log/pull/625 + +**Full Changelog**: https://github.com/rust-lang/log/compare/0.4.21...0.4.22 + +## [0.4.21] - 2024-02-27 + +### What's Changed +* Minor clippy nits by @nyurik in https://github.com/rust-lang/log/pull/578 +* Simplify Display impl by @nyurik in https://github.com/rust-lang/log/pull/579 +* Set all crates to 2021 edition by @nyurik in https://github.com/rust-lang/log/pull/580 +* Various changes based on review by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/583 +* Fix typo in file_static() method doc by @dimo414 in https://github.com/rust-lang/log/pull/590 +* Specialize empty key value pairs by @EFanZh in https://github.com/rust-lang/log/pull/576 +* Fix incorrect lifetime in Value::to_str() by @peterjoel in https://github.com/rust-lang/log/pull/587 +* Remove some API of the key-value feature by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/585 +* Add logcontrol-log and log-reload by @swsnr in https://github.com/rust-lang/log/pull/595 +* Add Serialization section to kv::Value docs by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/593 +* Rename Value::to_str to to_cow_str by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/592 +* Clarify documentation and simplify initialization of `STATIC_MAX_LEVEL` by @ptosi in https://github.com/rust-lang/log/pull/594 +* Update docs to 2021 edition, test by @nyurik in https://github.com/rust-lang/log/pull/577 +* Add "alterable_logger" link to README.md by @brummer-simon in https://github.com/rust-lang/log/pull/589 +* Normalize line ending by @EFanZh in https://github.com/rust-lang/log/pull/602 +* Remove `ok_or` in favor of `Option::ok_or` by @AngelicosPhosphoros in https://github.com/rust-lang/log/pull/607 +* Use `Acquire` ordering for initialization check by @AngelicosPhosphoros in https://github.com/rust-lang/log/pull/610 +* Get structured logging API ready for stabilization by @KodrAus in https://github.com/rust-lang/log/pull/613 + +### New Contributors +* @nyurik made their first contribution in https://github.com/rust-lang/log/pull/578 +* @dimo414 made their first contribution in https://github.com/rust-lang/log/pull/590 +* @peterjoel made their first contribution in https://github.com/rust-lang/log/pull/587 +* @ptosi made their first contribution in https://github.com/rust-lang/log/pull/594 +* @brummer-simon made their first contribution in https://github.com/rust-lang/log/pull/589 +* @AngelicosPhosphoros made their first contribution in https://github.com/rust-lang/log/pull/607 + +## [0.4.20] - 2023-07-11 + +* Remove rustversion dev-dependency by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/568 +* Remove `local_inner_macros` usage by @EFanZh in https://github.com/rust-lang/log/pull/570 + +## [0.4.19] - 2023-06-10 + +* Use target_has_atomic instead of the old atomic_cas cfg by @GuillaumeGomez in https://github.com/rust-lang/log/pull/555 +* Put MSRV into Cargo.toml by @est31 in https://github.com/rust-lang/log/pull/557 + +## [0.4.18] - 2023-05-28 + +* fix Markdown links (again) by @hellow554 in https://github.com/rust-lang/log/pull/513 +* add cargo doc to workflow by @hellow554 in https://github.com/rust-lang/log/pull/515 +* Apply Clippy lints by @hellow554 in https://github.com/rust-lang/log/pull/516 +* Replace ad-hoc eq_ignore_ascii_case with slice::eq_ignore_ascii_case by @glandium in https://github.com/rust-lang/log/pull/519 +* fix up windows targets by @KodrAus in https://github.com/rust-lang/log/pull/528 +* typo fix by @jiangying000 in https://github.com/rust-lang/log/pull/529 +* Remove dependency on cfg_if by @EriKWDev in https://github.com/rust-lang/log/pull/536 +* GitHub Workflows security hardening by @sashashura in https://github.com/rust-lang/log/pull/538 +* Fix build status badge by @atouchet in https://github.com/rust-lang/log/pull/539 +* Add call_logger to the documentation by @a1ecbr0wn in https://github.com/rust-lang/log/pull/547 +* Use stable internals for key-value API by @KodrAus in https://github.com/rust-lang/log/pull/550 +* Change wording of list of implementations by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/553 +* Add std-logger to list of implementations by @Thomasdezeeuw in https://github.com/rust-lang/log/pull/554 +* Add `set_max_level_racy` and gate `set_max_level` by @djkoloski in https://github.com/rust-lang/log/pull/544 +* [doc] src/lib.rs : prefix an unused variable with an underscore by @OccupyMars2025 in https://github.com/rust-lang/log/pull/561 +* [doc] src/macros.rs : correct grammar errors of an example in lib documentation by @OccupyMars2025 in https://github.com/rust-lang/log/pull/562 + +## [0.4.17] - 2022-04-29 + +* Update `kv_unstable` internal dependencies. + +## [0.4.16] - 2022-03-22 + +* Fix a conflict with unqualified `Option` use in macros. + +## [0.4.15] - 2022-02-23 + +* Silence a warning about the deprecated `spin_loop_hint`. +* Relax ordering in the atomic `set_max_level` call. +* Add thumbv4t-none-eabi to targets that don't support atomics +* Allow levels to be iterated over. +* Implement `Log` on some common wrapper types. +* Improvements to test coverage. +* Improvements to documentation. +* Add key-value support to the `log!` macros. +* Tighten `kv_unstable` internal dependencies, so they don't bump past their current alpha. +* Add a simple visit API to `kv_unstable`. +* Support `NonZero*` integers as values in structured logging +* Support static strings as keys in structured logging + +## [0.4.14] - 2021-01-27 + +* Remove the `__private_api_log_lit` special case. +* Fixed incorrect combination of `kv_unstable` and `std` features causing compile failures. +* Remove unstable `Value::to_*` conversions that were incorrectly using `as`. +* Rename unstable `Value::to_error` to `Value::to_borrowed_error`. + +## [0.4.13] - 2021-01-11 + +* This is the same as `0.4.11`, except with a `kv_unstable_std` feature added to aid migrating current dependents to `0.4.14` (which was originally going to be `0.4.13` until it was decided to create a patch from `0.4.11` to minimize disruption). + +## [0.4.12] - 2020-12-24 + +### New + +* Support platforms without atomics by racing instead of failing to compile +* Implement `Log` for `Box` +* Update `cfg-if` to `1.0` +* Internal reworks of the structured logging API. Removed the `Fill` API +and added `source::as_map` and `source::as_list` to easily serialize a `Source` +as either a map of `{key: value, ..}` or as a list of `[(key, value), ..]`. + +### Fixed + +* Fixed deserialization of `LevelFilter` to use their `u64` index variants + ## [0.4.11] - 2020-07-09 ### New @@ -21,7 +228,7 @@ ### Fixed -* Fixed the `log!` macros so they work in expression context (this regressed in `0.4.9`, which has been yanked). +* Fixed the `log!` macros, so they work in expression context (this regressed in `0.4.9`, which has been yanked). ## [0.4.9] - 2019-12-12 (yanked) @@ -132,7 +339,7 @@ version using log 0.4.x to avoid losing module and file information. * The `logger` free function returns a reference to the logger implementation. This, along with the ability to construct `Record`s, makes it possible to bridge from another logging framework to this one without digging into the private internals of the crate. The standard `error!` `warn!`, - etc, macros now exclusively use the public API of the crate rather than "secret" internal APIs. + etc., macros now exclusively use the public API of the crate rather than "secret" internal APIs. * `Log::flush` has been added to allow crates to tell the logging implementation to ensure that all "in flight" log events have been persisted. This can be used, for example, just before an application exits to ensure that asynchronous log sinks finish their work. @@ -170,7 +377,24 @@ version using log 0.4.x to avoid losing module and file information. Look at the [release tags] for information about older releases. -[Unreleased]: https://github.com/rust-lang-nursery/log/compare/0.4.11...HEAD +[Unreleased]: https://github.com/rust-lang-nursery/log/compare/0.4.28...HEAD +[0.4.28]: https://github.com/rust-lang/log/compare/0.4.27...0.4.28 +[0.4.27]: https://github.com/rust-lang/log/compare/0.4.26...0.4.27 +[0.4.26]: https://github.com/rust-lang/log/compare/0.4.25...0.4.26 +[0.4.25]: https://github.com/rust-lang/log/compare/0.4.24...0.4.25 +[0.4.24]: https://github.com/rust-lang/log/compare/0.4.23...0.4.24 +[0.4.23]: https://github.com/rust-lang/log/compare/0.4.22...0.4.23 +[0.4.22]: https://github.com/rust-lang/log/compare/0.4.21...0.4.22 +[0.4.21]: https://github.com/rust-lang/log/compare/0.4.20...0.4.21 +[0.4.20]: https://github.com/rust-lang-nursery/log/compare/0.4.19...0.4.20 +[0.4.19]: https://github.com/rust-lang-nursery/log/compare/0.4.18...0.4.19 +[0.4.18]: https://github.com/rust-lang-nursery/log/compare/0.4.17...0.4.18 +[0.4.17]: https://github.com/rust-lang-nursery/log/compare/0.4.16...0.4.17 +[0.4.16]: https://github.com/rust-lang-nursery/log/compare/0.4.15...0.4.16 +[0.4.15]: https://github.com/rust-lang-nursery/log/compare/0.4.13...0.4.15 +[0.4.14]: https://github.com/rust-lang-nursery/log/compare/0.4.13...0.4.14 +[0.4.13]: https://github.com/rust-lang-nursery/log/compare/0.4.11...0.4.13 +[0.4.12]: https://github.com/rust-lang-nursery/log/compare/0.4.11...0.4.12 [0.4.11]: https://github.com/rust-lang-nursery/log/compare/0.4.10...0.4.11 [0.4.10]: https://github.com/rust-lang-nursery/log/compare/0.4.9...0.4.10 [0.4.9]: https://github.com/rust-lang-nursery/log/compare/0.4.8...0.4.9 diff --git a/Cargo.toml b/Cargo.toml index 4021fb041..47b847133 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "log" -version = "0.4.11" # remember to update html_root_url +version = "0.4.28" # remember to update html_root_url authors = ["The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,19 +12,12 @@ A lightweight logging facade for Rust """ categories = ["development-tools::debugging"] keywords = ["logging"] -exclude = ["rfcs/**/*", "/.travis.yml", "/appveyor.yml"] -build = "build.rs" +exclude = ["rfcs/**/*"] +rust-version = "1.61.0" +edition = "2021" [package.metadata.docs.rs] -features = ["std", "serde", "kv_unstable_sval"] - -[[test]] -name = "filters" -harness = false - -[[test]] -name = "macros" -harness = true +features = ["std", "serde", "kv_std", "kv_sval", "kv_serde"] [features] max_level_off = [] @@ -43,16 +36,36 @@ release_max_level_trace = [] std = [] -# requires the latest stable -# this will have a tighter MSRV before stabilization -kv_unstable = [] -kv_unstable_sval = ["kv_unstable", "sval/fmt"] +kv = [] +kv_sval = ["kv", "value-bag/sval", "sval", "sval_ref"] +kv_std = ["std", "kv", "value-bag/error"] +kv_serde = ["kv_std", "value-bag/serde", "serde"] + +# Deprecated: use `kv_*` instead +# These `*_unstable` features will be removed in a future release +kv_unstable = ["kv", "value-bag"] +kv_unstable_sval = ["kv_sval", "kv_unstable"] +kv_unstable_std = ["kv_std", "kv_unstable"] +kv_unstable_serde = ["kv_serde", "kv_unstable_std"] [dependencies] -cfg-if = "0.1.2" serde = { version = "1.0", optional = true, default-features = false } -sval = { version = "0.5.2", optional = true, default-features = false } +sval = { version = "2.14.1", optional = true, default-features = false } +sval_ref = { version = "2.1", optional = true, default-features = false } +value-bag = { version = "1.7", optional = true, default-features = false, features = ["inline-i128"] } [dev-dependencies] -serde_test = "1.0" -sval = { version = "0.5.2", features = ["test"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +serde_test = { version = "1.0" } +sval = { version = "2.1" } +sval_derive = { version = "2.1" } +value-bag = { version = "1.7", features = ["test"] } + +# NOTE: log doesn't actually depend on this crate. However, our dependencies, +# serde and sval, dependent on version 1.0 of the crate, which has problem fixed +# in 1.0.60, specifically in the following commit +# https://github.com/dtolnay/proc-macro2/commit/e31d61910049e097afdd3d27c37786309082bdcb. +# By defining the crate as direct dependency we can increase its minimal +# version making the minimal (crate) version CI happy. +proc-macro2 = { version = "1.0.63", default-features = false } diff --git a/README.md b/README.md index d320b9c2f..9d5113d24 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ log A Rust library providing a lightweight logging *facade*. -[![Build status](https://img.shields.io/github/workflow/status/rust-lang/log/CI/master)](https://github.com/rust-lang/log/actions) +[![Build status](https://img.shields.io/github/actions/workflow/status/rust-lang/log/main.yml?branch=master)](https://github.com/rust-lang/log/actions) [![Latest version](https://img.shields.io/crates/v/log.svg)](https://crates.io/crates/log) [![Documentation](https://docs.rs/log/badge.svg)](https://docs.rs/log) ![License](https://img.shields.io/crates/l/log.svg) @@ -18,13 +18,13 @@ implementation that is most suitable for its use case. ## Minimum supported `rustc` -`1.31.0+` +`1.61.0+` This version is explicitly tested in CI and may be bumped in any release as needed. Maintaining compatibility with older compilers is a priority though, so the bar for bumping the minimum supported version is set very high. Any changes to the supported minimum version will be called out in the release notes. ## Usage -## In libraries +### In libraries Libraries should link only to the `log` crate, and use the provided macros to log whatever information will be useful to downstream consumers: @@ -43,40 +43,58 @@ pub fn shave_the_yak(yak: &mut Yak) { loop { match find_a_razor() { Ok(razor) => { - info!("Razor located: {}", razor); + info!("Razor located: {razor}"); yak.shave(razor); break; } Err(err) => { - warn!("Unable to locate a razor: {}, retrying", err); + warn!("Unable to locate a razor: {err}, retrying"); } } } } ``` -## In executables +### In executables In order to produce log output, executables have to use a logger implementation compatible with the facade. -There are many available implementations to choose from, here are some of the most popular ones: +There are many available implementations to choose from, here are some options: * Simple minimal loggers: * [`env_logger`](https://docs.rs/env_logger/*/env_logger/) - * [`simple_logger`](https://github.com/borntyping/rust-simple_logger) - * [`simplelog`](https://github.com/drakulix/simplelog.rs) + * [`colog`](https://docs.rs/colog/*/colog/) + * [`simple_logger`](https://docs.rs/simple_logger/*/simple_logger/) + * [`simplelog`](https://docs.rs/simplelog/*/simplelog/) * [`pretty_env_logger`](https://docs.rs/pretty_env_logger/*/pretty_env_logger/) * [`stderrlog`](https://docs.rs/stderrlog/*/stderrlog/) * [`flexi_logger`](https://docs.rs/flexi_logger/*/flexi_logger/) + * [`call_logger`](https://docs.rs/call_logger/*/call_logger/) + * [`std-logger`](https://docs.rs/std-logger/*/std_logger/) + * [`structured-logger`](https://docs.rs/structured-logger/latest/structured_logger/) + * [`clang_log`](https://docs.rs/clang_log/latest/clang_log) + * [`ftail`](https://docs.rs/ftail/latest/ftail/) * Complex configurable frameworks: * [`log4rs`](https://docs.rs/log4rs/*/log4rs/) + * [`logforth`](https://docs.rs/logforth/*/logforth/) * [`fern`](https://docs.rs/fern/*/fern/) + * [`spdlog-rs`](https://docs.rs/spdlog-rs/*/spdlog/) * Adaptors for other facilities: * [`syslog`](https://docs.rs/syslog/*/syslog/) + * [`systemd-journal-logger`](https://docs.rs/systemd-journal-logger/*/systemd_journal_logger/) * [`slog-stdlog`](https://docs.rs/slog-stdlog/*/slog_stdlog/) * [`android_log`](https://docs.rs/android_log/*/android_log/) * [`win_dbg_logger`](https://docs.rs/win_dbg_logger/*/win_dbg_logger/) + * [`db_logger`](https://docs.rs/db_logger/*/db_logger/) + * [`log-to-defmt`](https://docs.rs/log-to-defmt/*/log_to_defmt/) + * [`logcontrol-log`](https://docs.rs/logcontrol-log/*/logcontrol_log/) * For WebAssembly binaries: * [`console_log`](https://docs.rs/console_log/*/console_log/) +* For dynamic libraries: + * You may need to construct [an FFI-safe wrapper over `log`](https://github.com/rust-lang/log/issues/421) to initialize in your libraries. +* Utilities: + * [`log_err`](https://docs.rs/log_err/*/log_err/) + * [`log-reload`](https://docs.rs/log-reload/*/log_reload/) + * [`alterable_logger`](https://docs.rs/alterable_logger/*/alterable_logger) Executables should choose a logger implementation and initialize it early in the runtime of the program. Logger implementations will typically include a @@ -84,3 +102,33 @@ function to do this. Any log messages generated before the logger is initialized will be ignored. The executable itself may use the `log` crate to log as well. + +## Structured logging + +If you enable the `kv` feature, you can associate structured data with your log records: + +```rust +use log::{info, trace, warn}; + +pub fn shave_the_yak(yak: &mut Yak) { + // `yak:serde` will capture `yak` using its `serde::Serialize` impl + // + // You could also use `:?` for `Debug`, or `:%` for `Display`. For a + // full list, see the `log` crate documentation + trace!(target = "yak_events", yak:serde; "Commencing yak shaving"); + + loop { + match find_a_razor() { + Ok(razor) => { + info!(razor; "Razor located"); + yak.shave(razor); + break; + } + Err(e) => { + // `e:err` will capture `e` using its `std::error::Error` impl + warn!(e:err; "Unable to locate a razor, retrying"); + } + } + } +} +``` diff --git a/benches/value.rs b/benches/value.rs new file mode 100644 index 000000000..3d0f18bfe --- /dev/null +++ b/benches/value.rs @@ -0,0 +1,27 @@ +#![cfg(feature = "kv")] +#![feature(test)] + +use log::kv::Value; + +#[bench] +fn u8_to_value(b: &mut test::Bencher) { + b.iter(|| Value::from(1u8)); +} + +#[bench] +fn u8_to_value_debug(b: &mut test::Bencher) { + b.iter(|| Value::from_debug(&1u8)); +} + +#[bench] +fn str_to_value_debug(b: &mut test::Bencher) { + b.iter(|| Value::from_debug(&"a string")); +} + +#[bench] +fn custom_to_value_debug(b: &mut test::Bencher) { + #[derive(Debug)] + struct A; + + b.iter(|| Value::from_debug(&A)); +} diff --git a/build.rs b/build.rs deleted file mode 100644 index 6717bf0f7..000000000 --- a/build.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This build script detects target platforms that lack proper support for -//! atomics and sets `cfg` flags accordingly. - -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - - if !target.starts_with("thumbv6") { - println!("cargo:rustc-cfg=atomic_cas"); - } - - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/rfcs/0296-structured-logging.md b/rfcs/0296-structured-logging.md index 6a53a7abb..835588c6b 100644 --- a/rfcs/0296-structured-logging.md +++ b/rfcs/0296-structured-logging.md @@ -337,7 +337,7 @@ fn log_record(w: impl Write, r: &Record) -> io::Result<()> { // Write each key-value pair on a new line record .key_values() - .for_each(|k, v| writeln!("{}: {}", k, v))?; + .for_each(|k, v| writeln!("{k}: {v}"))?; Ok(()) } @@ -1203,7 +1203,7 @@ Some useful adapters exist as provided methods on the `Source` trait. They're si - `by_ref` to get a reference to a `Source` within a method chain. - `chain` to concatenate one source with another. This is useful for composing implementations of `Log` together for contextual logging. -- `get` to try find the value associated with a key. This is useful for well-defined key-value pairs that a framework built over `log` might want to provide, like timestamps or message templates. +- `get` to try finding the value associated with a key. This is useful for well-defined key-value pairs that a framework built over `log` might want to provide, like timestamps or message templates. - `for_each` to execute some closure over all key-value pairs. This is a convenient way to do something with each key-value pair without having to create and implement a `Visitor`. One potential downside of `for_each` is the `Result` return value, which seems surprising when the closure itself can't fail. The `Source::for_each` call might itself fail if the underlying `visit` call fails when iterating over its key-value pairs. This shouldn't be common though, so when paired with `try_for_each`, it might be reasonable to make `for_each` return a `()` and rely on `try_for_each` for surfacing any fallibility. - `try_for_each` is like `for_each`, but takes a fallible closure. - `as_map` to get a serializable map. This is a convenient way to serialize key-value pairs without having to create and implement a `Visitor`. @@ -1633,7 +1633,7 @@ Structured logging is a paradigm that's supported by logging frameworks in many ## Rust -The `slog` library is a structured logging framework for Rust. Its API predates a stable `serde` crate so it defines its own traits that are similar to `serde::Serialize`. A log record consists of a rendered message and bag of structured key-value pairs. `slog` goes further than this RFC proposes by requiring callers of its `log!` macros to state whether key-values are owned or borrowed by the record, and whether the data is safe to share across threads. +The `slog` library is a structured logging framework for Rust. Its API predates a stable `serde` crate, so it defines its own traits that are similar to `serde::Serialize`. A log record consists of a rendered message and bag of structured key-value pairs. `slog` goes further than this RFC proposes by requiring callers of its `log!` macros to state whether key-values are owned or borrowed by the record, and whether the data is safe to share across threads. This RFC proposes an API that's inspired by `slog`, but doesn't directly support distinguishing between owned or borrowed key-value pairs. Everything is borrowed. That means the only way to send a `Record` to another thread is to serialize it into a different type. diff --git a/src/__private_api.rs b/src/__private_api.rs new file mode 100644 index 000000000..58d4c0fab --- /dev/null +++ b/src/__private_api.rs @@ -0,0 +1,151 @@ +//! WARNING: this is not part of the crate's public API and is subject to change at any time + +use self::sealed::KVs; +use crate::{logger, Level, Log, Metadata, Record}; +use std::fmt::Arguments; +use std::panic::Location; +pub use std::{format_args, module_path, stringify}; + +#[cfg(not(feature = "kv"))] +pub type Value<'a> = &'a str; + +mod sealed { + /// Types for the `kv` argument. + pub trait KVs<'a> { + fn into_kvs(self) -> Option<&'a [(&'a str, super::Value<'a>)]>; + } +} + +// Types for the `kv` argument. + +impl<'a> KVs<'a> for &'a [(&'a str, Value<'a>)] { + #[inline] + fn into_kvs(self) -> Option<&'a [(&'a str, Value<'a>)]> { + Some(self) + } +} + +impl<'a> KVs<'a> for () { + #[inline] + fn into_kvs(self) -> Option<&'a [(&'a str, Value<'a>)]> { + None + } +} + +// Log implementation. + +/// The global logger proxy. +#[derive(Debug)] +pub struct GlobalLogger; + +impl Log for GlobalLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + logger().enabled(metadata) + } + + fn log(&self, record: &Record) { + logger().log(record) + } + + fn flush(&self) { + logger().flush() + } +} + +// Split from `log` to reduce generics and code size +fn log_impl( + logger: L, + args: Arguments, + level: Level, + &(target, module_path, loc): &(&str, &'static str, &'static Location), + kvs: Option<&[(&str, Value)]>, +) { + #[cfg(not(feature = "kv"))] + if kvs.is_some() { + panic!("key-value support is experimental and must be enabled using the `kv` feature") + } + + let mut builder = Record::builder(); + + builder + .args(args) + .level(level) + .target(target) + .module_path_static(Some(module_path)) + .file_static(Some(loc.file())) + .line(Some(loc.line())); + + #[cfg(feature = "kv")] + builder.key_values(&kvs); + + logger.log(&builder.build()); +} + +pub fn log<'a, K, L>( + logger: L, + args: Arguments, + level: Level, + target_module_path_and_loc: &(&str, &'static str, &'static Location), + kvs: K, +) where + K: KVs<'a>, + L: Log, +{ + log_impl( + logger, + args, + level, + target_module_path_and_loc, + kvs.into_kvs(), + ) +} + +pub fn enabled(logger: L, level: Level, target: &str) -> bool { + logger.enabled(&Metadata::builder().level(level).target(target).build()) +} + +#[track_caller] +pub fn loc() -> &'static Location<'static> { + Location::caller() +} + +#[cfg(feature = "kv")] +mod kv_support { + use crate::kv; + + pub type Value<'a> = kv::Value<'a>; + + // NOTE: Many functions here accept a double reference &&V + // This is so V itself can be ?Sized, while still letting us + // erase it to some dyn Trait (because &T is sized) + + pub fn capture_to_value<'a, V: kv::ToValue + ?Sized>(v: &'a &'a V) -> Value<'a> { + v.to_value() + } + + pub fn capture_debug<'a, V: core::fmt::Debug + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_debug(v) + } + + pub fn capture_display<'a, V: core::fmt::Display + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_display(v) + } + + #[cfg(feature = "kv_std")] + pub fn capture_error<'a>(v: &'a (dyn std::error::Error + 'static)) -> Value<'a> { + Value::from_dyn_error(v) + } + + #[cfg(feature = "kv_sval")] + pub fn capture_sval<'a, V: sval::Value + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_sval(v) + } + + #[cfg(feature = "kv_serde")] + pub fn capture_serde<'a, V: serde::Serialize + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_serde(v) + } +} + +#[cfg(feature = "kv")] +pub use self::kv_support::*; diff --git a/src/kv/error.rs b/src/kv/error.rs index 0439b3a55..7efa5af36 100644 --- a/src/kv/error.rs +++ b/src/kv/error.rs @@ -11,6 +11,8 @@ enum Inner { #[cfg(feature = "std")] Boxed(std_support::BoxedError), Msg(&'static str), + #[cfg(feature = "value-bag")] + Value(crate::kv::value::inner::Error), Fmt, } @@ -21,6 +23,26 @@ impl Error { inner: Inner::Msg(msg), } } + + // Not public so we don't leak the `crate::kv::value::inner` API + #[cfg(feature = "value-bag")] + pub(super) fn from_value(err: crate::kv::value::inner::Error) -> Self { + Error { + inner: Inner::Value(err), + } + } + + // Not public so we don't leak the `crate::kv::value::inner` API + #[cfg(feature = "value-bag")] + pub(super) fn into_value(self) -> crate::kv::value::inner::Error { + match self.inner { + Inner::Value(err) => err, + #[cfg(feature = "kv_std")] + _ => crate::kv::value::inner::Error::boxed(self), + #[cfg(not(feature = "kv_std"))] + _ => crate::kv::value::inner::Error::msg("error inspecting a value"), + } + } } impl fmt::Display for Error { @@ -28,9 +50,11 @@ impl fmt::Display for Error { use self::Inner::*; match &self.inner { #[cfg(feature = "std")] - &Boxed(ref err) => err.fmt(f), - &Msg(ref msg) => msg.fmt(f), - &Fmt => fmt::Error.fmt(f), + Boxed(err) => err.fmt(f), + #[cfg(feature = "value-bag")] + Value(err) => err.fmt(f), + Msg(msg) => msg.fmt(f), + Fmt => fmt::Error.fmt(f), } } } diff --git a/src/kv/key.rs b/src/kv/key.rs index 86fd0668a..6e00a2ca8 100644 --- a/src/kv/key.rs +++ b/src/kv/key.rs @@ -1,9 +1,7 @@ //! Structured keys. use std::borrow::Borrow; -use std::cmp; use std::fmt; -use std::hash; /// A type that can be converted into a [`Key`](struct.Key.html). pub trait ToKey { @@ -32,27 +30,38 @@ impl ToKey for str { } } -/// A key in a structured key-value pair. -#[derive(Clone)] +/// A key in a key-value. +// These impls must only be based on the as_str() representation of the key +// If a new field (such as an optional index) is added to the key they must not affect comparison +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Key<'k> { + // NOTE: This may become `Cow<'k, str>` key: &'k str, } impl<'k> Key<'k> { /// Get a key from a borrowed string. pub fn from_str(key: &'k str) -> Self { - Key { key: key } + Key { key } } /// Get a borrowed string from this key. + /// + /// The lifetime of the returned string is bound to the borrow of `self` rather + /// than to `'k`. pub fn as_str(&self) -> &str { self.key } -} -impl<'k> fmt::Debug for Key<'k> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.key.fmt(f) + /// Try get a borrowed string for the lifetime `'k` from this key. + /// + /// If the key is a borrow of a longer lived string, this method will return `Some`. + /// If the key is internally buffered, this method will return `None`. + pub fn to_borrowed_str(&self) -> Option<&'k str> { + // NOTE: If the internals of `Key` support buffering this + // won't be unconditionally `Some` anymore. We want to keep + // this option open + Some(self.key) } } @@ -62,35 +71,6 @@ impl<'k> fmt::Display for Key<'k> { } } -impl<'k> hash::Hash for Key<'k> { - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - self.as_str().hash(state) - } -} - -impl<'k, 'ko> PartialEq> for Key<'k> { - fn eq(&self, other: &Key<'ko>) -> bool { - self.as_str().eq(other.as_str()) - } -} - -impl<'k> Eq for Key<'k> {} - -impl<'k, 'ko> PartialOrd> for Key<'k> { - fn partial_cmp(&self, other: &Key<'ko>) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} - -impl<'k> Ord for Key<'k> { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.as_str().cmp(other.as_str()) - } -} - impl<'k> AsRef for Key<'k> { fn as_ref(&self) -> &str { self.as_str() @@ -128,6 +108,45 @@ mod std_support { } } +#[cfg(feature = "kv_sval")] +mod sval_support { + use super::*; + + use sval::Value; + use sval_ref::ValueRef; + + impl<'a> Value for Key<'a> { + fn stream<'sval, S: sval::Stream<'sval> + ?Sized>( + &'sval self, + stream: &mut S, + ) -> sval::Result { + self.key.stream(stream) + } + } + + impl<'a> ValueRef<'a> for Key<'a> { + fn stream_ref + ?Sized>(&self, stream: &mut S) -> sval::Result { + self.key.stream(stream) + } + } +} + +#[cfg(feature = "kv_serde")] +mod serde_support { + use super::*; + + use serde::{Serialize, Serializer}; + + impl<'a> Serialize for Key<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.key.serialize(serializer) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -136,4 +155,9 @@ mod tests { fn key_from_string() { assert_eq!("a key", Key::from_str("a key").as_str()); } + + #[test] + fn key_to_borrowed() { + assert_eq!("a key", Key::from_str("a key").to_borrowed_str().unwrap()); + } } diff --git a/src/kv/mod.rs b/src/kv/mod.rs index fa697c2bb..34e61c3ae 100644 --- a/src/kv/mod.rs +++ b/src/kv/mod.rs @@ -1,26 +1,265 @@ -//! **UNSTABLE:** Structured key-value pairs. +//! Structured logging. //! -//! This module is unstable and breaking changes may be made -//! at any time. See [the tracking issue](https://github.com/rust-lang-nursery/log/issues/328) -//! for more details. -//! -//! Add the `kv_unstable` feature to your `Cargo.toml` to enable +//! Add the `kv` feature to your `Cargo.toml` to enable //! this module: //! //! ```toml //! [dependencies.log] -//! features = ["kv_unstable"] +//! features = ["kv"] +//! ``` +//! +//! # Structured logging in `log` +//! +//! Structured logging enhances traditional text-based log records with user-defined +//! attributes. Structured logs can be analyzed using a variety of data processing +//! techniques, without needing to find and parse attributes from unstructured text first. +//! +//! In `log`, user-defined attributes are part of a [`Source`] on the log record. +//! Each attribute is a key-value; a pair of [`Key`] and [`Value`]. Keys are strings +//! and values are a datum of any type that can be formatted or serialized. Simple types +//! like strings, booleans, and numbers are supported, as well as arbitrarily complex +//! structures involving nested objects and sequences. +//! +//! ## Adding key-values to log records +//! +//! Key-values appear before the message format in the `log!` macros: +//! +//! ``` +//! # use log::info; +//! info!(a = 1; "Something of interest"); +//! ``` +//! +//! Key-values support the same shorthand identifier syntax as `format_args`: +//! +//! ``` +//! # use log::info; +//! let a = 1; +//! +//! info!(a; "Something of interest"); +//! ``` +//! +//! Values are capturing using the [`ToValue`] trait by default. To capture a value +//! using a different trait implementation, use a modifier after its key. Here's how +//! the same example can capture `a` using its `Debug` implementation instead: +//! +//! ``` +//! # use log::info; +//! info!(a:? = 1; "Something of interest"); +//! ``` +//! +//! The following capturing modifiers are supported: +//! +//! - `:?` will capture the value using `Debug`. +//! - `:debug` will capture the value using `Debug`. +//! - `:%` will capture the value using `Display`. +//! - `:display` will capture the value using `Display`. +//! - `:err` will capture the value using `std::error::Error` (requires the `kv_std` feature). +//! - `:sval` will capture the value using `sval::Value` (requires the `kv_sval` feature). +//! - `:serde` will capture the value using `serde::Serialize` (requires the `kv_serde` feature). +//! +//! ## Working with key-values on log records +//! +//! Use the [`Record::key_values`](../struct.Record.html#method.key_values) method to access key-values. +//! +//! Individual values can be pulled from the source by their key: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{Source, Key, Value}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a: Value = record.key_values().get(Key::from("a")).unwrap(); +//! assert_eq!(1, a.to_i64().unwrap()); +//! # Ok(()) +//! # } +//! ``` +//! +//! All key-values can also be enumerated using a [`VisitSource`]: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use std::collections::BTreeMap; +//! +//! use log::kv::{self, Source, Key, Value, VisitSource}; +//! +//! struct Collect<'kvs>(BTreeMap, Value<'kvs>>); +//! +//! impl<'kvs> VisitSource<'kvs> for Collect<'kvs> { +//! fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { +//! self.0.insert(key, value); +//! +//! Ok(()) +//! } +//! } +//! +//! let mut visitor = Collect(BTreeMap::new()); +//! +//! # let record = log::Record::builder().key_values(&[("a", 1), ("b", 2), ("c", 3)]).build(); +//! // info!(a = 1, b = 2, c = 3; "Something of interest"); +//! +//! record.key_values().visit(&mut visitor)?; +//! +//! let collected = visitor.0; +//! +//! assert_eq!( +//! vec!["a", "b", "c"], +//! collected +//! .keys() +//! .map(|k| k.as_str()) +//! .collect::>(), +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! [`Value`]s have methods for conversions to common types: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{Source, Key}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!(1, a.to_i64().unwrap()); +//! # Ok(()) +//! # } +//! ``` +//! +//! Values also have their own [`VisitValue`] type. Value visitors are a lightweight +//! API for working with primitives types: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{self, Source, Key, VisitValue}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! struct IsNumeric(bool); +//! +//! impl<'kvs> VisitValue<'kvs> for IsNumeric { +//! fn visit_any(&mut self, _value: kv::Value) -> Result<(), kv::Error> { +//! self.0 = false; +//! Ok(()) +//! } +//! +//! fn visit_u64(&mut self, _value: u64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_i64(&mut self, _value: i64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_u128(&mut self, _value: u128) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_i128(&mut self, _value: i128) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_f64(&mut self, _value: f64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! } +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! let mut visitor = IsNumeric(false); +//! +//! a.visit(&mut visitor)?; +//! +//! let is_numeric = visitor.0; +//! +//! assert!(is_numeric); +//! # Ok(()) +//! # } +//! ``` +//! +//! To serialize a value to a format like JSON, you can also use either `serde` or `sval`: +//! +//! ``` +//! # fn main() -> Result<(), Box> { +//! # #[cfg(feature = "serde")] +//! # { +//! # use log::kv::Key; +//! #[derive(serde::Serialize)] +//! struct Data { +//! a: i32, b: bool, +//! c: &'static str, +//! } +//! +//! let data = Data { a: 1, b: true, c: "Some data" }; +//! +//! # let source = [("a", log::kv::Value::from_serde(&data))]; +//! # let record = log::Record::builder().key_values(&source).build(); +//! // info!(a = data; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!("{\"a\":1,\"b\":true,\"c\":\"Some data\"}", serde_json::to_string(&a)?); +//! # } +//! # Ok(()) +//! # } +//! ``` +//! +//! The choice of serialization framework depends on the needs of the consumer. +//! If you're in a no-std environment, you can use `sval`. In other cases, you can use `serde`. +//! Log producers and log consumers don't need to agree on the serialization framework. +//! A value can be captured using its `serde::Serialize` implementation and still be serialized +//! through `sval` without losing any structure or data. +//! +//! Values can also always be formatted using the standard `Debug` and `Display` +//! traits: +//! +//! ``` +//! # use log::kv::Key; +//! #[derive(Debug)] +//! struct Data { +//! a: i32, +//! b: bool, +//! c: &'static str, +//! } +//! +//! let data = Data { a: 1, b: true, c: "Some data" }; +//! +//! # let source = [("a", log::kv::Value::from_debug(&data))]; +//! # let record = log::Record::builder().key_values(&source).build(); +//! // info!(a = data; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!("Data { a: 1, b: true, c: \"Some data\" }", format!("{a:?}")); //! ``` mod error; mod key; -mod source; -pub mod value; +#[cfg(not(feature = "kv_unstable"))] +mod source; +#[cfg(not(feature = "kv_unstable"))] +mod value; pub use self::error::Error; pub use self::key::{Key, ToKey}; -pub use self::source::{Source, Visitor}; +pub use self::source::{Source, VisitSource}; +pub use self::value::{ToValue, Value, VisitValue}; + +#[cfg(feature = "kv_unstable")] +pub mod source; +#[cfg(feature = "kv_unstable")] +pub mod value; -#[doc(inline)] -pub use self::value::{ToValue, Value}; +#[cfg(feature = "kv_unstable")] +pub use self::source::Visitor; diff --git a/src/kv/source.rs b/src/kv/source.rs index a882068f9..f463e6d2b 100644 --- a/src/kv/source.rs +++ b/src/kv/source.rs @@ -1,25 +1,65 @@ -//! Sources for key-value pairs. +//! Sources for key-values. +//! +//! This module defines the [`Source`] type and supporting APIs for +//! working with collections of key-values. -use kv::{Error, Key, ToKey, ToValue, Value}; +use crate::kv::{Error, Key, ToKey, ToValue, Value}; use std::fmt; -/// A source of key-value pairs. +/// A source of key-values. /// /// The source may be a single pair, a set of pairs, or a filter over a set of pairs. -/// Use the [`Visitor`](trait.Visitor.html) trait to inspect the structured data +/// Use the [`VisitSource`](trait.VisitSource.html) trait to inspect the structured data /// in a source. +/// +/// A source is like an iterator over its key-values, except with a push-based API +/// instead of a pull-based one. +/// +/// # Examples +/// +/// Enumerating the key-values in a source: +/// +/// ``` +/// # fn main() -> Result<(), log::kv::Error> { +/// use log::kv::{self, Source, Key, Value, VisitSource}; +/// +/// // A `VisitSource` that prints all key-values +/// // VisitSources are fed the key-value pairs of each key-values +/// struct Printer; +/// +/// impl<'kvs> VisitSource<'kvs> for Printer { +/// fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { +/// println!("{key}: {value}"); +/// +/// Ok(()) +/// } +/// } +/// +/// // A source with 3 key-values +/// // Common collection types implement the `Source` trait +/// let source = &[ +/// ("a", 1), +/// ("b", 2), +/// ("c", 3), +/// ]; +/// +/// // Pass an instance of the `VisitSource` to a `Source` to visit it +/// source.visit(&mut Printer)?; +/// # Ok(()) +/// # } +/// ``` pub trait Source { - /// Visit key-value pairs. + /// Visit key-values. /// - /// A source doesn't have to guarantee any ordering or uniqueness of key-value pairs. + /// A source doesn't have to guarantee any ordering or uniqueness of key-values. /// If the given visitor returns an error then the source may early-return with it, - /// even if there are more key-value pairs. + /// even if there are more key-values. /// /// # Implementation notes /// - /// A source should yield the same key-value pairs to a subsequent visitor unless + /// A source should yield the same key-values to a subsequent visitor unless /// that visitor itself fails. - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error>; + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error>; /// Get the value for a given key. /// @@ -30,63 +70,72 @@ pub trait Source { /// /// A source that can provide a more efficient implementation of this method /// should override it. - fn get<'v>(&'v self, key: Key) -> Option> { - struct Get<'k, 'v> { - key: Key<'k>, - found: Option>, - } - - impl<'k, 'kvs> Visitor<'kvs> for Get<'k, 'kvs> { - fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { - if self.key == key { - self.found = Some(value); - } - - Ok(()) - } - } - - let mut get = Get { key, found: None }; - - let _ = self.visit(&mut get); - get.found + fn get(&self, key: Key) -> Option> { + get_default(self, key) } - /// Count the number of key-value pairs that can be visited. + /// Count the number of key-values that can be visited. /// /// # Implementation notes /// - /// A source that knows the number of key-value pairs upfront may provide a more + /// A source that knows the number of key-values upfront may provide a more /// efficient implementation. /// - /// A subsequent call to `visit` should yield the same number of key-value pairs - /// to the visitor, unless that visitor fails part way through. + /// A subsequent call to `visit` should yield the same number of key-values. fn count(&self) -> usize { - struct Count(usize); + count_default(self) + } +} - impl<'kvs> Visitor<'kvs> for Count { - fn visit_pair(&mut self, _: Key<'kvs>, _: Value<'kvs>) -> Result<(), Error> { - self.0 += 1; +/// The default implementation of `Source::get` +fn get_default<'v>(source: &'v (impl Source + ?Sized), key: Key) -> Option> { + struct Get<'k, 'v> { + key: Key<'k>, + found: Option>, + } - Ok(()) + impl<'k, 'kvs> VisitSource<'kvs> for Get<'k, 'kvs> { + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { + if self.key == key { + self.found = Some(value); } + + Ok(()) } + } + + let mut get = Get { key, found: None }; + + let _ = source.visit(&mut get); + get.found +} - let mut count = Count(0); - let _ = self.visit(&mut count); - count.0 +/// The default implementation of `Source::count`. +fn count_default(source: impl Source) -> usize { + struct Count(usize); + + impl<'kvs> VisitSource<'kvs> for Count { + fn visit_pair(&mut self, _: Key<'kvs>, _: Value<'kvs>) -> Result<(), Error> { + self.0 += 1; + + Ok(()) + } } + + let mut count = Count(0); + let _ = source.visit(&mut count); + count.0 } impl<'a, T> Source for &'a T where T: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { Source::get(&**self, key) } @@ -100,11 +149,11 @@ where K: ToKey, V: ToValue, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { visitor.visit_pair(self.0.to_key(), self.1.to_value()) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { if self.0.to_key() == key { Some(self.1.to_value()) } else { @@ -121,7 +170,7 @@ impl Source for [S] where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for source in self { source.visit(visitor)?; } @@ -129,8 +178,35 @@ where Ok(()) } + fn get(&self, key: Key) -> Option> { + for source in self { + if let Some(found) = source.get(key.clone()) { + return Some(found); + } + } + + None + } + fn count(&self) -> usize { - self.len() + self.iter().map(Source::count).sum() + } +} + +impl Source for [S; N] +where + S: Source, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { + Source::visit(self as &[_], visitor) + } + + fn get(&self, key: Key) -> Option> { + Source::get(self as &[_], key) + } + + fn count(&self) -> usize { + Source::count(self as &[_]) } } @@ -138,56 +214,60 @@ impl Source for Option where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { - if let Some(ref source) = *self { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { + if let Some(source) = self { source.visit(visitor)?; } Ok(()) } + fn get(&self, key: Key) -> Option> { + self.as_ref().and_then(|s| s.get(key)) + } + fn count(&self) -> usize { - self.as_ref().map(Source::count).unwrap_or(0) + self.as_ref().map_or(0, Source::count) } } /// A visitor for the key-value pairs in a [`Source`](trait.Source.html). -pub trait Visitor<'kvs> { +pub trait VisitSource<'kvs> { /// Visit a key-value pair. fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error>; } -impl<'a, 'kvs, T> Visitor<'kvs> for &'a mut T +impl<'a, 'kvs, T> VisitSource<'kvs> for &'a mut T where - T: Visitor<'kvs> + ?Sized, + T: VisitSource<'kvs> + ?Sized, { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { (**self).visit_pair(key, value) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugMap<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugMap<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&key, &value); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugList<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugList<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&(key, value)); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugSet<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugSet<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&(key, value)); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugTuple<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugTuple<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.field(&key); self.field(&value); @@ -201,16 +281,52 @@ mod std_support { use std::borrow::Borrow; use std::collections::{BTreeMap, HashMap}; use std::hash::{BuildHasher, Hash}; + use std::rc::Rc; + use std::sync::Arc; impl Source for Box where S: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { + Source::visit(&**self, visitor) + } + + fn get(&self, key: Key) -> Option> { + Source::get(&**self, key) + } + + fn count(&self) -> usize { + Source::count(&**self) + } + } + + impl Source for Arc + where + S: Source + ?Sized, + { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { + Source::visit(&**self, visitor) + } + + fn get(&self, key: Key) -> Option> { + Source::get(&**self, key) + } + + fn count(&self) -> usize { + Source::count(&**self) + } + } + + impl Source for Rc + where + S: Source + ?Sized, + { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { Source::get(&**self, key) } @@ -223,11 +339,11 @@ mod std_support { where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { Source::get(&**self, key) } @@ -236,9 +352,9 @@ mod std_support { } } - impl<'kvs, V> Visitor<'kvs> for Box + impl<'kvs, V> VisitSource<'kvs> for Box where - V: Visitor<'kvs> + ?Sized, + V: VisitSource<'kvs> + ?Sized, { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { (**self).visit_pair(key, value) @@ -251,14 +367,14 @@ mod std_support { V: ToValue, S: BuildHasher, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for (key, value) in self { visitor.visit_pair(key.to_key(), value.to_value())?; } Ok(()) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { HashMap::get(self, key.as_str()).map(|v| v.to_value()) } @@ -272,14 +388,14 @@ mod std_support { K: ToKey + Borrow + Ord, V: ToValue, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for (key, value) in self { visitor.visit_pair(key.to_key(), value.to_value())?; } Ok(()) } - fn get<'v>(&'v self, key: Key) -> Option> { + fn get(&self, key: Key) -> Option> { BTreeMap::get(self, key.as_str()).map(|v| v.to_value()) } @@ -290,9 +406,9 @@ mod std_support { #[cfg(test)] mod tests { + use crate::kv::value; + use super::*; - use kv::value::test::Token; - use std::collections::{BTreeMap, HashMap}; #[test] fn count() { @@ -304,11 +420,11 @@ mod std_support { fn get() { let source = vec![("a", 1), ("b", 2), ("a", 1)]; assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&source, Key::from_str("a")).unwrap().to_token() ); - let source = Box::new(Option::None::<(&str, i32)>); + let source = Box::new(None::<(&str, i32)>); assert!(Source::get(&source, Key::from_str("a")).is_none()); } @@ -320,7 +436,7 @@ mod std_support { assert_eq!(2, Source::count(&map)); assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&map, Key::from_str("a")).unwrap().to_token() ); } @@ -333,17 +449,22 @@ mod std_support { assert_eq!(2, Source::count(&map)); assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&map, Key::from_str("a")).unwrap().to_token() ); } } } +// NOTE: Deprecated; but aliases can't carry this attribute +#[cfg(feature = "kv_unstable")] +pub use VisitSource as Visitor; + #[cfg(test)] mod tests { + use crate::kv::value; + use super::*; - use kv::value::test::Token; #[test] fn source_is_object_safe() { @@ -352,7 +473,7 @@ mod tests { #[test] fn visitor_is_object_safe() { - fn _check(_: &dyn Visitor) {} + fn _check(_: &dyn VisitSource) {} } #[test] @@ -363,14 +484,14 @@ mod tests { } impl Source for OnePair { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { visitor.visit_pair(self.key.to_key(), self.value.to_value()) } } assert_eq!(1, Source::count(&("a", 1))); assert_eq!(2, Source::count(&[("a", 1), ("b", 2)] as &[_])); - assert_eq!(0, Source::count(&Option::None::<(&str, i32)>)); + assert_eq!(0, Source::count(&None::<(&str, i32)>)); assert_eq!(1, Source::count(&OnePair { key: "a", value: 1 })); } @@ -378,16 +499,16 @@ mod tests { fn get() { let source = &[("a", 1), ("b", 2), ("a", 1)] as &[_]; assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(source, Key::from_str("a")).unwrap().to_token() ); assert_eq!( - Token::I64(2), + value::inner::Token::I64(2), Source::get(source, Key::from_str("b")).unwrap().to_token() ); assert!(Source::get(&source, Key::from_str("c")).is_none()); - let source = Option::None::<(&str, i32)>; + let source = None::<(&str, i32)>; assert!(Source::get(&source, Key::from_str("a")).is_none()); } } diff --git a/src/kv/value.rs b/src/kv/value.rs new file mode 100644 index 000000000..e604c806c --- /dev/null +++ b/src/kv/value.rs @@ -0,0 +1,1395 @@ +//! Structured values. +//! +//! This module defines the [`Value`] type and supporting APIs for +//! capturing and serializing them. + +use std::fmt; + +pub use crate::kv::Error; + +/// A type that can be converted into a [`Value`](struct.Value.html). +pub trait ToValue { + /// Perform the conversion. + fn to_value(&self) -> Value; +} + +impl<'a, T> ToValue for &'a T +where + T: ToValue + ?Sized, +{ + fn to_value(&self) -> Value { + (**self).to_value() + } +} + +impl<'v> ToValue for Value<'v> { + fn to_value(&self) -> Value { + Value { + inner: self.inner.clone(), + } + } +} + +/// A value in a key-value. +/// +/// Values are an anonymous bag containing some structured datum. +/// +/// # Capturing values +/// +/// There are a few ways to capture a value: +/// +/// - Using the `Value::from_*` methods. +/// - Using the `ToValue` trait. +/// - Using the standard `From` trait. +/// +/// ## Using the `Value::from_*` methods +/// +/// `Value` offers a few constructor methods that capture values of different kinds. +/// +/// ``` +/// use log::kv::Value; +/// +/// let value = Value::from_debug(&42i32); +/// +/// assert_eq!(None, value.to_i64()); +/// ``` +/// +/// ## Using the `ToValue` trait +/// +/// The `ToValue` trait can be used to capture values generically. +/// It's the bound used by `Source`. +/// +/// ``` +/// # use log::kv::ToValue; +/// let value = 42i32.to_value(); +/// +/// assert_eq!(Some(42), value.to_i64()); +/// ``` +/// +/// ## Using the standard `From` trait +/// +/// Standard types that implement `ToValue` also implement `From`. +/// +/// ``` +/// use log::kv::Value; +/// +/// let value = Value::from(42i32); +/// +/// assert_eq!(Some(42), value.to_i64()); +/// ``` +/// +/// # Data model +/// +/// Values can hold one of a number of types: +/// +/// - **Null:** The absence of any other meaningful value. Note that +/// `Some(Value::null())` is not the same as `None`. The former is +/// `null` while the latter is `undefined`. This is important to be +/// able to tell the difference between a key-value that was logged, +/// but its value was empty (`Some(Value::null())`) and a key-value +/// that was never logged at all (`None`). +/// - **Strings:** `str`, `char`. +/// - **Booleans:** `bool`. +/// - **Integers:** `u8`-`u128`, `i8`-`i128`, `NonZero*`. +/// - **Floating point numbers:** `f32`-`f64`. +/// - **Errors:** `dyn (Error + 'static)`. +/// - **`serde`:** Any type in `serde`'s data model. +/// - **`sval`:** Any type in `sval`'s data model. +/// +/// # Serialization +/// +/// Values provide a number of ways to be serialized. +/// +/// For basic types the [`Value::visit`] method can be used to extract the +/// underlying typed value. However, this is limited in the amount of types +/// supported (see the [`VisitValue`] trait methods). +/// +/// For more complex types one of the following traits can be used: +/// * `sval::Value`, requires the `kv_sval` feature. +/// * `serde::Serialize`, requires the `kv_serde` feature. +/// +/// You don't need a visitor to serialize values through `serde` or `sval`. +/// +/// A value can always be serialized using any supported framework, regardless +/// of how it was captured. If, for example, a value was captured using its +/// `Display` implementation, it will serialize through `serde` as a string. If it was +/// captured as a struct using `serde`, it will also serialize as a struct +/// through `sval`, or can be formatted using a `Debug`-compatible representation. +#[derive(Clone)] +pub struct Value<'v> { + inner: inner::Inner<'v>, +} + +impl<'v> Value<'v> { + /// Get a value from a type implementing `ToValue`. + pub fn from_any(value: &'v T) -> Self + where + T: ToValue, + { + value.to_value() + } + + /// Get a value from a type implementing `std::fmt::Debug`. + pub fn from_debug(value: &'v T) -> Self + where + T: fmt::Debug, + { + Value { + inner: inner::Inner::from_debug(value), + } + } + + /// Get a value from a type implementing `std::fmt::Display`. + pub fn from_display(value: &'v T) -> Self + where + T: fmt::Display, + { + Value { + inner: inner::Inner::from_display(value), + } + } + + /// Get a value from a type implementing `serde::Serialize`. + #[cfg(feature = "kv_serde")] + pub fn from_serde(value: &'v T) -> Self + where + T: serde::Serialize, + { + Value { + inner: inner::Inner::from_serde1(value), + } + } + + /// Get a value from a type implementing `sval::Value`. + #[cfg(feature = "kv_sval")] + pub fn from_sval(value: &'v T) -> Self + where + T: sval::Value, + { + Value { + inner: inner::Inner::from_sval2(value), + } + } + + /// Get a value from a dynamic `std::fmt::Debug`. + pub fn from_dyn_debug(value: &'v dyn fmt::Debug) -> Self { + Value { + inner: inner::Inner::from_dyn_debug(value), + } + } + + /// Get a value from a dynamic `std::fmt::Display`. + pub fn from_dyn_display(value: &'v dyn fmt::Display) -> Self { + Value { + inner: inner::Inner::from_dyn_display(value), + } + } + + /// Get a value from a dynamic error. + #[cfg(feature = "kv_std")] + pub fn from_dyn_error(err: &'v (dyn std::error::Error + 'static)) -> Self { + Value { + inner: inner::Inner::from_dyn_error(err), + } + } + + /// Get a `null` value. + pub fn null() -> Self { + Value { + inner: inner::Inner::empty(), + } + } + + /// Get a value from an internal primitive. + fn from_inner(value: T) -> Self + where + T: Into>, + { + Value { + inner: value.into(), + } + } + + /// Inspect this value using a simple visitor. + /// + /// When the `kv_serde` or `kv_sval` features are enabled, you can also + /// serialize a value using its `Serialize` or `Value` implementation. + pub fn visit(&self, visitor: impl VisitValue<'v>) -> Result<(), Error> { + inner::visit(&self.inner, visitor) + } +} + +impl<'v> fmt::Debug for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.inner, f) + } +} + +impl<'v> fmt::Display for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.inner, f) + } +} + +#[cfg(feature = "kv_serde")] +impl<'v> serde::Serialize for Value<'v> { + fn serialize(&self, s: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize(s) + } +} + +#[cfg(feature = "kv_sval")] +impl<'v> sval::Value for Value<'v> { + fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result { + sval::Value::stream(&self.inner, stream) + } +} + +#[cfg(feature = "kv_sval")] +impl<'v> sval_ref::ValueRef<'v> for Value<'v> { + fn stream_ref + ?Sized>(&self, stream: &mut S) -> sval::Result { + sval_ref::ValueRef::stream_ref(&self.inner, stream) + } +} + +impl ToValue for str { + fn to_value(&self) -> Value { + Value::from(self) + } +} + +impl<'v> From<&'v str> for Value<'v> { + fn from(value: &'v str) -> Self { + Value::from_inner(value) + } +} + +impl ToValue for () { + fn to_value(&self) -> Value { + Value::from_inner(()) + } +} + +impl ToValue for Option +where + T: ToValue, +{ + fn to_value(&self) -> Value { + match *self { + Some(ref value) => value.to_value(), + None => Value::from_inner(()), + } + } +} + +macro_rules! impl_to_value_primitive { + ($($into_ty:ty,)*) => { + $( + impl ToValue for $into_ty { + fn to_value(&self) -> Value { + Value::from(*self) + } + } + + impl<'v> From<$into_ty> for Value<'v> { + fn from(value: $into_ty) -> Self { + Value::from_inner(value) + } + } + + impl<'v> From<&'v $into_ty> for Value<'v> { + fn from(value: &'v $into_ty) -> Self { + Value::from_inner(*value) + } + } + )* + }; +} + +macro_rules! impl_to_value_nonzero_primitive { + ($($into_ty:ident,)*) => { + $( + impl ToValue for std::num::$into_ty { + fn to_value(&self) -> Value { + Value::from(self.get()) + } + } + + impl<'v> From for Value<'v> { + fn from(value: std::num::$into_ty) -> Self { + Value::from(value.get()) + } + } + + impl<'v> From<&'v std::num::$into_ty> for Value<'v> { + fn from(value: &'v std::num::$into_ty) -> Self { + Value::from(value.get()) + } + } + )* + }; +} + +macro_rules! impl_value_to_primitive { + ($(#[doc = $doc:tt] $into_name:ident -> $into_ty:ty,)*) => { + impl<'v> Value<'v> { + $( + #[doc = $doc] + pub fn $into_name(&self) -> Option<$into_ty> { + self.inner.$into_name() + } + )* + } + } +} + +impl_to_value_primitive![ + usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64, char, bool, +]; + +#[rustfmt::skip] +impl_to_value_nonzero_primitive![ + NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, + NonZeroIsize, NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, +]; + +impl_value_to_primitive![ + #[doc = "Try convert this value into a `u64`."] + to_u64 -> u64, + #[doc = "Try convert this value into a `i64`."] + to_i64 -> i64, + #[doc = "Try convert this value into a `u128`."] + to_u128 -> u128, + #[doc = "Try convert this value into a `i128`."] + to_i128 -> i128, + #[doc = "Try convert this value into a `f64`."] + to_f64 -> f64, + #[doc = "Try convert this value into a `char`."] + to_char -> char, + #[doc = "Try convert this value into a `bool`."] + to_bool -> bool, +]; + +impl<'v> Value<'v> { + /// Try to convert this value into an error. + #[cfg(feature = "kv_std")] + pub fn to_borrowed_error(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.inner.to_borrowed_error() + } + + /// Try to convert this value into a borrowed string. + pub fn to_borrowed_str(&self) -> Option<&'v str> { + self.inner.to_borrowed_str() + } +} + +#[cfg(feature = "kv_std")] +mod std_support { + use std::borrow::Cow; + use std::rc::Rc; + use std::sync::Arc; + + use super::*; + + impl ToValue for Box + where + T: ToValue + ?Sized, + { + fn to_value(&self) -> Value { + (**self).to_value() + } + } + + impl ToValue for Arc + where + T: ToValue + ?Sized, + { + fn to_value(&self) -> Value { + (**self).to_value() + } + } + + impl ToValue for Rc + where + T: ToValue + ?Sized, + { + fn to_value(&self) -> Value { + (**self).to_value() + } + } + + impl ToValue for String { + fn to_value(&self) -> Value { + Value::from(&**self) + } + } + + impl<'v> ToValue for Cow<'v, str> { + fn to_value(&self) -> Value { + Value::from(&**self) + } + } + + impl<'v> Value<'v> { + /// Try convert this value into a string. + pub fn to_cow_str(&self) -> Option> { + self.inner.to_str() + } + } + + impl<'v> From<&'v String> for Value<'v> { + fn from(v: &'v String) -> Self { + Value::from(&**v) + } + } +} + +/// A visitor for a [`Value`]. +/// +/// Also see [`Value`'s documentation on serialization]. Value visitors are a simple alternative +/// to a more fully-featured serialization framework like `serde` or `sval`. A value visitor +/// can differentiate primitive types through methods like [`VisitValue::visit_bool`] and +/// [`VisitValue::visit_str`], but more complex types like maps and sequences +/// will fallthrough to [`VisitValue::visit_any`]. +/// +/// If you're trying to serialize a value to a format like JSON, you can use either `serde` +/// or `sval` directly with the value. You don't need a visitor. +/// +/// [`Value`'s documentation on serialization]: Value#serialization +pub trait VisitValue<'v> { + /// Visit a `Value`. + /// + /// This is the only required method on `VisitValue` and acts as a fallback for any + /// more specific methods that aren't overridden. + /// The `Value` may be formatted using its `fmt::Debug` or `fmt::Display` implementation, + /// or serialized using its `sval::Value` or `serde::Serialize` implementation. + fn visit_any(&mut self, value: Value) -> Result<(), Error>; + + /// Visit an empty value. + fn visit_null(&mut self) -> Result<(), Error> { + self.visit_any(Value::null()) + } + + /// Visit an unsigned integer. + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { + self.visit_any(value.into()) + } + + /// Visit a signed integer. + fn visit_i64(&mut self, value: i64) -> Result<(), Error> { + self.visit_any(value.into()) + } + + /// Visit a big unsigned integer. + fn visit_u128(&mut self, value: u128) -> Result<(), Error> { + self.visit_any((value).into()) + } + + /// Visit a big signed integer. + fn visit_i128(&mut self, value: i128) -> Result<(), Error> { + self.visit_any((value).into()) + } + + /// Visit a floating point. + fn visit_f64(&mut self, value: f64) -> Result<(), Error> { + self.visit_any(value.into()) + } + + /// Visit a boolean. + fn visit_bool(&mut self, value: bool) -> Result<(), Error> { + self.visit_any(value.into()) + } + + /// Visit a string. + fn visit_str(&mut self, value: &str) -> Result<(), Error> { + self.visit_any(value.into()) + } + + /// Visit a string. + fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { + self.visit_str(value) + } + + /// Visit a Unicode character. + fn visit_char(&mut self, value: char) -> Result<(), Error> { + let mut b = [0; 4]; + self.visit_str(&*value.encode_utf8(&mut b)) + } + + /// Visit an error. + #[cfg(feature = "kv_std")] + fn visit_error(&mut self, err: &(dyn std::error::Error + 'static)) -> Result<(), Error> { + self.visit_any(Value::from_dyn_error(err)) + } + + /// Visit an error. + #[cfg(feature = "kv_std")] + fn visit_borrowed_error( + &mut self, + err: &'v (dyn std::error::Error + 'static), + ) -> Result<(), Error> { + self.visit_any(Value::from_dyn_error(err)) + } +} + +impl<'a, 'v, T: ?Sized> VisitValue<'v> for &'a mut T +where + T: VisitValue<'v>, +{ + fn visit_any(&mut self, value: Value) -> Result<(), Error> { + (**self).visit_any(value) + } + + fn visit_null(&mut self) -> Result<(), Error> { + (**self).visit_null() + } + + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { + (**self).visit_u64(value) + } + + fn visit_i64(&mut self, value: i64) -> Result<(), Error> { + (**self).visit_i64(value) + } + + fn visit_u128(&mut self, value: u128) -> Result<(), Error> { + (**self).visit_u128(value) + } + + fn visit_i128(&mut self, value: i128) -> Result<(), Error> { + (**self).visit_i128(value) + } + + fn visit_f64(&mut self, value: f64) -> Result<(), Error> { + (**self).visit_f64(value) + } + + fn visit_bool(&mut self, value: bool) -> Result<(), Error> { + (**self).visit_bool(value) + } + + fn visit_str(&mut self, value: &str) -> Result<(), Error> { + (**self).visit_str(value) + } + + fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { + (**self).visit_borrowed_str(value) + } + + fn visit_char(&mut self, value: char) -> Result<(), Error> { + (**self).visit_char(value) + } + + #[cfg(feature = "kv_std")] + fn visit_error(&mut self, err: &(dyn std::error::Error + 'static)) -> Result<(), Error> { + (**self).visit_error(err) + } + + #[cfg(feature = "kv_std")] + fn visit_borrowed_error( + &mut self, + err: &'v (dyn std::error::Error + 'static), + ) -> Result<(), Error> { + (**self).visit_borrowed_error(err) + } +} + +#[cfg(feature = "value-bag")] +pub(in crate::kv) mod inner { + /** + An implementation of `Value` based on a library called `value_bag`. + + `value_bag` was written specifically for use in `log`'s value, but was split out when it outgrew + the codebase here. It's a general-purpose type-erasure library that handles mapping between + more fully-featured serialization frameworks. + */ + use super::*; + + pub use value_bag::ValueBag as Inner; + + pub use value_bag::Error; + + #[cfg(test)] + pub use value_bag::test::TestToken as Token; + + pub fn visit<'v>( + inner: &Inner<'v>, + visitor: impl VisitValue<'v>, + ) -> Result<(), crate::kv::Error> { + struct InnerVisitValue(V); + + impl<'v, V> value_bag::visit::Visit<'v> for InnerVisitValue + where + V: VisitValue<'v>, + { + fn visit_any(&mut self, value: value_bag::ValueBag) -> Result<(), Error> { + self.0 + .visit_any(Value { inner: value }) + .map_err(crate::kv::Error::into_value) + } + + fn visit_empty(&mut self) -> Result<(), Error> { + self.0.visit_null().map_err(crate::kv::Error::into_value) + } + + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { + self.0 + .visit_u64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_i64(&mut self, value: i64) -> Result<(), Error> { + self.0 + .visit_i64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_u128(&mut self, value: u128) -> Result<(), Error> { + self.0 + .visit_u128(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_i128(&mut self, value: i128) -> Result<(), Error> { + self.0 + .visit_i128(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_f64(&mut self, value: f64) -> Result<(), Error> { + self.0 + .visit_f64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_bool(&mut self, value: bool) -> Result<(), Error> { + self.0 + .visit_bool(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_str(&mut self, value: &str) -> Result<(), Error> { + self.0 + .visit_str(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { + self.0 + .visit_borrowed_str(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_char(&mut self, value: char) -> Result<(), Error> { + self.0 + .visit_char(value) + .map_err(crate::kv::Error::into_value) + } + + #[cfg(feature = "kv_std")] + fn visit_error( + &mut self, + err: &(dyn std::error::Error + 'static), + ) -> Result<(), Error> { + self.0 + .visit_error(err) + .map_err(crate::kv::Error::into_value) + } + + #[cfg(feature = "kv_std")] + fn visit_borrowed_error( + &mut self, + err: &'v (dyn std::error::Error + 'static), + ) -> Result<(), Error> { + self.0 + .visit_borrowed_error(err) + .map_err(crate::kv::Error::into_value) + } + } + + inner + .visit(&mut InnerVisitValue(visitor)) + .map_err(crate::kv::Error::from_value) + } +} + +#[cfg(not(feature = "value-bag"))] +pub(in crate::kv) mod inner { + /** + This is a dependency-free implementation of `Value` when there's no serialization frameworks involved. + In these simple cases a more fully featured solution like `value_bag` isn't needed, so we avoid pulling it in. + + There are a few things here that need to remain consistent with the `value_bag`-based implementation: + + 1. Conversions should always produce the same results. If a conversion here returns `Some`, then + the same `value_bag`-based conversion must also. Of particular note here are floats to ints; they're + based on the standard library's `TryInto` conversions, which need to be converted to `i32` or `u32`, + and then to `f64`. + 2. VisitValues should always be called in the same way. If a particular type of value calls `visit_i64`, + then the same `value_bag`-based visitor must also. + */ + use super::*; + + #[derive(Clone)] + pub enum Inner<'v> { + None, + Bool(bool), + Str(&'v str), + Char(char), + I64(i64), + U64(u64), + F64(f64), + I128(i128), + U128(u128), + Debug(&'v dyn fmt::Debug), + Display(&'v dyn fmt::Display), + } + + impl<'v> From<()> for Inner<'v> { + fn from(_: ()) -> Self { + Inner::None + } + } + + impl<'v> From for Inner<'v> { + fn from(v: bool) -> Self { + Inner::Bool(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: char) -> Self { + Inner::Char(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: f32) -> Self { + Inner::F64(v as f64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: f64) -> Self { + Inner::F64(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i8) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i16) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i32) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i64) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: isize) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u8) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u16) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u32) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u64) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: usize) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i128) -> Self { + Inner::I128(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u128) -> Self { + Inner::U128(v) + } + } + + impl<'v> From<&'v str> for Inner<'v> { + fn from(v: &'v str) -> Self { + Inner::Str(v) + } + } + + impl<'v> fmt::Debug for Inner<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Inner::None => fmt::Debug::fmt(&None::<()>, f), + Inner::Bool(v) => fmt::Debug::fmt(v, f), + Inner::Str(v) => fmt::Debug::fmt(v, f), + Inner::Char(v) => fmt::Debug::fmt(v, f), + Inner::I64(v) => fmt::Debug::fmt(v, f), + Inner::U64(v) => fmt::Debug::fmt(v, f), + Inner::F64(v) => fmt::Debug::fmt(v, f), + Inner::I128(v) => fmt::Debug::fmt(v, f), + Inner::U128(v) => fmt::Debug::fmt(v, f), + Inner::Debug(v) => fmt::Debug::fmt(v, f), + Inner::Display(v) => fmt::Display::fmt(v, f), + } + } + } + + impl<'v> fmt::Display for Inner<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Inner::None => fmt::Debug::fmt(&None::<()>, f), + Inner::Bool(v) => fmt::Display::fmt(v, f), + Inner::Str(v) => fmt::Display::fmt(v, f), + Inner::Char(v) => fmt::Display::fmt(v, f), + Inner::I64(v) => fmt::Display::fmt(v, f), + Inner::U64(v) => fmt::Display::fmt(v, f), + Inner::F64(v) => fmt::Display::fmt(v, f), + Inner::I128(v) => fmt::Display::fmt(v, f), + Inner::U128(v) => fmt::Display::fmt(v, f), + Inner::Debug(v) => fmt::Debug::fmt(v, f), + Inner::Display(v) => fmt::Display::fmt(v, f), + } + } + } + + impl<'v> Inner<'v> { + pub fn from_debug(value: &'v T) -> Self { + Inner::Debug(value) + } + + pub fn from_display(value: &'v T) -> Self { + Inner::Display(value) + } + + pub fn from_dyn_debug(value: &'v dyn fmt::Debug) -> Self { + Inner::Debug(value) + } + + pub fn from_dyn_display(value: &'v dyn fmt::Display) -> Self { + Inner::Display(value) + } + + pub fn empty() -> Self { + Inner::None + } + + pub fn to_bool(&self) -> Option { + match self { + Inner::Bool(v) => Some(*v), + _ => None, + } + } + + pub fn to_char(&self) -> Option { + match self { + Inner::Char(v) => Some(*v), + _ => None, + } + } + + pub fn to_f64(&self) -> Option { + match self { + Inner::F64(v) => Some(*v), + Inner::I64(v) => { + let v: i32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::U64(v) => { + let v: u32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::I128(v) => { + let v: i32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::U128(v) => { + let v: u32 = (*v).try_into().ok()?; + v.try_into().ok() + } + _ => None, + } + } + + pub fn to_i64(&self) -> Option { + match self { + Inner::I64(v) => Some(*v), + Inner::U64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_u64(&self) -> Option { + match self { + Inner::U64(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_u128(&self) -> Option { + match self { + Inner::U128(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::U64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_i128(&self) -> Option { + match self { + Inner::I128(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::U64(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_borrowed_str(&self) -> Option<&'v str> { + match self { + Inner::Str(v) => Some(v), + _ => None, + } + } + + #[cfg(test)] + pub fn to_test_token(&self) -> Token { + match self { + Inner::None => Token::None, + Inner::Bool(v) => Token::Bool(*v), + Inner::Str(v) => Token::Str(*v), + Inner::Char(v) => Token::Char(*v), + Inner::I64(v) => Token::I64(*v), + Inner::U64(v) => Token::U64(*v), + Inner::F64(v) => Token::F64(*v), + Inner::I128(_) => unimplemented!(), + Inner::U128(_) => unimplemented!(), + Inner::Debug(_) => unimplemented!(), + Inner::Display(_) => unimplemented!(), + } + } + } + + #[cfg(test)] + #[derive(Debug, PartialEq)] + pub enum Token<'v> { + None, + Bool(bool), + Char(char), + Str(&'v str), + F64(f64), + I64(i64), + U64(u64), + } + + pub fn visit<'v>( + inner: &Inner<'v>, + mut visitor: impl VisitValue<'v>, + ) -> Result<(), crate::kv::Error> { + match inner { + Inner::None => visitor.visit_null(), + Inner::Bool(v) => visitor.visit_bool(*v), + Inner::Str(v) => visitor.visit_borrowed_str(*v), + Inner::Char(v) => visitor.visit_char(*v), + Inner::I64(v) => visitor.visit_i64(*v), + Inner::U64(v) => visitor.visit_u64(*v), + Inner::F64(v) => visitor.visit_f64(*v), + Inner::I128(v) => visitor.visit_i128(*v), + Inner::U128(v) => visitor.visit_u128(*v), + Inner::Debug(v) => visitor.visit_any(Value::from_dyn_debug(*v)), + Inner::Display(v) => visitor.visit_any(Value::from_dyn_display(*v)), + } + } +} + +impl<'v> Value<'v> { + /// Get a value from a type implementing `std::fmt::Debug`. + #[cfg(feature = "kv_unstable")] + #[deprecated(note = "use `from_debug` instead")] + pub fn capture_debug(value: &'v T) -> Self + where + T: fmt::Debug + 'static, + { + Value::from_debug(value) + } + + /// Get a value from a type implementing `std::fmt::Display`. + #[cfg(feature = "kv_unstable")] + #[deprecated(note = "use `from_display` instead")] + pub fn capture_display(value: &'v T) -> Self + where + T: fmt::Display + 'static, + { + Value::from_display(value) + } + + /// Get a value from an error. + #[cfg(feature = "kv_unstable_std")] + #[deprecated(note = "use `from_dyn_error` instead")] + pub fn capture_error(err: &'v T) -> Self + where + T: std::error::Error + 'static, + { + Value::from_dyn_error(err) + } + + /// Get a value from a type implementing `serde::Serialize`. + #[cfg(feature = "kv_unstable_serde")] + #[deprecated(note = "use `from_serde` instead")] + pub fn capture_serde(value: &'v T) -> Self + where + T: serde::Serialize + 'static, + { + Value::from_serde(value) + } + + /// Get a value from a type implementing `sval::Value`. + #[cfg(feature = "kv_unstable_sval")] + #[deprecated(note = "use `from_sval` instead")] + pub fn capture_sval(value: &'v T) -> Self + where + T: sval::Value + 'static, + { + Value::from_sval(value) + } + + /// Check whether this value can be downcast to `T`. + #[cfg(feature = "kv_unstable")] + #[deprecated( + note = "downcasting has been removed; log an issue at https://github.com/rust-lang/log/issues if this is something you rely on" + )] + pub fn is(&self) -> bool { + false + } + + /// Try downcast this value to `T`. + #[cfg(feature = "kv_unstable")] + #[deprecated( + note = "downcasting has been removed; log an issue at https://github.com/rust-lang/log/issues if this is something you rely on" + )] + pub fn downcast_ref(&self) -> Option<&T> { + None + } +} + +// NOTE: Deprecated; but aliases can't carry this attribute +#[cfg(feature = "kv_unstable")] +pub use VisitValue as Visit; + +/// Get a value from a type implementing `std::fmt::Debug`. +#[cfg(feature = "kv_unstable")] +#[deprecated(note = "use the `key:? = value` macro syntax instead")] +#[macro_export] +macro_rules! as_debug { + ($capture:expr) => { + $crate::kv::Value::from_debug(&$capture) + }; +} + +/// Get a value from a type implementing `std::fmt::Display`. +#[cfg(feature = "kv_unstable")] +#[deprecated(note = "use the `key:% = value` macro syntax instead")] +#[macro_export] +macro_rules! as_display { + ($capture:expr) => { + $crate::kv::Value::from_display(&$capture) + }; +} + +/// Get a value from an error. +#[cfg(feature = "kv_unstable_std")] +#[deprecated(note = "use the `key:err = value` macro syntax instead")] +#[macro_export] +macro_rules! as_error { + ($capture:expr) => { + $crate::kv::Value::from_dyn_error(&$capture) + }; +} + +#[cfg(feature = "kv_unstable_serde")] +#[deprecated(note = "use the `key:serde = value` macro syntax instead")] +/// Get a value from a type implementing `serde::Serialize`. +#[macro_export] +macro_rules! as_serde { + ($capture:expr) => { + $crate::kv::Value::from_serde(&$capture) + }; +} + +/// Get a value from a type implementing `sval::Value`. +#[cfg(feature = "kv_unstable_sval")] +#[deprecated(note = "use the `key:sval = value` macro syntax instead")] +#[macro_export] +macro_rules! as_sval { + ($capture:expr) => { + $crate::kv::Value::from_sval(&$capture) + }; +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + impl<'v> Value<'v> { + pub(crate) fn to_token(&self) -> inner::Token { + self.inner.to_test_token() + } + } + + fn unsigned() -> impl Iterator> { + vec![ + Value::from(8u8), + Value::from(16u16), + Value::from(32u32), + Value::from(64u64), + Value::from(1usize), + Value::from(std::num::NonZeroU8::new(8).unwrap()), + Value::from(std::num::NonZeroU16::new(16).unwrap()), + Value::from(std::num::NonZeroU32::new(32).unwrap()), + Value::from(std::num::NonZeroU64::new(64).unwrap()), + Value::from(std::num::NonZeroUsize::new(1).unwrap()), + ] + .into_iter() + } + + fn signed() -> impl Iterator> { + vec![ + Value::from(-8i8), + Value::from(-16i16), + Value::from(-32i32), + Value::from(-64i64), + Value::from(-1isize), + Value::from(std::num::NonZeroI8::new(-8).unwrap()), + Value::from(std::num::NonZeroI16::new(-16).unwrap()), + Value::from(std::num::NonZeroI32::new(-32).unwrap()), + Value::from(std::num::NonZeroI64::new(-64).unwrap()), + Value::from(std::num::NonZeroIsize::new(-1).unwrap()), + ] + .into_iter() + } + + fn float() -> impl Iterator> { + vec![Value::from(32.32f32), Value::from(64.64f64)].into_iter() + } + + fn bool() -> impl Iterator> { + vec![Value::from(true), Value::from(false)].into_iter() + } + + fn str() -> impl Iterator> { + vec![Value::from("a string"), Value::from("a loong string")].into_iter() + } + + fn char() -> impl Iterator> { + vec![Value::from('a'), Value::from('⛰')].into_iter() + } + + #[test] + fn test_to_value_display() { + assert_eq!(42u64.to_value().to_string(), "42"); + assert_eq!(42i64.to_value().to_string(), "42"); + assert_eq!(42.01f64.to_value().to_string(), "42.01"); + assert_eq!(true.to_value().to_string(), "true"); + assert_eq!('a'.to_value().to_string(), "a"); + assert_eq!("a loong string".to_value().to_string(), "a loong string"); + assert_eq!(Some(true).to_value().to_string(), "true"); + assert_eq!(().to_value().to_string(), "None"); + assert_eq!(None::.to_value().to_string(), "None"); + } + + #[test] + fn test_to_value_structured() { + assert_eq!(42u64.to_value().to_token(), inner::Token::U64(42)); + assert_eq!(42i64.to_value().to_token(), inner::Token::I64(42)); + assert_eq!(42.01f64.to_value().to_token(), inner::Token::F64(42.01)); + assert_eq!(true.to_value().to_token(), inner::Token::Bool(true)); + assert_eq!('a'.to_value().to_token(), inner::Token::Char('a')); + assert_eq!( + "a loong string".to_value().to_token(), + inner::Token::Str("a loong string".into()) + ); + assert_eq!(Some(true).to_value().to_token(), inner::Token::Bool(true)); + assert_eq!(().to_value().to_token(), inner::Token::None); + assert_eq!(None::.to_value().to_token(), inner::Token::None); + } + + #[test] + fn test_to_number() { + for v in unsigned() { + assert!(v.to_u64().is_some()); + assert!(v.to_i64().is_some()); + } + + for v in signed() { + assert!(v.to_i64().is_some()); + } + + for v in unsigned().chain(signed()).chain(float()) { + assert!(v.to_f64().is_some()); + } + + for v in bool().chain(str()).chain(char()) { + assert!(v.to_u64().is_none()); + assert!(v.to_i64().is_none()); + assert!(v.to_f64().is_none()); + } + } + + #[test] + fn test_to_float() { + // Only integers from i32::MIN..=u32::MAX can be converted into floats + assert!(Value::from(i32::MIN).to_f64().is_some()); + assert!(Value::from(u32::MAX).to_f64().is_some()); + + assert!(Value::from((i32::MIN as i64) - 1).to_f64().is_none()); + assert!(Value::from((u32::MAX as u64) + 1).to_f64().is_none()); + } + + #[test] + fn test_to_cow_str() { + for v in str() { + assert!(v.to_borrowed_str().is_some()); + + #[cfg(feature = "kv_std")] + assert!(v.to_cow_str().is_some()); + } + + let short_lived = String::from("short lived"); + let v = Value::from(&*short_lived); + + assert!(v.to_borrowed_str().is_some()); + + #[cfg(feature = "kv_std")] + assert!(v.to_cow_str().is_some()); + + for v in unsigned().chain(signed()).chain(float()).chain(bool()) { + assert!(v.to_borrowed_str().is_none()); + + #[cfg(feature = "kv_std")] + assert!(v.to_cow_str().is_none()); + } + } + + #[test] + fn test_to_bool() { + for v in bool() { + assert!(v.to_bool().is_some()); + } + + for v in unsigned() + .chain(signed()) + .chain(float()) + .chain(str()) + .chain(char()) + { + assert!(v.to_bool().is_none()); + } + } + + #[test] + fn test_to_char() { + for v in char() { + assert!(v.to_char().is_some()); + } + + for v in unsigned() + .chain(signed()) + .chain(float()) + .chain(str()) + .chain(bool()) + { + assert!(v.to_char().is_none()); + } + } + + #[test] + fn test_visit_integer() { + struct Extract(Option); + + impl<'v> VisitValue<'v> for Extract { + fn visit_any(&mut self, value: Value) -> Result<(), Error> { + unimplemented!("unexpected value: {value:?}") + } + + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { + self.0 = Some(value); + + Ok(()) + } + } + + let mut extract = Extract(None); + Value::from(42u64).visit(&mut extract).unwrap(); + + assert_eq!(Some(42), extract.0); + } + + #[test] + fn test_visit_borrowed_str() { + struct Extract<'v>(Option<&'v str>); + + impl<'v> VisitValue<'v> for Extract<'v> { + fn visit_any(&mut self, value: Value) -> Result<(), Error> { + unimplemented!("unexpected value: {value:?}") + } + + fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { + self.0 = Some(value); + + Ok(()) + } + } + + let mut extract = Extract(None); + + let short_lived = String::from("A short-lived string"); + Value::from(&*short_lived).visit(&mut extract).unwrap(); + + assert_eq!(Some("A short-lived string"), extract.0); + } +} diff --git a/src/kv/value/fill.rs b/src/kv/value/fill.rs deleted file mode 100644 index d14a64258..000000000 --- a/src/kv/value/fill.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Lazy value initialization. - -use std::fmt; - -use super::internal::{Erased, Inner, Visitor}; -use super::{Error, Value}; - -impl<'v> Value<'v> { - /// Get a value from a fillable slot. - pub fn from_fill(value: &'v T) -> Self - where - T: Fill + 'static, - { - Value { - inner: Inner::Fill(unsafe { Erased::new_unchecked::(value) }), - } - } -} - -/// A type that requires extra work to convert into a [`Value`](struct.Value.html). -/// -/// This trait is a more advanced initialization API than [`ToValue`](trait.ToValue.html). -/// It's intended for erased values coming from other logging frameworks that may need -/// to perform extra work to determine the concrete type to use. -pub trait Fill { - /// Fill a value. - fn fill(&self, slot: &mut Slot) -> Result<(), Error>; -} - -impl<'a, T> Fill for &'a T -where - T: Fill + ?Sized, -{ - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - (**self).fill(slot) - } -} - -/// A value slot to fill using the [`Fill`](trait.Fill.html) trait. -pub struct Slot<'s, 'f> { - filled: bool, - visitor: &'s mut dyn Visitor<'f>, -} - -impl<'s, 'f> fmt::Debug for Slot<'s, 'f> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Slot").finish() - } -} - -impl<'s, 'f> Slot<'s, 'f> { - pub(super) fn new(visitor: &'s mut dyn Visitor<'f>) -> Self { - Slot { - visitor, - filled: false, - } - } - - pub(super) fn fill(&mut self, f: F) -> Result<(), Error> - where - F: FnOnce(&mut dyn Visitor<'f>) -> Result<(), Error>, - { - assert!(!self.filled, "the slot has already been filled"); - self.filled = true; - - f(self.visitor) - } - - /// Fill the slot with a value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_any(&mut self, value: T) -> Result<(), Error> - where - T: Into>, - { - self.fill(|visitor| value.into().inner.visit(visitor)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fill_value_borrowed() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - let dbg: &dyn fmt::Debug = &1; - - slot.fill_debug(&dbg) - } - } - - assert_eq!("1", Value::from_fill(&TestFill).to_string()); - } - - #[test] - fn fill_value_owned() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any("a string") - } - } - } - - #[test] - #[should_panic] - fn fill_multiple_times_panics() { - struct BadFill; - - impl Fill for BadFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any(42)?; - slot.fill_any(6789)?; - - Ok(()) - } - } - - let _ = Value::from_fill(&BadFill).to_string(); - } - - #[test] - fn fill_cast() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any("a string") - } - } - - assert_eq!( - "a string", - Value::from_fill(&TestFill) - .to_borrowed_str() - .expect("invalid value") - ); - } - - #[test] - fn fill_debug() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any(42u64) - } - } - - assert_eq!( - format!("{:04?}", 42u64), - format!("{:04?}", Value::from_fill(&TestFill)), - ) - } -} diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs deleted file mode 100644 index a6169f101..000000000 --- a/src/kv/value/impls.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! Converting standard types into `Value`s. -//! -//! This module provides `ToValue` implementations for commonly -//! logged types from the standard library. - -use std::fmt; - -use super::{Primitive, ToValue, Value}; - -macro_rules! impl_into_owned { - ($($into_ty:ty => $convert:ident,)*) => { - $( - impl ToValue for $into_ty { - fn to_value(&self) -> Value { - Value::from(*self) - } - } - - impl<'v> From<$into_ty> for Value<'v> { - fn from(value: $into_ty) -> Self { - Value::from_primitive(value as $convert) - } - } - )* - }; -} - -impl<'v> ToValue for &'v str { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From<&'v str> for Value<'v> { - fn from(value: &'v str) -> Self { - Value::from_primitive(value) - } -} - -impl<'v> ToValue for fmt::Arguments<'v> { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From> for Value<'v> { - fn from(value: fmt::Arguments<'v>) -> Self { - Value::from_primitive(value) - } -} - -impl ToValue for () { - fn to_value(&self) -> Value { - Value::from_primitive(Primitive::None) - } -} - -impl ToValue for Option -where - T: ToValue, -{ - fn to_value(&self) -> Value { - match *self { - Some(ref value) => value.to_value(), - None => Value::from_primitive(Primitive::None), - } - } -} - -impl_into_owned! [ - usize => u64, - u8 => u64, - u16 => u64, - u32 => u64, - u64 => u64, - - isize => i64, - i8 => i64, - i16 => i64, - i32 => i64, - i64 => i64, - - f32 => f64, - f64 => f64, - - char => char, - bool => bool, -]; - -#[cfg(feature = "std")] -mod std_support { - use super::*; - - use std::borrow::Cow; - - impl ToValue for Box - where - T: ToValue + ?Sized, - { - fn to_value(&self) -> Value { - (**self).to_value() - } - } - - impl ToValue for String { - fn to_value(&self) -> Value { - Value::from_primitive(Primitive::Str(&*self)) - } - } - - impl<'v> ToValue for Cow<'v, str> { - fn to_value(&self) -> Value { - Value::from_primitive(Primitive::Str(&*self)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use kv::value::test::Token; - - #[test] - fn test_to_value_display() { - assert_eq!(42u64.to_value().to_string(), "42"); - assert_eq!(42i64.to_value().to_string(), "42"); - assert_eq!(42.01f64.to_value().to_string(), "42.01"); - assert_eq!(true.to_value().to_string(), "true"); - assert_eq!('a'.to_value().to_string(), "a"); - assert_eq!( - format_args!("a {}", "value").to_value().to_string(), - "a value" - ); - assert_eq!("a loong string".to_value().to_string(), "a loong string"); - assert_eq!(Some(true).to_value().to_string(), "true"); - assert_eq!(().to_value().to_string(), "None"); - assert_eq!(Option::None::.to_value().to_string(), "None"); - } - - #[test] - fn test_to_value_structured() { - assert_eq!(42u64.to_value().to_token(), Token::U64(42)); - assert_eq!(42i64.to_value().to_token(), Token::I64(42)); - assert_eq!(42.01f64.to_value().to_token(), Token::F64(42.01)); - assert_eq!(true.to_value().to_token(), Token::Bool(true)); - assert_eq!('a'.to_value().to_token(), Token::Char('a')); - assert_eq!( - format_args!("a {}", "value").to_value().to_token(), - Token::Str("a value".into()) - ); - assert_eq!( - "a loong string".to_value().to_token(), - Token::Str("a loong string".into()) - ); - assert_eq!(Some(true).to_value().to_token(), Token::Bool(true)); - assert_eq!(().to_value().to_token(), Token::None); - assert_eq!(Option::None::.to_value().to_token(), Token::None); - } -} diff --git a/src/kv/value/internal/cast.rs b/src/kv/value/internal/cast.rs deleted file mode 100644 index d2aa86e63..000000000 --- a/src/kv/value/internal/cast.rs +++ /dev/null @@ -1,475 +0,0 @@ -//! Coerce a `Value` into some concrete types. -//! -//! These operations are cheap when the captured value is a simple primitive, -//! but may end up executing arbitrary caller code if the value is complex. -//! They will also attempt to downcast erased types into a primitive where possible. - -use std::any::TypeId; -use std::fmt; - -use super::{Erased, Inner, Primitive, Visitor}; -use crate::kv::value::{Error, Value}; - -impl<'v> Value<'v> { - /// Try get a `usize` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_usize(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_u64() - .map(|v| v as usize) - } - - /// Try get a `u8` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_u8(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_u64() - .map(|v| v as u8) - } - - /// Try get a `u16` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_u16(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_u64() - .map(|v| v as u16) - } - - /// Try get a `u32` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_u32(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_u64() - .map(|v| v as u32) - } - - /// Try get a `u64` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_u64(&self) -> Option { - self.inner.cast().into_primitive().into_u64() - } - - /// Try get a `isize` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_isize(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_i64() - .map(|v| v as isize) - } - - /// Try get a `i8` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_i8(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_i64() - .map(|v| v as i8) - } - - /// Try get a `i16` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_i16(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_i64() - .map(|v| v as i16) - } - - /// Try get a `i32` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_i32(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_i64() - .map(|v| v as i32) - } - - /// Try get a `i64` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_i64(&self) -> Option { - self.inner.cast().into_primitive().into_i64() - } - - /// Try get a `f32` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_f32(&self) -> Option { - self.inner - .cast() - .into_primitive() - .into_f64() - .map(|v| v as f32) - } - - /// Try get a `f64` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_f64(&self) -> Option { - self.inner.cast().into_primitive().into_f64() - } - - /// Try get a `bool` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_bool(&self) -> Option { - self.inner.cast().into_primitive().into_bool() - } - - /// Try get a `char` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. - pub fn to_char(&self) -> Option { - self.inner.cast().into_primitive().into_char() - } - - /// Try get a `str` from this value. - /// - /// This method is cheap for primitive types. It won't allocate an owned - /// `String` if the value is a complex type. - pub fn to_borrowed_str(&self) -> Option<&str> { - self.inner.cast().into_primitive().into_borrowed_str() - } -} - -impl<'v> Inner<'v> { - /// Cast the inner value to another type. - fn cast(self) -> Cast<'v> { - struct CastVisitor<'v>(Cast<'v>); - - impl<'v> Visitor<'v> for CastVisitor<'v> { - fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Unsigned(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Signed(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Float(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Bool(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Char(v)); - Ok(()) - } - - fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::Str(v)); - Ok(()) - } - - #[cfg(not(feature = "std"))] - fn str(&mut self, _: &str) -> Result<(), Error> { - Ok(()) - } - - #[cfg(feature = "std")] - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0 = Cast::String(v.into()); - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.0 = Cast::Primitive(Primitive::None); - Ok(()) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { - self.0 = super::sval::cast(v); - Ok(()) - } - } - - // Try downcast an erased value first - // It also lets us avoid the Visitor infrastructure for simple primitives - let primitive = match self { - Inner::Primitive(value) => Some(value), - Inner::Fill(value) => value.downcast_primitive(), - Inner::Debug(value) => value.downcast_primitive(), - Inner::Display(value) => value.downcast_primitive(), - - #[cfg(feature = "sval")] - Inner::Sval(value) => value.downcast_primitive(), - }; - - primitive.map(Cast::Primitive).unwrap_or_else(|| { - // If the erased value isn't a primitive then we visit it - let mut cast = CastVisitor(Cast::Primitive(Primitive::None)); - let _ = self.visit(&mut cast); - cast.0 - }) - } -} - -pub(super) enum Cast<'v> { - Primitive(Primitive<'v>), - #[cfg(feature = "std")] - String(String), -} - -impl<'v> Cast<'v> { - fn into_primitive(self) -> Primitive<'v> { - match self { - Cast::Primitive(value) => value, - #[cfg(feature = "std")] - _ => Primitive::None, - } - } -} - -impl<'v> Primitive<'v> { - fn into_borrowed_str(self) -> Option<&'v str> { - if let Primitive::Str(value) = self { - Some(value) - } else { - None - } - } - - fn into_u64(self) -> Option { - match self { - Primitive::Unsigned(value) => Some(value), - Primitive::Signed(value) => Some(value as u64), - Primitive::Float(value) => Some(value as u64), - _ => None, - } - } - - fn into_i64(self) -> Option { - match self { - Primitive::Signed(value) => Some(value), - Primitive::Unsigned(value) => Some(value as i64), - Primitive::Float(value) => Some(value as i64), - _ => None, - } - } - - fn into_f64(self) -> Option { - match self { - Primitive::Float(value) => Some(value), - Primitive::Unsigned(value) => Some(value as f64), - Primitive::Signed(value) => Some(value as f64), - _ => None, - } - } - - fn into_char(self) -> Option { - if let Primitive::Char(value) = self { - Some(value) - } else { - None - } - } - - fn into_bool(self) -> Option { - if let Primitive::Bool(value) = self { - Some(value) - } else { - None - } - } -} - -impl<'v, T: ?Sized + 'static> Erased<'v, T> { - // NOTE: This function is a perfect candidate for memoization - // The outcome could be stored in a `Cell` - fn downcast_primitive(self) -> Option> { - macro_rules! type_ids { - ($($value:ident : $ty:ty => $cast:expr,)*) => {{ - struct TypeIds; - - impl TypeIds { - fn downcast_primitive<'v, T: ?Sized>(&self, value: Erased<'v, T>) -> Option> { - $( - if TypeId::of::<$ty>() == value.type_id { - let $value = unsafe { value.downcast_unchecked::<$ty>() }; - return Some(Primitive::from($cast)); - } - )* - - None - } - } - - TypeIds - }}; - } - - let type_ids = type_ids![ - value: usize => *value as u64, - value: u8 => *value as u64, - value: u16 => *value as u64, - value: u32 => *value as u64, - value: u64 => *value, - - value: isize => *value as i64, - value: i8 => *value as i64, - value: i16 => *value as i64, - value: i32 => *value as i64, - value: i64 => *value, - - value: f32 => *value as f64, - value: f64 => *value, - - value: char => *value, - value: bool => *value, - - value: &str => *value, - ]; - - type_ids.downcast_primitive(self) - } -} - -#[cfg(feature = "std")] -mod std_support { - use super::*; - - use std::borrow::Cow; - - impl<'v> Value<'v> { - /// Try get a `usize` from this value. - /// - /// This method is cheap for primitive types, but may call arbitrary - /// serialization implementations for complex ones. If the serialization - /// implementation produces a short lived string it will be allocated. - pub fn to_str(&self) -> Option> { - self.inner.cast().into_str() - } - } - - impl<'v> Cast<'v> { - pub(super) fn into_str(self) -> Option> { - match self { - Cast::Primitive(Primitive::Str(value)) => Some(value.into()), - Cast::String(value) => Some(value.into()), - _ => None, - } - } - } - - #[cfg(test)] - mod tests { - use crate::kv::ToValue; - - #[test] - fn primitive_cast() { - assert_eq!( - "a string", - "a string" - .to_owned() - .to_value() - .to_borrowed_str() - .expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string".to_value().to_str().expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string" - .to_owned() - .to_value() - .to_str() - .expect("invalid value") - ); - } - } -} - -#[cfg(test)] -mod tests { - use crate::kv::ToValue; - - #[test] - fn primitive_cast() { - assert_eq!( - "a string", - "a string" - .to_value() - .to_borrowed_str() - .expect("invalid value") - ); - assert_eq!( - "a string", - Some("a string") - .to_value() - .to_borrowed_str() - .expect("invalid value") - ); - - assert_eq!(1u8, 1u64.to_value().to_u8().expect("invalid value")); - assert_eq!(1u16, 1u64.to_value().to_u16().expect("invalid value")); - assert_eq!(1u32, 1u64.to_value().to_u32().expect("invalid value")); - assert_eq!(1u64, 1u64.to_value().to_u64().expect("invalid value")); - assert_eq!(1usize, 1u64.to_value().to_usize().expect("invalid value")); - - assert_eq!(-1i8, -1i64.to_value().to_i8().expect("invalid value")); - assert_eq!(-1i16, -1i64.to_value().to_i16().expect("invalid value")); - assert_eq!(-1i32, -1i64.to_value().to_i32().expect("invalid value")); - assert_eq!(-1i64, -1i64.to_value().to_i64().expect("invalid value")); - assert_eq!(-1isize, -1i64.to_value().to_isize().expect("invalid value")); - - assert!(1f32.to_value().to_f32().is_some(), "invalid value"); - assert!(1f64.to_value().to_f64().is_some(), "invalid value"); - - assert_eq!(1u32, 1i64.to_value().to_u32().expect("invalid value")); - assert_eq!(1i32, 1u64.to_value().to_i32().expect("invalid value")); - assert!(1f32.to_value().to_i32().is_some(), "invalid value"); - - assert_eq!('a', 'a'.to_value().to_char().expect("invalid value")); - assert_eq!(true, true.to_value().to_bool().expect("invalid value")); - } -} diff --git a/src/kv/value/internal/fmt.rs b/src/kv/value/internal/fmt.rs deleted file mode 100644 index eabd4dd15..000000000 --- a/src/kv/value/internal/fmt.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Integration between `Value` and `std::fmt`. -//! -//! This module allows any `Value` to implement the `fmt::Debug` and `fmt::Display` traits, -//! and for any `fmt::Debug` or `fmt::Display` to be captured as a `Value`. - -use std::fmt; - -use super::{Erased, Inner, Visitor}; -use crate::kv; -use crate::kv::value::{Error, Slot}; - -impl<'v> kv::Value<'v> { - /// Get a value from a debuggable type. - pub fn from_debug(value: &'v T) -> Self - where - T: fmt::Debug + 'static, - { - kv::Value { - inner: Inner::Debug(unsafe { Erased::new_unchecked::(value) }), - } - } - - /// Get a value from a displayable type. - pub fn from_display(value: &'v T) -> Self - where - T: fmt::Display + 'static, - { - kv::Value { - inner: Inner::Display(unsafe { Erased::new_unchecked::(value) }), - } - } -} - -impl<'s, 'f> Slot<'s, 'f> { - /// Fill the slot with a debuggable value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_debug(&mut self, value: T) -> Result<(), Error> - where - T: fmt::Debug, - { - self.fill(|visitor| visitor.debug(&value)) - } - - /// Fill the slot with a displayable value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_display(&mut self, value: T) -> Result<(), Error> - where - T: fmt::Display, - { - self.fill(|visitor| visitor.display(&value)) - } -} - -pub(in kv::value) use self::fmt::{Arguments, Debug, Display}; - -impl<'v> fmt::Debug for kv::Value<'v> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - struct DebugVisitor<'a, 'b: 'a>(&'a mut fmt::Formatter<'b>); - - impl<'a, 'b: 'a, 'v> Visitor<'v> for DebugVisitor<'a, 'b> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - fmt::Debug::fmt(v, self.0)?; - - Ok(()) - } - - fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { - fmt::Display::fmt(v, self.0)?; - - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - fmt::Debug::fmt(&v, self.0)?; - - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.debug(&format_args!("None")) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { - super::sval::fmt(self.0, v) - } - } - - self.visit(&mut DebugVisitor(f)).map_err(|_| fmt::Error)?; - - Ok(()) - } -} - -impl<'v> fmt::Display for kv::Value<'v> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - struct DisplayVisitor<'a, 'b: 'a>(&'a mut fmt::Formatter<'b>); - - impl<'a, 'b: 'a, 'v> Visitor<'v> for DisplayVisitor<'a, 'b> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - fmt::Debug::fmt(v, self.0)?; - - Ok(()) - } - - fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { - fmt::Display::fmt(v, self.0)?; - - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - fmt::Display::fmt(&v, self.0)?; - - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.debug(&format_args!("None")) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { - super::sval::fmt(self.0, v) - } - } - - self.visit(&mut DisplayVisitor(f)).map_err(|_| fmt::Error)?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::kv::value::ToValue; - - #[test] - fn fmt_cast() { - assert_eq!( - 42u32, - kv::Value::from_debug(&42u64) - .to_u32() - .expect("invalid value") - ); - - assert_eq!( - "a string", - kv::Value::from_display(&"a string") - .to_borrowed_str() - .expect("invalid value") - ); - } - - #[test] - fn fmt_debug() { - assert_eq!( - format!("{:?}", "a string"), - format!("{:?}", "a string".to_value()), - ); - - assert_eq!( - format!("{:04?}", 42u64), - format!("{:04?}", 42u64.to_value()), - ); - } - - #[test] - fn fmt_display() { - assert_eq!( - format!("{}", "a string"), - format!("{}", "a string".to_value()), - ); - - assert_eq!(format!("{:04}", 42u64), format!("{:04}", 42u64.to_value()),); - } -} diff --git a/src/kv/value/internal/mod.rs b/src/kv/value/internal/mod.rs deleted file mode 100644 index 429f0db98..000000000 --- a/src/kv/value/internal/mod.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! The internal `Value` serialization API. -//! -//! This implementation isn't intended to be public. It may need to change -//! for optimizations or to support new external serialization frameworks. - -use std::any::TypeId; - -use super::{Error, Fill, Slot}; - -pub(super) mod cast; -pub(super) mod fmt; -#[cfg(feature = "kv_unstable_sval")] -pub(super) mod sval; - -/// A container for a structured value for a specific kind of visitor. -#[derive(Clone, Copy)] -pub(super) enum Inner<'v> { - /// A simple primitive value that can be copied without allocating. - Primitive(Primitive<'v>), - /// A value that can be filled. - Fill(Erased<'v, dyn Fill + 'static>), - /// A debuggable value. - Debug(Erased<'v, dyn fmt::Debug + 'static>), - /// A displayable value. - Display(Erased<'v, dyn fmt::Display + 'static>), - - #[cfg(feature = "kv_unstable_sval")] - /// A structured value from `sval`. - Sval(Erased<'v, dyn sval::Value + 'static>), -} - -impl<'v> Inner<'v> { - pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { - match self { - Inner::Primitive(value) => value.visit(visitor), - Inner::Fill(value) => value.get().fill(&mut Slot::new(visitor)), - Inner::Debug(value) => visitor.debug(value.get()), - Inner::Display(value) => visitor.display(value.get()), - - #[cfg(feature = "kv_unstable_sval")] - Inner::Sval(value) => visitor.sval(value.get()), - } - } -} - -/// The internal serialization contract. -pub(super) trait Visitor<'v> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error>; - fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { - self.debug(&format_args!("{}", v)) - } - - fn u64(&mut self, v: u64) -> Result<(), Error>; - fn i64(&mut self, v: i64) -> Result<(), Error>; - fn f64(&mut self, v: f64) -> Result<(), Error>; - fn bool(&mut self, v: bool) -> Result<(), Error>; - fn char(&mut self, v: char) -> Result<(), Error>; - - fn str(&mut self, v: &str) -> Result<(), Error>; - fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { - self.str(v) - } - - fn none(&mut self) -> Result<(), Error>; - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn sval::Value) -> Result<(), Error>; -} - -/// A captured primitive value. -/// -/// These values are common and cheap to copy around. -#[derive(Clone, Copy)] -pub(super) enum Primitive<'v> { - Signed(i64), - Unsigned(u64), - Float(f64), - Bool(bool), - Char(char), - Str(&'v str), - Fmt(fmt::Arguments<'v>), - None, -} - -impl<'v> Primitive<'v> { - fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { - match self { - Primitive::Signed(value) => visitor.i64(value), - Primitive::Unsigned(value) => visitor.u64(value), - Primitive::Float(value) => visitor.f64(value), - Primitive::Bool(value) => visitor.bool(value), - Primitive::Char(value) => visitor.char(value), - Primitive::Str(value) => visitor.borrowed_str(value), - Primitive::Fmt(value) => visitor.debug(&value), - Primitive::None => visitor.none(), - } - } -} - -impl<'v> From for Primitive<'v> { - fn from(v: u64) -> Self { - Primitive::Unsigned(v) - } -} - -impl<'v> From for Primitive<'v> { - fn from(v: i64) -> Self { - Primitive::Signed(v) - } -} - -impl<'v> From for Primitive<'v> { - fn from(v: f64) -> Self { - Primitive::Float(v) - } -} - -impl<'v> From for Primitive<'v> { - fn from(v: bool) -> Self { - Primitive::Bool(v) - } -} - -impl<'v> From for Primitive<'v> { - fn from(v: char) -> Self { - Primitive::Char(v) - } -} - -impl<'v> From<&'v str> for Primitive<'v> { - fn from(v: &'v str) -> Self { - Primitive::Str(v) - } -} - -impl<'v> From> for Primitive<'v> { - fn from(v: fmt::Arguments<'v>) -> Self { - Primitive::Fmt(v) - } -} - -/// A downcastable dynamic type. -pub(super) struct Erased<'v, T: ?Sized> { - type_id: TypeId, - inner: &'v T, -} - -impl<'v, T: ?Sized> Clone for Erased<'v, T> { - fn clone(&self) -> Self { - Erased { - type_id: self.type_id, - inner: self.inner, - } - } -} - -impl<'v, T: ?Sized> Copy for Erased<'v, T> {} - -impl<'v, T: ?Sized> Erased<'v, T> { - // SAFETY: `U: Unsize` and the underlying value `T` must not change - // We could add a safe variant of this method with the `Unsize` trait - pub(super) unsafe fn new_unchecked(inner: &'v T) -> Self - where - U: 'static, - T: 'static, - { - Erased { - type_id: TypeId::of::(), - inner, - } - } - - pub(super) fn get(self) -> &'v T { - self.inner - } - - // SAFETY: The underlying type of `T` is `U` - pub(super) unsafe fn downcast_unchecked(self) -> &'v U { - &*(self.inner as *const T as *const U) - } -} diff --git a/src/kv/value/internal/sval.rs b/src/kv/value/internal/sval.rs deleted file mode 100644 index 0d76f4e1f..000000000 --- a/src/kv/value/internal/sval.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Integration between `Value` and `sval`. -//! -//! This module allows any `Value` to implement the `sval::Value` trait, -//! and for any `sval::Value` to be captured as a `Value`. - -extern crate sval; - -use std::fmt; - -use super::cast::Cast; -use super::{Erased, Inner, Primitive, Visitor}; -use crate::kv; -use crate::kv::value::{Error, Slot}; - -impl<'v> kv::Value<'v> { - /// Get a value from a structured type. - pub fn from_sval(value: &'v T) -> Self - where - T: sval::Value + 'static, - { - kv::Value { - inner: Inner::Sval(unsafe { Erased::new_unchecked::(value) }), - } - } -} - -impl<'s, 'f> Slot<'s, 'f> { - /// Fill the slot with a structured value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_sval(&mut self, value: T) -> Result<(), Error> - where - T: sval::Value, - { - self.fill(|visitor| visitor.sval(&value)) - } -} - -impl<'v> sval::Value for kv::Value<'v> { - fn stream(&self, s: &mut sval::value::Stream) -> sval::value::Result { - struct SvalVisitor<'a, 'b: 'a>(&'a mut sval::value::Stream<'b>); - - impl<'a, 'b: 'a, 'v> Visitor<'v> for SvalVisitor<'a, 'b> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - self.0 - .fmt(format_args!("{:?}", v)) - .map_err(Error::from_sval) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0.u64(v).map_err(Error::from_sval) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0.i64(v).map_err(Error::from_sval) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0.f64(v).map_err(Error::from_sval) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0.bool(v).map_err(Error::from_sval) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0.char(v).map_err(Error::from_sval) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0.str(v).map_err(Error::from_sval) - } - - fn none(&mut self) -> Result<(), Error> { - self.0.none().map_err(Error::from_sval) - } - - fn sval(&mut self, v: &dyn sval::Value) -> Result<(), Error> { - self.0.any(v).map_err(Error::from_sval) - } - } - - self.visit(&mut SvalVisitor(s)).map_err(Error::into_sval)?; - - Ok(()) - } -} - -pub(in kv::value) use self::sval::Value; - -pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Error> { - sval::fmt::debug(f, v)?; - Ok(()) -} - -pub(super) fn cast<'v>(v: &dyn sval::Value) -> Cast<'v> { - struct CastStream<'v>(Cast<'v>); - - impl<'v> sval::Stream for CastStream<'v> { - fn u64(&mut self, v: u64) -> sval::stream::Result { - self.0 = Cast::Primitive(Primitive::Unsigned(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> sval::stream::Result { - self.0 = Cast::Primitive(Primitive::Signed(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> sval::stream::Result { - self.0 = Cast::Primitive(Primitive::Float(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> sval::stream::Result { - self.0 = Cast::Primitive(Primitive::Char(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> sval::stream::Result { - self.0 = Cast::Primitive(Primitive::Bool(v)); - Ok(()) - } - - #[cfg(feature = "std")] - fn str(&mut self, s: &str) -> sval::stream::Result { - self.0 = Cast::String(s.into()); - Ok(()) - } - } - - let mut cast = CastStream(Cast::Primitive(Primitive::None)); - let _ = sval::stream(&mut cast, v); - - cast.0 -} - -impl Error { - fn from_sval(_: sval::value::Error) -> Self { - Error::msg("`sval` serialization failed") - } - - fn into_sval(self) -> sval::value::Error { - sval::value::Error::msg("`sval` serialization failed") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use kv::value::test::Token; - - #[test] - fn test_from_sval() { - assert_eq!(kv::Value::from_sval(&42u64).to_token(), Token::Sval); - } - - #[test] - fn test_sval_structured() { - let value = kv::Value::from(42u64); - let expected = vec![sval::test::Token::Unsigned(42)]; - - assert_eq!(sval::test::tokens(value), expected); - } - - #[test] - fn sval_cast() { - assert_eq!( - 42u32, - kv::Value::from_sval(&42u64) - .to_u32() - .expect("invalid value") - ); - - assert_eq!( - "a string", - kv::Value::from_sval(&"a string") - .to_borrowed_str() - .expect("invalid value") - ); - - #[cfg(feature = "std")] - assert_eq!( - "a string", - kv::Value::from_sval(&"a string") - .to_str() - .expect("invalid value") - ); - } - - #[test] - fn sval_debug() { - struct TestSval; - - impl sval::Value for TestSval { - fn stream(&self, stream: &mut sval::value::Stream) -> sval::value::Result { - stream.u64(42) - } - } - - assert_eq!( - format!("{:04?}", 42u64), - format!("{:04?}", kv::Value::from_sval(&TestSval)), - ); - } -} diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs deleted file mode 100644 index 422adb0b5..000000000 --- a/src/kv/value/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Structured values. - -mod fill; -mod impls; -mod internal; - -#[cfg(test)] -pub(in kv) mod test; - -pub use self::fill::{Fill, Slot}; -pub use kv::Error; - -use self::internal::{Inner, Primitive, Visitor}; - -/// A type that can be converted into a [`Value`](struct.Value.html). -pub trait ToValue { - /// Perform the conversion. - fn to_value(&self) -> Value; -} - -impl<'a, T> ToValue for &'a T -where - T: ToValue + ?Sized, -{ - fn to_value(&self) -> Value { - (**self).to_value() - } -} - -impl<'v> ToValue for Value<'v> { - fn to_value(&self) -> Value { - Value { inner: self.inner } - } -} - -/// A value in a structured key-value pair. -pub struct Value<'v> { - inner: Inner<'v>, -} - -impl<'v> Value<'v> { - /// Get a value from an internal primitive. - fn from_primitive(value: T) -> Self - where - T: Into>, - { - Value { - inner: Inner::Primitive(value.into()), - } - } - - /// Visit the value using an internal visitor. - fn visit<'a>(&'a self, visitor: &mut dyn Visitor<'a>) -> Result<(), Error> { - self.inner.visit(visitor) - } -} diff --git a/src/kv/value/test.rs b/src/kv/value/test.rs deleted file mode 100644 index ab5f8075e..000000000 --- a/src/kv/value/test.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Test support for inspecting Values - -use std::fmt; -use std::str; - -use super::internal; -use super::{Error, Value}; - -#[derive(Debug, PartialEq)] -pub(in kv) enum Token { - U64(u64), - I64(i64), - F64(f64), - Char(char), - Bool(bool), - Str(String), - None, - - #[cfg(feature = "kv_unstable_sval")] - Sval, -} - -#[cfg(test)] -impl<'v> Value<'v> { - pub(in kv) fn to_token(&self) -> Token { - struct TestVisitor(Option); - - impl<'v> internal::Visitor<'v> for TestVisitor { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - self.0 = Some(Token::Str(format!("{:?}", v))); - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0 = Some(Token::U64(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0 = Some(Token::I64(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0 = Some(Token::F64(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0 = Some(Token::Bool(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0 = Some(Token::Char(v)); - Ok(()) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0 = Some(Token::Str(v.into())); - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.0 = Some(Token::None); - Ok(()) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, _: &dyn internal::sval::Value) -> Result<(), Error> { - self.0 = Some(Token::Sval); - Ok(()) - } - } - - let mut visitor = TestVisitor(None); - self.visit(&mut visitor).unwrap(); - - visitor.0.unwrap() - } -} diff --git a/src/lib.rs b/src/lib.rs index 0dca5e3cc..47f2cf132 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ //! though that default may be overridden. Logger implementations typically use //! the target to filter requests based on some user configuration. //! -//! # Use +//! # Usage //! //! The basic use of the log crate is through the five logging macros: [`error!`], //! [`warn!`], [`info!`], [`debug!`] and [`trace!`] @@ -41,6 +41,8 @@ //! [`trace!`]: ./macro.trace.html //! [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html //! +//! Avoid writing expressions with side-effects in log statements. They may not be evaluated. +//! //! ## In libraries //! //! Libraries should link only to the `log` crate, and use the provided @@ -48,24 +50,24 @@ //! //! ### Examples //! -//! ```edition2018 +//! ``` //! # #[derive(Debug)] pub struct Yak(String); //! # impl Yak { fn shave(&mut self, _: u32) {} } //! # fn find_a_razor() -> Result { Ok(1) } //! use log::{info, warn}; //! //! pub fn shave_the_yak(yak: &mut Yak) { -//! info!(target: "yak_events", "Commencing yak shaving for {:?}", yak); +//! info!(target: "yak_events", "Commencing yak shaving for {yak:?}"); //! //! loop { //! match find_a_razor() { //! Ok(razor) => { -//! info!("Razor located: {}", razor); +//! info!("Razor located: {razor}"); //! yak.shave(razor); //! break; //! } //! Err(err) => { -//! warn!("Unable to locate a razor: {}, retrying", err); +//! warn!("Unable to locate a razor: {err}, retrying"); //! } //! } //! } @@ -86,6 +88,44 @@ //! //! The logging system may only be initialized once. //! +//! ## Structured logging +//! +//! If you enable the `kv` feature you can associate structured values +//! with your log records. If we take the example from before, we can include +//! some additional context besides what's in the formatted message: +//! +//! ``` +//! # use serde::Serialize; +//! # #[derive(Debug, Serialize)] pub struct Yak(String); +//! # impl Yak { fn shave(&mut self, _: u32) {} } +//! # fn find_a_razor() -> Result { Ok(1) } +//! # #[cfg(feature = "kv_serde")] +//! # fn main() { +//! use log::{info, warn}; +//! +//! pub fn shave_the_yak(yak: &mut Yak) { +//! info!(target: "yak_events", yak:serde; "Commencing yak shaving"); +//! +//! loop { +//! match find_a_razor() { +//! Ok(razor) => { +//! info!(razor; "Razor located"); +//! yak.shave(razor); +//! break; +//! } +//! Err(e) => { +//! warn!(e:err; "Unable to locate a razor, retrying"); +//! } +//! } +//! } +//! } +//! # } +//! # #[cfg(not(feature = "kv_serde"))] +//! # fn main() {} +//! ``` +//! +//! See the [`kv`] module documentation for more details. +//! //! # Available logging implementations //! //! In order to produce log output executables have to use @@ -95,17 +135,37 @@ //! //! * Simple minimal loggers: //! * [env_logger] +//! * [colog] //! * [simple_logger] //! * [simplelog] //! * [pretty_env_logger] //! * [stderrlog] //! * [flexi_logger] +//! * [call_logger] +//! * [structured-logger] +//! * [clang_log] +//! * [ftail] //! * Complex configurable frameworks: //! * [log4rs] +//! * [logforth] //! * [fern] +//! * [spdlog-rs] //! * Adaptors for other facilities: //! * [syslog] //! * [slog-stdlog] +//! * [systemd-journal-logger] +//! * [android_log] +//! * [win_dbg_logger] +//! * [db_logger] +//! * [log-to-defmt] +//! * [logcontrol-log] +//! * For WebAssembly binaries: +//! * [console_log] +//! * For dynamic libraries: +//! * You may need to construct an FFI-safe wrapper over `log` to initialize in your libraries +//! * Utilities: +//! * [log_err] +//! * [log-reload] //! //! # Implementing a Logger //! @@ -113,7 +173,7 @@ //! logs all messages at the [`Error`][level_link], [`Warn`][level_link] or //! [`Info`][level_link] levels to stdout: //! -//! ```edition2018 +//! ``` //! use log::{Record, Level, Metadata}; //! //! struct SimpleLogger; @@ -146,7 +206,7 @@ //! provide a function that wraps a call to [`set_logger`] and //! [`set_max_level`], handling initialization of the logger: //! -//! ```edition2018 +//! ``` //! # use log::{Level, Metadata}; //! # struct SimpleLogger; //! # impl log::Log for SimpleLogger { @@ -176,7 +236,7 @@ //! identical to `set_logger` except that it takes a `Box` rather than a //! `&'static Log`: //! -//! ```edition2018 +//! ``` //! # use log::{Level, LevelFilter, Log, SetLoggerError, Metadata}; //! # struct SimpleLogger; //! # impl log::Log for SimpleLogger { @@ -194,9 +254,7 @@ //! //! # Compile time filters //! -//! Log levels can be statically disabled at compile time via Cargo features. Log invocations at -//! disabled levels will be skipped and will not even be present in the resulting binary. -//! This level is configured separately for release and debug builds. The features are: +//! Log levels can be statically disabled at compile time by enabling one of these Cargo features: //! //! * `max_level_off` //! * `max_level_error` @@ -204,6 +262,13 @@ //! * `max_level_info` //! * `max_level_debug` //! * `max_level_trace` +//! +//! Log invocations at disabled levels will be skipped and will not even be present in the +//! resulting binary. These features control the value of the `STATIC_MAX_LEVEL` constant. The +//! logging macros check this value before logging a message. By default, no levels are disabled. +//! +//! It is possible to override this level for release builds only with the following features: +//! //! * `release_max_level_off` //! * `release_max_level_error` //! * `release_max_level_warn` @@ -211,9 +276,6 @@ //! * `release_max_level_debug` //! * `release_max_level_trace` //! -//! These features control the value of the `STATIC_MAX_LEVEL` constant. The logging macros check -//! this value before logging a message. By default, no levels are disabled. -//! //! Libraries should avoid using the max level features because they're global and can't be changed //! once they're set. //! @@ -230,7 +292,7 @@ //! configured in your `Cargo.toml`. //! //! * `std` allows use of `std` crate instead of the default `core`. Enables using `std::error` and -//! `set_boxed_logger` functionality. +//! `set_boxed_logger` functionality. //! * `serde` enables support for serialization and deserialization of `Level` and `LevelFilter`. //! //! ```toml @@ -253,50 +315,129 @@ //! [`try_set_logger_raw`]: fn.try_set_logger_raw.html //! [`shutdown_logger_raw`]: fn.shutdown_logger_raw.html //! [env_logger]: https://docs.rs/env_logger/*/env_logger/ +//! [colog]: https://docs.rs/colog/*/colog/ //! [simple_logger]: https://github.com/borntyping/rust-simple_logger //! [simplelog]: https://github.com/drakulix/simplelog.rs //! [pretty_env_logger]: https://docs.rs/pretty_env_logger/*/pretty_env_logger/ //! [stderrlog]: https://docs.rs/stderrlog/*/stderrlog/ //! [flexi_logger]: https://docs.rs/flexi_logger/*/flexi_logger/ +//! [call_logger]: https://docs.rs/call_logger/*/call_logger/ //! [syslog]: https://docs.rs/syslog/*/syslog/ //! [slog-stdlog]: https://docs.rs/slog-stdlog/*/slog_stdlog/ //! [log4rs]: https://docs.rs/log4rs/*/log4rs/ +//! [logforth]: https://docs.rs/logforth/*/logforth/ //! [fern]: https://docs.rs/fern/*/fern/ +//! [spdlog-rs]: https://docs.rs/spdlog-rs/*/spdlog/ +//! [systemd-journal-logger]: https://docs.rs/systemd-journal-logger/*/systemd_journal_logger/ +//! [android_log]: https://docs.rs/android_log/*/android_log/ +//! [win_dbg_logger]: https://docs.rs/win_dbg_logger/*/win_dbg_logger/ +//! [db_logger]: https://docs.rs/db_logger/*/db_logger/ +//! [log-to-defmt]: https://docs.rs/log-to-defmt/*/log_to_defmt/ +//! [console_log]: https://docs.rs/console_log/*/console_log/ +//! [structured-logger]: https://docs.rs/structured-logger/latest/structured_logger/ +//! [logcontrol-log]: https://docs.rs/logcontrol-log/*/logcontrol_log/ +//! [log_err]: https://docs.rs/log_err/*/log_err/ +//! [log-reload]: https://docs.rs/log-reload/*/log_reload/ +//! [clang_log]: https://docs.rs/clang_log/latest/clang_log +//! [ftail]: https://docs.rs/ftail/latest/ftail #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", html_favicon_url = "https://www.rust-lang.org/favicon.ico", - html_root_url = "https://docs.rs/log/0.4.11" + html_root_url = "https://docs.rs/log/0.4.28" )] #![warn(missing_docs)] -#![deny(missing_debug_implementations)] +#![deny(missing_debug_implementations, unconditional_recursion)] #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] -// 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"))] + +#[cfg(any( + all(feature = "max_level_off", feature = "max_level_error"), + all(feature = "max_level_off", feature = "max_level_warn"), + all(feature = "max_level_off", feature = "max_level_info"), + all(feature = "max_level_off", feature = "max_level_debug"), + all(feature = "max_level_off", feature = "max_level_trace"), + all(feature = "max_level_error", feature = "max_level_warn"), + all(feature = "max_level_error", feature = "max_level_info"), + all(feature = "max_level_error", feature = "max_level_debug"), + all(feature = "max_level_error", feature = "max_level_trace"), + all(feature = "max_level_warn", feature = "max_level_info"), + all(feature = "max_level_warn", feature = "max_level_debug"), + all(feature = "max_level_warn", feature = "max_level_trace"), + all(feature = "max_level_info", feature = "max_level_debug"), + all(feature = "max_level_info", feature = "max_level_trace"), + all(feature = "max_level_debug", feature = "max_level_trace"), +))] +compile_error!("multiple max_level_* features set"); + +#[rustfmt::skip] +#[cfg(any( + all(feature = "release_max_level_off", feature = "release_max_level_error"), + all(feature = "release_max_level_off", feature = "release_max_level_warn"), + all(feature = "release_max_level_off", feature = "release_max_level_info"), + all(feature = "release_max_level_off", feature = "release_max_level_debug"), + all(feature = "release_max_level_off", feature = "release_max_level_trace"), + all(feature = "release_max_level_error", feature = "release_max_level_warn"), + all(feature = "release_max_level_error", feature = "release_max_level_info"), + all(feature = "release_max_level_error", feature = "release_max_level_debug"), + all(feature = "release_max_level_error", feature = "release_max_level_trace"), + all(feature = "release_max_level_warn", feature = "release_max_level_info"), + all(feature = "release_max_level_warn", feature = "release_max_level_debug"), + all(feature = "release_max_level_warn", feature = "release_max_level_trace"), + all(feature = "release_max_level_info", feature = "release_max_level_debug"), + all(feature = "release_max_level_info", feature = "release_max_level_trace"), + all(feature = "release_max_level_debug", feature = "release_max_level_trace"), +))] +compile_error!("multiple release_max_level_* features set"); #[cfg(all(not(feature = "std"), not(test)))] extern crate core as std; -#[macro_use] -extern crate cfg_if; - -use std::cmp; +use std::cfg; #[cfg(feature = "std")] use std::error; -use std::fmt; -use std::mem; use std::str::FromStr; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{cmp, fmt, mem}; #[macro_use] mod macros; mod serde; -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] pub mod kv; +#[cfg(target_has_atomic = "ptr")] +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(not(target_has_atomic = "ptr"))] +use std::cell::Cell; +#[cfg(not(target_has_atomic = "ptr"))] +use std::sync::atomic::Ordering; + +#[cfg(not(target_has_atomic = "ptr"))] +struct AtomicUsize { + v: Cell, +} + +#[cfg(not(target_has_atomic = "ptr"))] +impl AtomicUsize { + const fn new(v: usize) -> AtomicUsize { + AtomicUsize { v: Cell::new(v) } + } + + fn load(&self, _order: Ordering) -> usize { + self.v.get() + } + + fn store(&self, val: usize, _order: Ordering) { + self.v.set(val) + } +} + +// Any platform without atomics is unlikely to have multiple cores, so +// writing via Cell will not be a race condition. +#[cfg(not(target_has_atomic = "ptr"))] +unsafe impl Sync for AtomicUsize {} + // The LOGGER static holds a pointer to the global logger. It is protected by // the STATE static which determines whether LOGGER has been initialized yet. static mut LOGGER: &dyn Log = &NopLogger; @@ -326,7 +467,7 @@ static LEVEL_PARSE_ERROR: &str = /// [`log!`](macro.log.html), and comparing a `Level` directly to a /// [`LevelFilter`](enum.LevelFilter.html). #[repr(usize)] -#[derive(Copy, Eq, Debug, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum Level { /// The "error" level. /// @@ -353,20 +494,6 @@ pub enum Level { Trace, } -impl Clone for Level { - #[inline] - fn clone(&self) -> Level { - *self - } -} - -impl PartialEq for Level { - #[inline] - fn eq(&self, other: &Level) -> bool { - *self as usize == *other as usize - } -} - impl PartialEq for Level { #[inline] fn eq(&self, other: &LevelFilter) -> bool { @@ -374,112 +501,30 @@ impl PartialEq for Level { } } -impl PartialOrd for Level { - #[inline] - fn partial_cmp(&self, other: &Level) -> Option { - Some(self.cmp(other)) - } - - #[inline] - fn lt(&self, other: &Level) -> bool { - (*self as usize) < *other as usize - } - - #[inline] - fn le(&self, other: &Level) -> bool { - *self as usize <= *other as usize - } - - #[inline] - fn gt(&self, other: &Level) -> bool { - *self as usize > *other as usize - } - - #[inline] - fn ge(&self, other: &Level) -> bool { - *self as usize >= *other as usize - } -} - impl PartialOrd for Level { #[inline] fn partial_cmp(&self, other: &LevelFilter) -> Option { Some((*self as usize).cmp(&(*other as usize))) } - - #[inline] - fn lt(&self, other: &LevelFilter) -> bool { - (*self as usize) < *other as usize - } - - #[inline] - fn le(&self, other: &LevelFilter) -> bool { - *self as usize <= *other as usize - } - - #[inline] - fn gt(&self, other: &LevelFilter) -> bool { - *self as usize > *other as usize - } - - #[inline] - fn ge(&self, other: &LevelFilter) -> bool { - *self as usize >= *other as usize - } -} - -impl Ord for Level { - #[inline] - fn cmp(&self, other: &Level) -> cmp::Ordering { - (*self as usize).cmp(&(*other as usize)) - } -} - -fn ok_or(t: Option, e: E) -> Result { - match t { - Some(t) => Ok(t), - None => Err(e), - } -} - -// Reimplemented here because std::ascii is not available in libcore -fn eq_ignore_ascii_case(a: &str, b: &str) -> bool { - fn to_ascii_uppercase(c: u8) -> u8 { - if c >= b'a' && c <= b'z' { - c - b'a' + b'A' - } else { - c - } - } - - if a.len() == b.len() { - a.bytes() - .zip(b.bytes()) - .all(|(a, b)| to_ascii_uppercase(a) == to_ascii_uppercase(b)) - } else { - false - } } impl FromStr for Level { type Err = ParseLevelError; fn from_str(level: &str) -> Result { - ok_or( - LOG_LEVEL_NAMES - .iter() - .position(|&name| eq_ignore_ascii_case(name, level)) - .into_iter() - .filter(|&idx| idx != 0) - .map(|idx| Level::from_usize(idx).unwrap()) - .next(), - ParseLevelError(()), - ) + LOG_LEVEL_NAMES + .iter() + .position(|&name| name.eq_ignore_ascii_case(level)) + .into_iter() + .filter(|&idx| idx != 0) + .map(|idx| Level::from_usize(idx).unwrap()) + .next() + .ok_or(ParseLevelError(())) } } impl fmt::Display for Level { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.pad(LOG_LEVEL_NAMES[*self as usize]) + fmt.pad(self.as_str()) } } @@ -506,6 +551,73 @@ impl Level { pub fn to_level_filter(&self) -> LevelFilter { LevelFilter::from_usize(*self as usize).unwrap() } + + /// Returns the string representation of the `Level`. + /// + /// This returns the same string as the `fmt::Display` implementation. + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[*self as usize] + } + + /// Iterate through all supported logging levels. + /// + /// The order of iteration is from more severe to less severe log messages. + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// + /// let mut levels = Level::iter(); + /// + /// assert_eq!(Some(Level::Error), levels.next()); + /// assert_eq!(Some(Level::Trace), levels.last()); + /// ``` + pub fn iter() -> impl Iterator { + (1..6).map(|i| Self::from_usize(i).unwrap()) + } + + /// Get the next-highest `Level` from this one. + /// + /// If the current `Level` is at the highest level, the returned `Level` will be the same as the + /// current one. + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// + /// let level = Level::Info; + /// + /// assert_eq!(Level::Debug, level.increment_severity()); + /// assert_eq!(Level::Trace, level.increment_severity().increment_severity()); + /// assert_eq!(Level::Trace, level.increment_severity().increment_severity().increment_severity()); // max level + /// ``` + pub fn increment_severity(&self) -> Self { + let current = *self as usize; + Self::from_usize(current + 1).unwrap_or(*self) + } + + /// Get the next-lowest `Level` from this one. + /// + /// If the current `Level` is at the lowest level, the returned `Level` will be the same as the + /// current one. + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// + /// let level = Level::Info; + /// + /// assert_eq!(Level::Warn, level.decrement_severity()); + /// assert_eq!(Level::Error, level.decrement_severity().decrement_severity()); + /// assert_eq!(Level::Error, level.decrement_severity().decrement_severity().decrement_severity()); // min level + /// ``` + pub fn decrement_severity(&self) -> Self { + let current = *self as usize; + Self::from_usize(current.saturating_sub(1)).unwrap_or(*self) + } } /// An enum representing the available verbosity level filters of the logger. @@ -517,7 +629,7 @@ impl Level { /// [`max_level()`]: fn.max_level.html /// [`set_max_level`]: fn.set_max_level.html #[repr(usize)] -#[derive(Copy, Eq, Debug, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum LevelFilter { /// A level lower than all log levels. Off, @@ -533,22 +645,6 @@ pub enum LevelFilter { Trace, } -// Deriving generates terrible impls of these traits - -impl Clone for LevelFilter { - #[inline] - fn clone(&self) -> LevelFilter { - *self - } -} - -impl PartialEq for LevelFilter { - #[inline] - fn eq(&self, other: &LevelFilter) -> bool { - *self as usize == *other as usize - } -} - impl PartialEq for LevelFilter { #[inline] fn eq(&self, other: &Level) -> bool { @@ -556,83 +652,27 @@ impl PartialEq for LevelFilter { } } -impl PartialOrd for LevelFilter { - #[inline] - fn partial_cmp(&self, other: &LevelFilter) -> Option { - Some(self.cmp(other)) - } - - #[inline] - fn lt(&self, other: &LevelFilter) -> bool { - (*self as usize) < *other as usize - } - - #[inline] - fn le(&self, other: &LevelFilter) -> bool { - *self as usize <= *other as usize - } - - #[inline] - fn gt(&self, other: &LevelFilter) -> bool { - *self as usize > *other as usize - } - - #[inline] - fn ge(&self, other: &LevelFilter) -> bool { - *self as usize >= *other as usize - } -} - impl PartialOrd for LevelFilter { #[inline] fn partial_cmp(&self, other: &Level) -> Option { Some((*self as usize).cmp(&(*other as usize))) } - - #[inline] - fn lt(&self, other: &Level) -> bool { - (*self as usize) < *other as usize - } - - #[inline] - fn le(&self, other: &Level) -> bool { - *self as usize <= *other as usize - } - - #[inline] - fn gt(&self, other: &Level) -> bool { - *self as usize > *other as usize - } - - #[inline] - fn ge(&self, other: &Level) -> bool { - *self as usize >= *other as usize - } -} - -impl Ord for LevelFilter { - #[inline] - fn cmp(&self, other: &LevelFilter) -> cmp::Ordering { - (*self as usize).cmp(&(*other as usize)) - } } impl FromStr for LevelFilter { type Err = ParseLevelError; fn from_str(level: &str) -> Result { - ok_or( - LOG_LEVEL_NAMES - .iter() - .position(|&name| eq_ignore_ascii_case(name, level)) - .map(|p| LevelFilter::from_usize(p).unwrap()), - ParseLevelError(()), - ) + LOG_LEVEL_NAMES + .iter() + .position(|&name| name.eq_ignore_ascii_case(level)) + .map(|p| LevelFilter::from_usize(p).unwrap()) + .ok_or(ParseLevelError(())) } } impl fmt::Display for LevelFilter { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.pad(LOG_LEVEL_NAMES[*self as usize]) + fmt.pad(self.as_str()) } } @@ -648,6 +688,7 @@ impl LevelFilter { _ => None, } } + /// Returns the most verbose logging level filter. #[inline] pub fn max() -> LevelFilter { @@ -661,6 +702,74 @@ impl LevelFilter { pub fn to_level(&self) -> Option { Level::from_usize(*self as usize) } + + /// Returns the string representation of the `LevelFilter`. + /// + /// This returns the same string as the `fmt::Display` implementation. + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[*self as usize] + } + + /// Iterate through all supported filtering levels. + /// + /// The order of iteration is from less to more verbose filtering. + /// + /// # Examples + /// + /// ``` + /// use log::LevelFilter; + /// + /// let mut levels = LevelFilter::iter(); + /// + /// assert_eq!(Some(LevelFilter::Off), levels.next()); + /// assert_eq!(Some(LevelFilter::Trace), levels.last()); + /// ``` + pub fn iter() -> impl Iterator { + (0..6).map(|i| Self::from_usize(i).unwrap()) + } + + /// Get the next-highest `LevelFilter` from this one. + /// + /// If the current `LevelFilter` is at the highest level, the returned `LevelFilter` will be the + /// same as the current one. + /// + /// # Examples + /// + /// ``` + /// use log::LevelFilter; + /// + /// let level_filter = LevelFilter::Info; + /// + /// assert_eq!(LevelFilter::Debug, level_filter.increment_severity()); + /// assert_eq!(LevelFilter::Trace, level_filter.increment_severity().increment_severity()); + /// assert_eq!(LevelFilter::Trace, level_filter.increment_severity().increment_severity().increment_severity()); // max level + /// ``` + pub fn increment_severity(&self) -> Self { + let current = *self as usize; + Self::from_usize(current + 1).unwrap_or(*self) + } + + /// Get the next-lowest `LevelFilter` from this one. + /// + /// If the current `LevelFilter` is at the lowest level, the returned `LevelFilter` will be the + /// same as the current one. + /// + /// # Examples + /// + /// ``` + /// use log::LevelFilter; + /// + /// let level_filter = LevelFilter::Info; + /// + /// assert_eq!(LevelFilter::Warn, level_filter.decrement_severity()); + /// assert_eq!(LevelFilter::Error, level_filter.decrement_severity().decrement_severity()); + /// assert_eq!(LevelFilter::Off, level_filter.decrement_severity().decrement_severity().decrement_severity()); + /// assert_eq!(LevelFilter::Off, level_filter.decrement_severity().decrement_severity().decrement_severity().decrement_severity()); // min level + /// ``` + pub fn decrement_severity(&self) -> Self { + let current = *self as usize; + Self::from_usize(current.saturating_sub(1)).unwrap_or(*self) + } } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] @@ -697,11 +806,11 @@ impl<'a> MaybeStaticStr<'a> { /// The following example shows a simple logger that displays the level, /// module path, and message of any `Record` that is passed to it. /// -/// ```edition2018 +/// ``` /// struct SimpleLogger; /// /// impl log::Log for SimpleLogger { -/// fn enabled(&self, metadata: &log::Metadata) -> bool { +/// fn enabled(&self, _metadata: &log::Metadata) -> bool { /// true /// } /// @@ -731,7 +840,7 @@ pub struct Record<'a> { module_path: Option>, file: Option>, line: Option, - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] key_values: KeyValues<'a>, } @@ -739,11 +848,11 @@ pub struct Record<'a> { // `#[derive(Debug)]` on `Record`. It also // provides a useful `Debug` implementation for // the underlying `Source`. -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] #[derive(Clone)] struct KeyValues<'a>(&'a dyn kv::Source); -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] impl<'a> fmt::Debug for KeyValues<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut visitor = f.debug_map(); @@ -804,7 +913,7 @@ impl<'a> Record<'a> { self.file.map(|s| s.get()) } - /// The module path of the message, if it is a `'static` string. + /// The source file containing the message, if it is a `'static` string. #[inline] pub fn file_static(&self) -> Option<&'static str> { match self.file { @@ -819,15 +928,15 @@ impl<'a> Record<'a> { self.line } - /// The structued key-value pairs associated with the message. - #[cfg(feature = "kv_unstable")] + /// The structured key-value pairs associated with the message. + #[cfg(feature = "kv")] #[inline] pub fn key_values(&self) -> &dyn kv::Source { self.key_values.0 } /// Create a new [`RecordBuilder`](struct.RecordBuilder.html) based on this record. - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] #[inline] pub fn to_builder(&self) -> RecordBuilder { RecordBuilder { @@ -854,8 +963,7 @@ impl<'a> Record<'a> { /// /// # Examples /// -/// -/// ```edition2018 +/// ``` /// use log::{Level, Record}; /// /// let record = Record::builder() @@ -870,7 +978,7 @@ impl<'a> Record<'a> { /// /// Alternatively, use [`MetadataBuilder`](struct.MetadataBuilder.html): /// -/// ```edition2018 +/// ``` /// use log::{Record, Level, MetadataBuilder}; /// /// let error_metadata = MetadataBuilder::new() @@ -913,8 +1021,8 @@ impl<'a> RecordBuilder<'a> { module_path: None, file: None, line: None, - #[cfg(feature = "kv_unstable")] - key_values: KeyValues(&Option::None::<(kv::Key, kv::Value)>), + #[cfg(feature = "kv")] + key_values: KeyValues(&None::<(kv::Key, kv::Value)>), }, } } @@ -983,7 +1091,7 @@ impl<'a> RecordBuilder<'a> { } /// Set [`key_values`](struct.Record.html#method.key_values) - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] #[inline] pub fn key_values(&mut self, kvs: &'a dyn kv::Source) -> &mut RecordBuilder<'a> { self.record.key_values = KeyValues(kvs); @@ -997,6 +1105,12 @@ impl<'a> RecordBuilder<'a> { } } +impl Default for RecordBuilder<'_> { + fn default() -> Self { + Self::new() + } +} + /// Metadata about a log message. /// /// # Use @@ -1015,7 +1129,7 @@ impl<'a> RecordBuilder<'a> { /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::{Record, Level, Metadata}; /// /// struct MyLogger; @@ -1069,7 +1183,7 @@ impl<'a> Metadata<'a> { /// /// # Example /// -/// ```edition2018 +/// ``` /// let target = "myApp"; /// use log::{Level, MetadataBuilder}; /// let metadata = MetadataBuilder::new() @@ -1120,6 +1234,12 @@ impl<'a> MetadataBuilder<'a> { } } +impl Default for MetadataBuilder<'_> { + fn default() -> Self { + Self::new() + } +} + /// A trait encapsulating the operations required of a logger. pub trait Log: Sync + Send { /// Determines if a log message with the specified metadata would be @@ -1128,20 +1248,33 @@ pub trait Log: Sync + Send { /// This is used by the `log_enabled!` macro to allow callers to avoid /// expensive computation of log message arguments if the message would be /// discarded anyway. + /// + /// # For implementors + /// + /// This method isn't called automatically by the `log!` macros. + /// It's up to an implementation of the `Log` trait to call `enabled` in its own + /// `log` method implementation to guarantee that filtering is applied. fn enabled(&self, metadata: &Metadata) -> bool; /// Logs the `Record`. /// + /// # For implementors + /// /// Note that `enabled` is *not* necessarily called before this method. /// Implementations of `log` should perform all necessary filtering /// internally. fn log(&self, record: &Record); /// Flushes any buffered records. + /// + /// # For implementors + /// + /// This method isn't called automatically by the `log!` macros. + /// It can be called manually on shut-down to ensure any in-flight records are flushed. fn flush(&self); } -// Just used as a dummy initial value for LOGGER +/// A dummy initial value for LOGGER. struct NopLogger; impl Log for NopLogger { @@ -1153,12 +1286,92 @@ impl Log for NopLogger { fn flush(&self) {} } +impl Log for &'_ T +where + T: ?Sized + Log, +{ + fn enabled(&self, metadata: &Metadata) -> bool { + (**self).enabled(metadata) + } + + fn log(&self, record: &Record) { + (**self).log(record); + } + fn flush(&self) { + (**self).flush(); + } +} + +#[cfg(feature = "std")] +impl Log for std::boxed::Box +where + T: ?Sized + Log, +{ + fn enabled(&self, metadata: &Metadata) -> bool { + self.as_ref().enabled(metadata) + } + + fn log(&self, record: &Record) { + self.as_ref().log(record); + } + fn flush(&self) { + self.as_ref().flush(); + } +} + +#[cfg(feature = "std")] +impl Log for std::sync::Arc +where + T: ?Sized + Log, +{ + fn enabled(&self, metadata: &Metadata) -> bool { + self.as_ref().enabled(metadata) + } + + fn log(&self, record: &Record) { + self.as_ref().log(record); + } + fn flush(&self) { + self.as_ref().flush(); + } +} + /// Sets the global maximum log level. /// /// Generally, this should only be called by the active logging implementation. +/// +/// Note that `Trace` is the maximum level, because it provides the maximum amount of detail in the emitted logs. #[inline] +#[cfg(target_has_atomic = "ptr")] pub fn set_max_level(level: LevelFilter) { - MAX_LOG_LEVEL_FILTER.store(level as usize, Ordering::SeqCst) + MAX_LOG_LEVEL_FILTER.store(level as usize, Ordering::Relaxed); +} + +/// A thread-unsafe version of [`set_max_level`]. +/// +/// This function is available on all platforms, even those that do not have +/// support for atomics that is needed by [`set_max_level`]. +/// +/// In almost all cases, [`set_max_level`] should be preferred. +/// +/// # Safety +/// +/// This function is only safe to call when it cannot race with any other +/// calls to `set_max_level` or `set_max_level_racy`. +/// +/// This can be upheld by (for example) making sure that **there are no other +/// threads**, and (on embedded) that **interrupts are disabled**. +/// +/// It is safe to use all other logging functions while this function runs +/// (including all logging macros). +/// +/// [`set_max_level`]: fn.set_max_level.html +#[inline] +pub unsafe fn set_max_level_racy(level: LevelFilter) { + // `MAX_LOG_LEVEL_FILTER` uses a `Cell` as the underlying primitive when a + // platform doesn't support `target_has_atomic = "ptr"`, so even though this looks the same + // as `set_max_level` it may have different safety properties. + MAX_LOG_LEVEL_FILTER.store(level as usize, Ordering::Relaxed); } /// Returns the current maximum log level. @@ -1198,7 +1411,7 @@ pub fn max_level() -> LevelFilter { /// An error is returned if a logger has already been set. /// /// [`set_logger`]: fn.set_logger.html -#[cfg(all(feature = "std", atomic_cas))] +#[cfg(all(feature = "std", target_has_atomic = "ptr"))] pub fn set_boxed_logger(logger: Box) -> Result<(), SetLoggerError> { set_logger_inner(|| Box::leak(logger)) } @@ -1225,7 +1438,7 @@ pub fn set_boxed_logger(logger: Box) -> Result<(), SetLoggerError> { /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::{error, info, warn, Record, Level, Metadata, LevelFilter}; /// /// static MY_LOGGER: MyLogger = MyLogger; @@ -1256,27 +1469,32 @@ pub fn set_boxed_logger(logger: Box) -> Result<(), SetLoggerError> { /// ``` /// /// [`set_logger_racy`]: fn.set_logger_racy.html -#[cfg(atomic_cas)] +#[cfg(target_has_atomic = "ptr")] pub fn set_logger(logger: &'static dyn Log) -> Result<(), SetLoggerError> { set_logger_inner(|| logger) } -#[cfg(atomic_cas)] +#[cfg(target_has_atomic = "ptr")] fn set_logger_inner(make_logger: F) -> Result<(), SetLoggerError> where F: FnOnce() -> &'static dyn Log, { - match STATE.compare_and_swap(UNINITIALIZED, INITIALIZING, Ordering::SeqCst) { - UNINITIALIZED => { + match STATE.compare_exchange( + UNINITIALIZED, + INITIALIZING, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(UNINITIALIZED) => { unsafe { LOGGER = make_logger(); } - STATE.store(INITIALIZED, Ordering::SeqCst); + STATE.store(INITIALIZED, Ordering::Release); Ok(()) } - INITIALIZING => { - while STATE.load(Ordering::SeqCst) == INITIALIZING { - std::sync::atomic::spin_loop_hint(); + Err(INITIALIZING) => { + while STATE.load(Ordering::Relaxed) == INITIALIZING { + std::hint::spin_loop(); } Err(SetLoggerError(())) } @@ -1293,8 +1511,8 @@ where /// /// # Safety /// -/// This function is only safe to call when no other logger initialization -/// function is called while this function still executes. +/// This function is only safe to call when it cannot race with any other +/// calls to `set_logger` or `set_logger_racy`. /// /// This can be upheld by (for example) making sure that **there are no other /// threads**, and (on embedded) that **interrupts are disabled**. @@ -1304,10 +1522,10 @@ where /// /// [`set_logger`]: fn.set_logger.html pub unsafe fn set_logger_racy(logger: &'static dyn Log) -> Result<(), SetLoggerError> { - match STATE.load(Ordering::SeqCst) { + match STATE.load(Ordering::Acquire) { UNINITIALIZED => { LOGGER = logger; - STATE.store(INITIALIZED, Ordering::SeqCst); + STATE.store(INITIALIZED, Ordering::Release); Ok(()) } INITIALIZING => { @@ -1339,7 +1557,7 @@ impl error::Error for SetLoggerError {} /// /// [`from_str`]: https://doc.rust-lang.org/std/str/trait.FromStr.html#tymethod.from_str #[allow(missing_copy_implementations)] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub struct ParseLevelError(()); impl fmt::Display for ParseLevelError { @@ -1356,7 +1574,15 @@ impl error::Error for ParseLevelError {} /// /// If a logger has not been set, a no-op implementation is returned. pub fn logger() -> &'static dyn Log { - if STATE.load(Ordering::SeqCst) != INITIALIZED { + // Acquire memory ordering guarantees that current thread would see any + // memory writes that happened before store of the value + // into `STATE` with memory ordering `Release` or stronger. + // + // Since the value `INITIALIZED` is written only after `LOGGER` was + // initialized, observing it after `Acquire` load here makes both + // write to the `LOGGER` static and initialization of the logger + // internal state synchronized with current thread. + if STATE.load(Ordering::Acquire) != INITIALIZED { static NOP: NopLogger = NopLogger; &NOP } else { @@ -1366,47 +1592,7 @@ pub fn logger() -> &'static dyn Log { // WARNING: this is not part of the crate's public API and is subject to change at any time #[doc(hidden)] -pub fn __private_api_log( - args: fmt::Arguments, - level: Level, - &(target, module_path, file, line): &(&str, &'static str, &'static str, u32), -) { - logger().log( - &Record::builder() - .args(args) - .level(level) - .target(target) - .module_path_static(Some(module_path)) - .file_static(Some(file)) - .line(Some(line)) - .build(), - ); -} - -// WARNING: this is not part of the crate's public API and is subject to change at any time -#[doc(hidden)] -pub fn __private_api_log_lit( - message: &str, - level: Level, - &(target, module_path, file, line): &(&str, &'static str, &'static str, u32), -) { - logger().log( - &Record::builder() - .args(format_args!("{}", message)) - .level(level) - .target(target) - .module_path_static(Some(module_path)) - .file_static(Some(file)) - .line(Some(line)) - .build(), - ); -} - -// WARNING: this is not part of the crate's public API and is subject to change at any time -#[doc(hidden)] -pub fn __private_api_enabled(level: Level, target: &str) -> bool { - logger().enabled(&Metadata::builder().level(level).target(target).build()) -} +pub mod __private_api; /// The statically resolved maximum log level. /// @@ -1417,41 +1603,24 @@ pub fn __private_api_enabled(level: Level, target: &str) -> bool { /// should compare the level against this value. /// /// [`logger`]: fn.logger.html -pub const STATIC_MAX_LEVEL: LevelFilter = MAX_LEVEL_INNER; - -cfg_if! { - if #[cfg(all(not(debug_assertions), feature = "release_max_level_off"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Off; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_error"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Error; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_warn"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Warn; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_info"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Info; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_debug"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Debug; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_trace"))] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Trace; - } else if #[cfg(feature = "max_level_off")] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Off; - } else if #[cfg(feature = "max_level_error")] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Error; - } else if #[cfg(feature = "max_level_warn")] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Warn; - } else if #[cfg(feature = "max_level_info")] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Info; - } else if #[cfg(feature = "max_level_debug")] { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Debug; - } else { - const MAX_LEVEL_INNER: LevelFilter = LevelFilter::Trace; - } -} +pub const STATIC_MAX_LEVEL: LevelFilter = match cfg!(debug_assertions) { + false if cfg!(feature = "release_max_level_off") => LevelFilter::Off, + false if cfg!(feature = "release_max_level_error") => LevelFilter::Error, + false if cfg!(feature = "release_max_level_warn") => LevelFilter::Warn, + false if cfg!(feature = "release_max_level_info") => LevelFilter::Info, + false if cfg!(feature = "release_max_level_debug") => LevelFilter::Debug, + false if cfg!(feature = "release_max_level_trace") => LevelFilter::Trace, + _ if cfg!(feature = "max_level_off") => LevelFilter::Off, + _ if cfg!(feature = "max_level_error") => LevelFilter::Error, + _ if cfg!(feature = "max_level_warn") => LevelFilter::Warn, + _ if cfg!(feature = "max_level_info") => LevelFilter::Info, + _ if cfg!(feature = "max_level_debug") => LevelFilter::Debug, + _ => LevelFilter::Trace, +}; #[cfg(test)] mod tests { - extern crate std; - use super::{Level, LevelFilter, ParseLevelError}; - use tests::std::string::ToString; + use super::{Level, LevelFilter, ParseLevelError, STATIC_MAX_LEVEL}; #[test] fn test_levelfilter_from_str() { @@ -1496,6 +1665,20 @@ mod tests { } } + #[test] + fn test_level_as_str() { + let tests = &[ + (Level::Error, "ERROR"), + (Level::Warn, "WARN"), + (Level::Info, "INFO"), + (Level::Debug, "DEBUG"), + (Level::Trace, "TRACE"), + ]; + for (input, expected) in tests { + assert_eq!(*expected, input.as_str()); + } + } + #[test] fn test_level_show() { assert_eq!("INFO", Level::Info.to_string()); @@ -1535,6 +1718,118 @@ mod tests { assert_eq!(LevelFilter::Trace, Level::Trace.to_level_filter()); } + #[test] + fn test_level_filter_as_str() { + let tests = &[ + (LevelFilter::Off, "OFF"), + (LevelFilter::Error, "ERROR"), + (LevelFilter::Warn, "WARN"), + (LevelFilter::Info, "INFO"), + (LevelFilter::Debug, "DEBUG"), + (LevelFilter::Trace, "TRACE"), + ]; + for (input, expected) in tests { + assert_eq!(*expected, input.as_str()); + } + } + + #[test] + fn test_level_up() { + let info = Level::Info; + let up = info.increment_severity(); + assert_eq!(up, Level::Debug); + + let trace = Level::Trace; + let up = trace.increment_severity(); + // trace is already highest level + assert_eq!(up, trace); + } + + #[test] + fn test_level_filter_up() { + let info = LevelFilter::Info; + let up = info.increment_severity(); + assert_eq!(up, LevelFilter::Debug); + + let trace = LevelFilter::Trace; + let up = trace.increment_severity(); + // trace is already highest level + assert_eq!(up, trace); + } + + #[test] + fn test_level_down() { + let info = Level::Info; + let down = info.decrement_severity(); + assert_eq!(down, Level::Warn); + + let error = Level::Error; + let down = error.decrement_severity(); + // error is already lowest level + assert_eq!(down, error); + } + + #[test] + fn test_level_filter_down() { + let info = LevelFilter::Info; + let down = info.decrement_severity(); + assert_eq!(down, LevelFilter::Warn); + + let error = LevelFilter::Error; + let down = error.decrement_severity(); + assert_eq!(down, LevelFilter::Off); + // Off is already the lowest + assert_eq!(down.decrement_severity(), down); + } + + #[test] + #[cfg_attr(not(debug_assertions), ignore)] + fn test_static_max_level_debug() { + if cfg!(feature = "max_level_off") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Off); + } else if cfg!(feature = "max_level_error") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Error); + } else if cfg!(feature = "max_level_warn") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Warn); + } else if cfg!(feature = "max_level_info") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Info); + } else if cfg!(feature = "max_level_debug") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Debug); + } else { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Trace); + } + } + + #[test] + #[cfg_attr(debug_assertions, ignore)] + fn test_static_max_level_release() { + if cfg!(feature = "release_max_level_off") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Off); + } else if cfg!(feature = "release_max_level_error") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Error); + } else if cfg!(feature = "release_max_level_warn") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Warn); + } else if cfg!(feature = "release_max_level_info") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Info); + } else if cfg!(feature = "release_max_level_debug") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Debug); + } else if cfg!(feature = "release_max_level_trace") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Trace); + } else if cfg!(feature = "max_level_off") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Off); + } else if cfg!(feature = "max_level_error") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Error); + } else if cfg!(feature = "max_level_warn") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Warn); + } else if cfg!(feature = "max_level_info") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Info); + } else if cfg!(feature = "max_level_debug") { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Debug); + } else { + assert_eq!(STATIC_MAX_LEVEL, LevelFilter::Trace); + } + } + #[test] #[cfg(feature = "std")] fn test_error_trait() { @@ -1628,16 +1923,16 @@ mod tests { } #[test] - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] fn test_record_key_values_builder() { use super::Record; - use kv::{self, Visitor}; + use crate::kv::{self, VisitSource}; - struct TestVisitor { + struct TestVisitSource { seen_pairs: usize, } - impl<'kvs> Visitor<'kvs> for TestVisitor { + impl<'kvs> VisitSource<'kvs> for TestVisitSource { fn visit_pair( &mut self, _: kv::Key<'kvs>, @@ -1651,7 +1946,7 @@ mod tests { let kvs: &[(&str, i32)] = &[("a", 1), ("b", 2)]; let record_test = Record::builder().key_values(&kvs).build(); - let mut visitor = TestVisitor { seen_pairs: 0 }; + let mut visitor = TestVisitSource { seen_pairs: 0 }; record_test.key_values().visit(&mut visitor).unwrap(); @@ -1659,7 +1954,7 @@ mod tests { } #[test] - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] fn test_record_key_values_get_coerce() { use super::Record; @@ -1676,4 +1971,35 @@ mod tests { .expect("invalid value") ); } + + // Test that the `impl Log for Foo` blocks work + // This test mostly operates on a type level, so failures will be compile errors + #[test] + fn test_foreign_impl() { + use super::Log; + #[cfg(feature = "std")] + use std::sync::Arc; + + fn assert_is_log() {} + + assert_is_log::<&dyn Log>(); + + #[cfg(feature = "std")] + assert_is_log::>(); + + #[cfg(feature = "std")] + assert_is_log::>(); + + // Assert these statements for all T: Log + ?Sized + #[allow(unused)] + fn forall() { + #[cfg(feature = "std")] + assert_is_log::>(); + + assert_is_log::<&T>(); + + #[cfg(feature = "std")] + assert_is_log::>(); + } + } } diff --git a/src/macros.rs b/src/macros.rs index ae6080d9b..14e4ac64b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -13,154 +13,313 @@ /// This macro will generically log with the specified `Level` and `format!` /// based argument list. /// -/// # Examples -/// -/// ```edition2018 +/// ``` /// use log::{log, Level}; /// -/// # fn main() { /// let data = (42, "Forty-two"); /// let private_data = "private"; /// /// log!(Level::Error, "Received errors: {}, {}", data.0, data.1); -/// log!(target: "app_events", Level::Warn, "App warning: {}, {}, {}", -/// data.0, data.1, private_data); +/// ``` +/// +/// Optionally, you can specify a `target` argument to attach a specific target +/// to the log record. By default, the target is the module path of the caller. +/// +/// ``` +/// use log::{log, Level}; +/// +/// let data = (42, "Forty-two"); +/// let private_data = "private"; +/// +/// log!( +/// target: "app_events", +/// Level::Error, +/// "Received errors: {}, {}", +/// data.0, data.1 +/// ); +/// ``` +/// +/// And optionally, you can specify a `logger` argument to use a specific logger +/// instead of the default global logger. +/// +/// ``` +/// # struct MyLogger {} +/// # impl Log for MyLogger { +/// # fn enabled(&self, _metadata: &log::Metadata) -> bool { +/// # false +/// # } +/// # fn log(&self, _record: &log::Record) {} +/// # fn flush(&self) {} /// # } +/// use log::{log, Level, Log}; +/// +/// let data = (42, "Forty-two"); +/// let private_data = "private"; +/// +/// let my_logger = MyLogger {}; +/// log!( +/// logger: my_logger, +/// Level::Error, +/// "Received errors: {}, {}", +/// data.0, data.1 +/// ); /// ``` -#[macro_export(local_inner_macros)] +/// +/// The `logger` argument accepts a value that implements the `Log` trait. The value +/// will be borrowed within the macro. +/// +/// Note that the global level set via Cargo features, or through `set_max_level` will +/// still apply, even when a custom logger is supplied with the `logger` argument. +#[macro_export] +#[clippy::format_args] macro_rules! log { - (target: $target:expr, $lvl:expr, $message:expr) => ({ + // log!(logger: my_logger, target: "my_target", Level::Info, "a {} event", "log"); + (logger: $logger:expr, target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ + $crate::__log!( + logger: $crate::__log_logger!($logger), + target: $target, + $lvl, + $($arg)+ + ) + }); + + // log!(logger: my_logger, Level::Info, "a log event") + (logger: $logger:expr, $lvl:expr, $($arg:tt)+) => ({ + $crate::__log!( + logger: $crate::__log_logger!($logger), + target: $crate::__private_api::module_path!(), + $lvl, + $($arg)+ + ) + }); + + // log!(target: "my_target", Level::Info, "a log event") + (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ + $crate::__log!( + logger: $crate::__log_logger!(__log_global_logger), + target: $target, + $lvl, + $($arg)+ + ) + }); + + // log!(Level::Info, "a log event") + ($lvl:expr, $($arg:tt)+) => ({ + $crate::__log!( + logger: $crate::__log_logger!(__log_global_logger), + target: $crate::__private_api::module_path!(), + $lvl, + $($arg)+ + ) + }); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __log { + // log!(logger: my_logger, target: "my_target", Level::Info, key1:? = 42, key2 = true; "a {} event", "log"); + (logger: $logger:expr, target: $target:expr, $lvl:expr, $($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({ let lvl = $lvl; if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { - // ensure that $message is a valid format string literal - let _ = __log_format_args!($message); - $crate::__private_api_log_lit( - $message, + $crate::__private_api::log( + $logger, + $crate::__private_api::format_args!($($arg)+), lvl, - &($target, __log_module_path!(), __log_file!(), __log_line!()), + &($target, $crate::__private_api::module_path!(), $crate::__private_api::loc()), + &[$(($crate::__log_key!($key), $crate::__log_value!($key $(:$capture)* = $($value)*))),+] as &[_], ); } }); - (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ + + // log!(logger: my_logger, target: "my_target", Level::Info, "a {} event", "log"); + (logger: $logger:expr, target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ let lvl = $lvl; if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { - $crate::__private_api_log( - __log_format_args!($($arg)+), + $crate::__private_api::log( + $logger, + $crate::__private_api::format_args!($($arg)+), lvl, - &($target, __log_module_path!(), __log_file!(), __log_line!()), + &($target, $crate::__private_api::module_path!(), $crate::__private_api::loc()), + (), ); } }); - ($lvl:expr, $($arg:tt)+) => (log!(target: __log_module_path!(), $lvl, $($arg)+)) } /// Logs a message at the error level. /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::error; /// -/// # fn main() { +/// # let my_logger = log::__private_api::GlobalLogger; /// let (err_info, port) = ("No connection", 22); /// -/// error!("Error: {} on port {}", err_info, port); -/// error!(target: "app_events", "App Error: {}, Port: {}", err_info, 22); -/// # } +/// error!("Error: {err_info} on port {port}"); +/// error!(target: "app_events", "App Error: {err_info}, Port: {port}"); +/// error!(logger: my_logger, "App Error: {err_info}, Port: {port}"); /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] +#[clippy::format_args] macro_rules! error { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Error, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Error, $($arg)+); - ) + // error!(logger: my_logger, target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // error!(logger: my_logger, target: "my_target", "a {} event", "log") + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), target: $target, $crate::Level::Error, $($arg)+) + }); + + // error!(logger: my_logger, key1 = 42, key2 = true; "a {} event", "log") + // error!(logger: my_logger, "a {} event", "log") + (logger: $logger:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), $crate::Level::Error, $($arg)+) + }); + + // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // error!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(target: $target, $crate::Level::Error, $($arg)+) + }); + + // error!("a {} event", "log") + ($($arg:tt)+) => ($crate::log!($crate::Level::Error, $($arg)+)) } /// Logs a message at the warn level. /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::warn; /// -/// # fn main() { +/// # let my_logger = log::__private_api::GlobalLogger; /// let warn_description = "Invalid Input"; /// -/// warn!("Warning! {}!", warn_description); -/// warn!(target: "input_events", "App received warning: {}", warn_description); -/// # } +/// warn!("Warning! {warn_description}!"); +/// warn!(target: "input_events", "App received warning: {warn_description}"); +/// warn!(logger: my_logger, "App received warning: {warn_description}"); /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] +#[clippy::format_args] macro_rules! warn { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Warn, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Warn, $($arg)+); - ) + // warn!(logger: my_logger, target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // warn!(logger: my_logger, target: "my_target", "a {} event", "log") + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), target: $target, $crate::Level::Warn, $($arg)+) + }); + + // warn!(logger: my_logger, key1 = 42, key2 = true; "a {} event", "log") + // warn!(logger: my_logger, "a {} event", "log") + (logger: $logger:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), $crate::Level::Warn, $($arg)+) + }); + + // warn!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // warn!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(target: $target, $crate::Level::Warn, $($arg)+) + }); + + // warn!("a {} event", "log") + ($($arg:tt)+) => ($crate::log!($crate::Level::Warn, $($arg)+)) } /// Logs a message at the info level. /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::info; /// -/// # fn main() { +/// # let my_logger = log::__private_api::GlobalLogger; /// # struct Connection { port: u32, speed: f32 } /// let conn_info = Connection { port: 40, speed: 3.20 }; /// /// info!("Connected to port {} at {} Mb/s", conn_info.port, conn_info.speed); -/// info!(target: "connection_events", "Successfull connection, port: {}, speed: {}", -/// conn_info.port, conn_info.speed); -/// # } +/// info!( +/// target: "connection_events", +/// "Successful connection, port: {}, speed: {}", +/// conn_info.port, conn_info.speed +/// ); +/// info!( +/// logger: my_logger, +/// "Successful connection, port: {}, speed: {}", +/// conn_info.port, conn_info.speed +/// ); /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] +#[clippy::format_args] macro_rules! info { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Info, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Info, $($arg)+); - ) + // info!(logger: my_logger, target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // info!(logger: my_logger, target: "my_target", "a {} event", "log") + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), target: $target, $crate::Level::Info, $($arg)+) + }); + + // info!(logger: my_logger, key1 = 42, key2 = true; "a {} event", "log") + // info!(logger: my_logger, "a {} event", "log") + (logger: $logger:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), $crate::Level::Info, $($arg)+) + }); + + // info!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // info!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(target: $target, $crate::Level::Info, $($arg)+) + }); + + // info!("a {} event", "log") + ($($arg:tt)+) => ($crate::log!($crate::Level::Info, $($arg)+)) } /// Logs a message at the debug level. /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::debug; /// -/// # fn main() { +/// # let my_logger = log::__private_api::GlobalLogger; /// # struct Position { x: f32, y: f32 } /// let pos = Position { x: 3.234, y: -1.223 }; /// /// debug!("New position: x: {}, y: {}", pos.x, pos.y); /// debug!(target: "app_events", "New position: x: {}, y: {}", pos.x, pos.y); -/// # } +/// debug!(logger: my_logger, "New position: x: {}, y: {}", pos.x, pos.y); /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] +#[clippy::format_args] macro_rules! debug { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Debug, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Debug, $($arg)+); - ) + // debug!(logger: my_logger, target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // debug!(logger: my_logger, target: "my_target", "a {} event", "log") + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), target: $target, $crate::Level::Debug, $($arg)+) + }); + + // debug!(logger: my_logger, key1 = 42, key2 = true; "a {} event", "log") + // debug!(logger: my_logger, "a {} event", "log") + (logger: $logger:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), $crate::Level::Debug, $($arg)+) + }); + + // debug!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // debug!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(target: $target, $crate::Level::Debug, $($arg)+) + }); + + // debug!("a {} event", "log") + ($($arg:tt)+) => ($crate::log!($crate::Level::Debug, $($arg)+)) } /// Logs a message at the trace level. /// /// # Examples /// -/// ```edition2018 +/// ``` /// use log::trace; /// -/// # fn main() { +/// # let my_logger = log::__private_api::GlobalLogger; /// # struct Position { x: f32, y: f32 } /// let pos = Position { x: 3.234, y: -1.223 }; /// @@ -168,16 +327,33 @@ macro_rules! debug { /// trace!(target: "app_events", "x is {} and y is {}", /// if pos.x >= 0.0 { "positive" } else { "negative" }, /// if pos.y >= 0.0 { "positive" } else { "negative" }); -/// # } +/// trace!(logger: my_logger, "x is {} and y is {}", +/// if pos.x >= 0.0 { "positive" } else { "negative" }, +/// if pos.y >= 0.0 { "positive" } else { "negative" }); /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] +#[clippy::format_args] macro_rules! trace { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Trace, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Trace, $($arg)+); - ) + // trace!(logger: my_logger, target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // trace!(logger: my_logger, target: "my_target", "a {} event", "log") + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), target: $target, $crate::Level::Trace, $($arg)+) + }); + + // trace!(logger: my_logger, key1 = 42, key2 = true; "a {} event", "log") + // trace!(logger: my_logger, "a {} event", "log") + (logger: $logger:expr, $($arg:tt)+) => ({ + $crate::log!(logger: $crate::__log_logger!($logger), $crate::Level::Trace, $($arg)+) + }); + + // trace!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") + // trace!(target: "my_target", "a {} event", "log") + (target: $target:expr, $($arg:tt)+) => ({ + $crate::log!(target: $target, $crate::Level::Trace, $($arg)+) + }); + + // trace!("a {} event", "log") + ($($arg:tt)+) => ($crate::log!($crate::Level::Trace, $($arg)+)) } /// Determines if a message logged at the specified level in that module will @@ -188,75 +364,216 @@ macro_rules! trace { /// /// # Examples /// -/// ```edition2018 -/// use log::Level::Debug; -/// use log::{debug, log_enabled}; +/// ``` +/// use log::{debug, log_enabled, Level}; /// -/// # fn foo() { -/// if log_enabled!(Debug) { +/// # struct Data { x: u32, y: u32 } +/// # fn expensive_call() -> Data { Data { x: 0, y: 0 } } +/// # let my_logger = log::__private_api::GlobalLogger; +/// if log_enabled!(Level::Debug) { /// let data = expensive_call(); /// debug!("expensive debug data: {} {}", data.x, data.y); /// } -/// if log_enabled!(target: "Global", Debug) { +/// +/// if log_enabled!(target: "Global", Level::Debug) { +/// let data = expensive_call(); +/// debug!(target: "Global", "expensive debug data: {} {}", data.x, data.y); +/// } +/// +/// if log_enabled!(logger: my_logger, Level::Debug) { /// let data = expensive_call(); /// debug!(target: "Global", "expensive debug data: {} {}", data.x, data.y); /// } -/// # } -/// # struct Data { x: u32, y: u32 } -/// # fn expensive_call() -> Data { Data { x: 0, y: 0 } } -/// # fn main() {} /// ``` -#[macro_export(local_inner_macros)] +/// +/// This macro accepts the same `target` and `logger` arguments as [`macro@log`]. +#[macro_export] macro_rules! log_enabled { - (target: $target:expr, $lvl:expr) => {{ + // log_enabled!(logger: my_logger, target: "my_target", Level::Info) + (logger: $logger:expr, target: $target:expr, $lvl:expr) => ({ + $crate::__log_enabled!(logger: $crate::__log_logger!($logger), target: $target, $lvl) + }); + + // log_enabled!(logger: my_logger, Level::Info) + (logger: $logger:expr, $lvl:expr) => ({ + $crate::__log_enabled!(logger: $crate::__log_logger!($logger), target: $crate::__private_api::module_path!(), $lvl) + }); + + // log_enabled!(target: "my_target", Level::Info) + (target: $target:expr, $lvl:expr) => ({ + $crate::__log_enabled!(logger: $crate::__log_logger!(__log_global_logger), target: $target, $lvl) + }); + + // log_enabled!(Level::Info) + ($lvl:expr) => ({ + $crate::__log_enabled!(logger: $crate::__log_logger!(__log_global_logger), target: $crate::__private_api::module_path!(), $lvl) + }); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __log_enabled { + // log_enabled!(logger: my_logger, target: "my_target", Level::Info) + (logger: $logger:expr, target: $target:expr, $lvl:expr) => {{ let lvl = $lvl; lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() - && $crate::__private_api_enabled(lvl, $target) + && $crate::__private_api::enabled($logger, lvl, $target) + }}; +} + +// Determine the logger to use, and whether to take it by-value or by reference + +#[doc(hidden)] +#[macro_export] +macro_rules! __log_logger { + (__log_global_logger) => {{ + $crate::__private_api::GlobalLogger + }}; + + ($logger:expr) => {{ + &($logger) }}; - ($lvl:expr) => { - log_enabled!(target: __log_module_path!(), $lvl) +} + +// These macros use a pattern of #[cfg]s to produce nicer error +// messages when log features aren't available + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv")] +macro_rules! __log_key { + // key1 = 42 + ($($args:ident)*) => { + $crate::__private_api::stringify!($($args)*) + }; + // "key1" = 42 + ($($args:expr)*) => { + $($args)* + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv"))] +macro_rules! __log_key { + ($($args:tt)*) => { + compile_error!("key value support requires the `kv` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv")] +macro_rules! __log_value { + // Entrypoint + ($key:tt = $args:expr) => { + $crate::__log_value!(($args):value) + }; + ($key:tt :$capture:tt = $args:expr) => { + $crate::__log_value!(($args):$capture) + }; + ($key:ident =) => { + $crate::__log_value!(($key):value) + }; + ($key:ident :$capture:tt =) => { + $crate::__log_value!(($key):$capture) + }; + // ToValue + (($args:expr):value) => { + $crate::__private_api::capture_to_value(&&$args) + }; + // Debug + (($args:expr):?) => { + $crate::__private_api::capture_debug(&&$args) + }; + (($args:expr):debug) => { + $crate::__private_api::capture_debug(&&$args) + }; + // Display + (($args:expr):%) => { + $crate::__private_api::capture_display(&&$args) + }; + (($args:expr):display) => { + $crate::__private_api::capture_display(&&$args) + }; + //Error + (($args:expr):err) => { + $crate::__log_value_error!($args) + }; + // sval::Value + (($args:expr):sval) => { + $crate::__log_value_sval!($args) + }; + // serde::Serialize + (($args:expr):serde) => { + $crate::__log_value_serde!($args) }; } -// The log macro above cannot invoke format_args directly because it uses -// local_inner_macros. A format_args invocation there would resolve to -// $crate::format_args which does not exist. Instead invoke format_args here -// outside of local_inner_macros so that it resolves (probably) to -// core::format_args or std::format_args. Same for the several macros that -// follow. -// -// This is a workaround until we drop support for pre-1.30 compilers. At that -// point we can remove use of local_inner_macros, use $crate:: when invoking -// local macros, and invoke format_args directly. #[doc(hidden)] #[macro_export] -macro_rules! __log_format_args { +#[cfg(not(feature = "kv"))] +macro_rules! __log_value { ($($args:tt)*) => { - format_args!($($args)*) + compile_error!("key value support requires the `kv` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv_sval")] +macro_rules! __log_value_sval { + ($args:expr) => { + $crate::__private_api::capture_sval(&&$args) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv_sval"))] +macro_rules! __log_value_sval { + ($args:expr) => { + compile_error!("capturing values as `sval::Value` requites the `kv_sval` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv_serde")] +macro_rules! __log_value_serde { + ($args:expr) => { + $crate::__private_api::capture_serde(&&$args) }; } #[doc(hidden)] #[macro_export] -macro_rules! __log_module_path { - () => { - module_path!() +#[cfg(not(feature = "kv_serde"))] +macro_rules! __log_value_serde { + ($args:expr) => { + compile_error!( + "capturing values as `serde::Serialize` requites the `kv_serde` feature of `log`" + ) }; } #[doc(hidden)] #[macro_export] -macro_rules! __log_file { - () => { - file!() +#[cfg(feature = "kv_std")] +macro_rules! __log_value_error { + ($args:expr) => { + $crate::__private_api::capture_error(&$args) }; } #[doc(hidden)] #[macro_export] -macro_rules! __log_line { - () => { - line!() +#[cfg(not(feature = "kv_std"))] +macro_rules! __log_value_error { + ($args:expr) => { + compile_error!( + "capturing values as `std::error::Error` requites the `kv_std` feature of `log`" + ) }; } diff --git a/src/serde.rs b/src/serde.rs index efc9f14a4..db732395b 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,18 +1,17 @@ #![cfg(feature = "serde")] -extern crate serde; -use self::serde::de::{ +use serde::de::{ Deserialize, DeserializeSeed, Deserializer, EnumAccess, Error, Unexpected, VariantAccess, Visitor, }; -use self::serde::ser::{Serialize, Serializer}; +use serde::ser::{Serialize, Serializer}; -use {Level, LevelFilter, LOG_LEVEL_NAMES}; +use crate::{Level, LevelFilter, LOG_LEVEL_NAMES}; use std::fmt; use std::str::{self, FromStr}; -// The Deserialize impls are handwritten to be case insensitive using FromStr. +// The Deserialize impls are handwritten to be case-insensitive using FromStr. impl Serialize for Level { fn serialize(&self, serializer: S) -> Result @@ -43,11 +42,22 @@ impl<'de> Deserialize<'de> for Level { formatter.write_str("log level") } + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + let variant = LOG_LEVEL_NAMES[1..] + .get(v as usize) + .ok_or_else(|| Error::invalid_value(Unexpected::Unsigned(v), &self))?; + + self.visit_str(variant) + } + fn visit_str(self, s: &str) -> Result where E: Error, { - // Case insensitive. + // Case-insensitive. FromStr::from_str(s).map_err(|_| Error::unknown_variant(s, &LOG_LEVEL_NAMES[1..])) } @@ -127,11 +137,22 @@ impl<'de> Deserialize<'de> for LevelFilter { formatter.write_str("log level filter") } + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + let variant = LOG_LEVEL_NAMES + .get(v as usize) + .ok_or_else(|| Error::invalid_value(Unexpected::Unsigned(v), &self))?; + + self.visit_str(variant) + } + fn visit_str(self, s: &str) -> Result where E: Error, { - // Case insensitive. + // Case-insensitive. FromStr::from_str(s).map_err(|_| Error::unknown_variant(s, &LOG_LEVEL_NAMES)) } @@ -183,15 +204,13 @@ impl<'de> Deserialize<'de> for LevelFilter { #[cfg(test)] mod tests { - extern crate serde_test; - use self::serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; - - use {Level, LevelFilter}; + use crate::{Level, LevelFilter}; + use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; fn level_token(variant: &'static str) -> Token { Token::UnitVariant { name: "Level", - variant: variant, + variant, } } @@ -203,10 +222,18 @@ mod tests { ] } + fn level_variant_tokens(variant: u32) -> [Token; 3] { + [ + Token::Enum { name: "Level" }, + Token::U32(variant), + Token::Unit, + ] + } + fn level_filter_token(variant: &'static str) -> Token { Token::UnitVariant { name: "LevelFilter", - variant: variant, + variant, } } @@ -220,9 +247,19 @@ mod tests { ] } + fn level_filter_variant_tokens(variant: u32) -> [Token; 3] { + [ + Token::Enum { + name: "LevelFilter", + }, + Token::U32(variant), + Token::Unit, + ] + } + #[test] fn test_level_ser_de() { - let cases = [ + let cases = &[ (Level::Error, [level_token("ERROR")]), (Level::Warn, [level_token("WARN")]), (Level::Info, [level_token("INFO")]), @@ -230,14 +267,14 @@ mod tests { (Level::Trace, [level_token("TRACE")]), ]; - for &(s, expected) in &cases { - assert_tokens(&s, &expected); + for (s, expected) in cases { + assert_tokens(s, expected); } } #[test] fn test_level_case_insensitive() { - let cases = [ + let cases = &[ (Level::Error, [level_token("error")]), (Level::Warn, [level_token("warn")]), (Level::Info, [level_token("info")]), @@ -245,14 +282,14 @@ mod tests { (Level::Trace, [level_token("trace")]), ]; - for &(s, expected) in &cases { - assert_de_tokens(&s, &expected); + for (s, expected) in cases { + assert_de_tokens(s, expected); } } #[test] fn test_level_de_bytes() { - let cases = [ + let cases = &[ (Level::Error, level_bytes_tokens(b"ERROR")), (Level::Warn, level_bytes_tokens(b"WARN")), (Level::Info, level_bytes_tokens(b"INFO")), @@ -260,8 +297,23 @@ mod tests { (Level::Trace, level_bytes_tokens(b"TRACE")), ]; - for &(value, tokens) in &cases { - assert_de_tokens(&value, &tokens); + for (value, tokens) in cases { + assert_de_tokens(value, tokens); + } + } + + #[test] + fn test_level_de_variant_index() { + let cases = &[ + (Level::Error, level_variant_tokens(0)), + (Level::Warn, level_variant_tokens(1)), + (Level::Info, level_variant_tokens(2)), + (Level::Debug, level_variant_tokens(3)), + (Level::Trace, level_variant_tokens(4)), + ]; + + for (value, tokens) in cases { + assert_de_tokens(value, tokens); } } @@ -274,7 +326,7 @@ mod tests { #[test] fn test_level_filter_ser_de() { - let cases = [ + let cases = &[ (LevelFilter::Off, [level_filter_token("OFF")]), (LevelFilter::Error, [level_filter_token("ERROR")]), (LevelFilter::Warn, [level_filter_token("WARN")]), @@ -283,14 +335,14 @@ mod tests { (LevelFilter::Trace, [level_filter_token("TRACE")]), ]; - for &(s, expected) in &cases { - assert_tokens(&s, &expected); + for (s, expected) in cases { + assert_tokens(s, expected); } } #[test] fn test_level_filter_case_insensitive() { - let cases = [ + let cases = &[ (LevelFilter::Off, [level_filter_token("off")]), (LevelFilter::Error, [level_filter_token("error")]), (LevelFilter::Warn, [level_filter_token("warn")]), @@ -299,14 +351,14 @@ mod tests { (LevelFilter::Trace, [level_filter_token("trace")]), ]; - for &(s, expected) in &cases { - assert_de_tokens(&s, &expected); + for (s, expected) in cases { + assert_de_tokens(s, expected); } } #[test] fn test_level_filter_de_bytes() { - let cases = [ + let cases = &[ (LevelFilter::Off, level_filter_bytes_tokens(b"OFF")), (LevelFilter::Error, level_filter_bytes_tokens(b"ERROR")), (LevelFilter::Warn, level_filter_bytes_tokens(b"WARN")), @@ -315,8 +367,24 @@ mod tests { (LevelFilter::Trace, level_filter_bytes_tokens(b"TRACE")), ]; - for &(value, tokens) in &cases { - assert_de_tokens(&value, &tokens); + for (value, tokens) in cases { + assert_de_tokens(value, tokens); + } + } + + #[test] + fn test_level_filter_de_variant_index() { + let cases = &[ + (LevelFilter::Off, level_filter_variant_tokens(0)), + (LevelFilter::Error, level_filter_variant_tokens(1)), + (LevelFilter::Warn, level_filter_variant_tokens(2)), + (LevelFilter::Info, level_filter_variant_tokens(3)), + (LevelFilter::Debug, level_filter_variant_tokens(4)), + (LevelFilter::Trace, level_filter_variant_tokens(5)), + ]; + + for (value, tokens) in cases { + assert_de_tokens(value, tokens); } } diff --git a/test_max_level_features/Cargo.toml b/test_max_level_features/Cargo.toml index 9d0b9759b..ae23058f9 100644 --- a/test_max_level_features/Cargo.toml +++ b/test_max_level_features/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "optimized" version = "0.1.0" +edition = "2021" publish = false [[bin]] diff --git a/test_max_level_features/main.rs b/test_max_level_features/main.rs index bfbf2b247..b714f2d4c 100644 --- a/test_max_level_features/main.rs +++ b/test_max_level_features/main.rs @@ -1,13 +1,10 @@ -#[macro_use] -extern crate log; - +use log::{debug, error, info, trace, warn, Level, LevelFilter, Log, Metadata, Record}; use std::sync::{Arc, Mutex}; -use log::{Level, LevelFilter, Log, Record, Metadata}; #[cfg(feature = "std")] use log::set_boxed_logger; #[cfg(not(feature = "std"))] -fn set_boxed_logger(logger: Box) -> Result<(), log::SetLoggerError> { +fn set_boxed_logger(logger: Box) -> Result<(), log::SetLoggerError> { log::set_logger(Box::leak(logger)) } @@ -30,7 +27,9 @@ impl Log for Logger { } fn main() { - let me = Arc::new(State { last_log: Mutex::new(None) }); + let me = Arc::new(State { + last_log: Mutex::new(None), + }); let a = me.clone(); set_boxed_logger(Box::new(Logger(me))).unwrap(); @@ -62,7 +61,11 @@ fn test(a: &State, filter: LevelFilter) { last(&a, None); fn t(lvl: Level, filter: LevelFilter) -> Option { - if lvl <= filter {Some(lvl)} else {None} + if lvl <= filter { + Some(lvl) + } else { + None + } } } diff --git a/tests/filters.rs b/tests/filters.rs deleted file mode 100644 index 3c261c469..000000000 --- a/tests/filters.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[macro_use] -extern crate log; - -use log::{Level, LevelFilter, Log, Metadata, Record}; -use std::sync::{Arc, Mutex}; - -#[cfg(feature = "std")] -use log::set_boxed_logger; - -#[cfg(not(feature = "std"))] -fn set_boxed_logger(logger: Box) -> Result<(), log::SetLoggerError> { - log::set_logger(Box::leak(logger)) -} - -struct State { - last_log: Mutex>, -} - -struct Logger(Arc); - -impl Log for Logger { - fn enabled(&self, _: &Metadata) -> bool { - true - } - - fn log(&self, record: &Record) { - *self.0.last_log.lock().unwrap() = Some(record.level()); - } - fn flush(&self) {} -} - -fn main() { - let me = Arc::new(State { - last_log: Mutex::new(None), - }); - let a = me.clone(); - set_boxed_logger(Box::new(Logger(me))).unwrap(); - - test(&a, LevelFilter::Off); - test(&a, LevelFilter::Error); - test(&a, LevelFilter::Warn); - test(&a, LevelFilter::Info); - test(&a, LevelFilter::Debug); - test(&a, LevelFilter::Trace); -} - -fn test(a: &State, filter: LevelFilter) { - log::set_max_level(filter); - error!(""); - last(&a, t(Level::Error, filter)); - warn!(""); - last(&a, t(Level::Warn, filter)); - info!(""); - last(&a, t(Level::Info, filter)); - debug!(""); - last(&a, t(Level::Debug, filter)); - trace!(""); - last(&a, t(Level::Trace, filter)); - - fn t(lvl: Level, filter: LevelFilter) -> Option { - if lvl <= filter { - Some(lvl) - } else { - None - } - } -} - -fn last(state: &State, expected: Option) { - let lvl = state.last_log.lock().unwrap().take(); - assert_eq!(lvl, expected); -} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 000000000..9bcb04697 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,101 @@ +#![allow(dead_code, unused_imports)] + +use log::{debug, error, info, trace, warn, Level, LevelFilter, Log, Metadata, Record}; +use std::sync::{Arc, Mutex}; + +struct State { + last_log_level: Mutex>, + last_log_location: Mutex>, +} + +struct Logger(Arc); + +impl Log for Logger { + fn enabled(&self, _: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + *self.0.last_log_level.lock().unwrap() = Some(record.level()); + *self.0.last_log_location.lock().unwrap() = record.line(); + } + fn flush(&self) {} +} + +#[test] +fn test_integration() { + // These tests don't really make sense when static + // max level filtering is applied + #[cfg(not(any( + feature = "max_level_off", + feature = "max_level_error", + feature = "max_level_warn", + feature = "max_level_info", + feature = "max_level_debug", + feature = "max_level_trace", + feature = "release_max_level_off", + feature = "release_max_level_error", + feature = "release_max_level_warn", + feature = "release_max_level_info", + feature = "release_max_level_debug", + feature = "release_max_level_trace", + )))] + { + let me = Arc::new(State { + last_log_level: Mutex::new(None), + last_log_location: Mutex::new(None), + }); + let a = me.clone(); + let logger = Logger(me); + + test_filter(&logger, &a, LevelFilter::Off); + test_filter(&logger, &a, LevelFilter::Error); + test_filter(&logger, &a, LevelFilter::Warn); + test_filter(&logger, &a, LevelFilter::Info); + test_filter(&logger, &a, LevelFilter::Debug); + test_filter(&logger, &a, LevelFilter::Trace); + + test_line_numbers(&logger, &a); + } +} + +fn test_filter(logger: &dyn Log, a: &State, filter: LevelFilter) { + // tests to ensure logs with a level beneath 'max_level' are filtered out + log::set_max_level(filter); + error!(logger: logger, ""); + last(a, t(Level::Error, filter)); + warn!(logger: logger, ""); + last(a, t(Level::Warn, filter)); + info!(logger: logger, ""); + last(a, t(Level::Info, filter)); + debug!(logger: logger, ""); + last(a, t(Level::Debug, filter)); + trace!(logger: logger, ""); + last(a, t(Level::Trace, filter)); + + fn t(lvl: Level, filter: LevelFilter) -> Option { + if lvl <= filter { + Some(lvl) + } else { + None + } + } + fn last(state: &State, expected: Option) { + let lvl = state.last_log_level.lock().unwrap().take(); + assert_eq!(lvl, expected); + } +} + +fn test_line_numbers(logger: &dyn Log, state: &State) { + log::set_max_level(LevelFilter::Trace); + + info!(logger: logger, ""); // ensure check_line function follows log macro + check_log_location(state); + + #[track_caller] + fn check_log_location(state: &State) { + let location = std::panic::Location::caller().line(); // get function calling location + let line_number = state.last_log_location.lock().unwrap().take().unwrap(); // get location of most recent log + assert_eq!(line_number, location - 1); + } +} diff --git a/tests/macros.rs b/tests/macros.rs index da397c3a5..dded475c1 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,36 +1,429 @@ -#[macro_use] -extern crate log; +use log::{log, log_enabled, Log, Metadata, Record}; + +macro_rules! all_log_macros { + ($($arg:tt)*) => ({ + ::log::trace!($($arg)*); + ::log::debug!($($arg)*); + ::log::info!($($arg)*); + ::log::warn!($($arg)*); + ::log::error!($($arg)*); + }); +} + +// Not `Copy` +struct Logger; + +impl Log for Logger { + fn enabled(&self, _: &Metadata) -> bool { + false + } + fn log(&self, _: &Record) {} + fn flush(&self) {} +} #[test] -fn base() { - info!("hello"); - info!("hello",); +fn no_args() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(lvl, "hello"); + log!(lvl, "hello",); + + log!(target: "my_target", lvl, "hello"); + log!(target: "my_target", lvl, "hello",); + + log!(logger: logger, lvl, "hello"); + log!(logger: logger, lvl, "hello",); + + log!(logger: logger, target: "my_target", lvl, "hello"); + log!(logger: logger, target: "my_target", lvl, "hello",); + } + + all_log_macros!("hello"); + all_log_macros!("hello",); + + all_log_macros!(target: "my_target", "hello"); + all_log_macros!(target: "my_target", "hello",); + + all_log_macros!(logger: logger, "hello"); + all_log_macros!(logger: logger, "hello",); + + all_log_macros!(logger: logger, target: "my_target", "hello"); + all_log_macros!(logger: logger, target: "my_target", "hello",); } #[test] -fn base_expr_context() { - let _ = info!("hello"); +fn anonymous_args() { + for lvl in log::Level::iter() { + log!(lvl, "hello {}", "world"); + log!(lvl, "hello {}", "world",); + + log!(target: "my_target", lvl, "hello {}", "world"); + log!(target: "my_target", lvl, "hello {}", "world",); + + log!(lvl, "hello {}", "world"); + log!(lvl, "hello {}", "world",); + } + + all_log_macros!("hello {}", "world"); + all_log_macros!("hello {}", "world",); + + all_log_macros!(target: "my_target", "hello {}", "world"); + all_log_macros!(target: "my_target", "hello {}", "world",); + + let logger = Logger; + + all_log_macros!(logger: logger, "hello {}", "world"); + all_log_macros!(logger: logger, "hello {}", "world",); + + all_log_macros!(logger: logger, target: "my_target", "hello {}", "world"); + all_log_macros!(logger: logger, target: "my_target", "hello {}", "world",); } #[test] -fn with_args() { - info!("hello {}", "cats"); - info!("hello {}", "cats",); - info!("hello {}", "cats",); +fn named_args() { + for lvl in log::Level::iter() { + log!(lvl, "hello {world}", world = "world"); + log!(lvl, "hello {world}", world = "world",); + + log!(target: "my_target", lvl, "hello {world}", world = "world"); + log!(target: "my_target", lvl, "hello {world}", world = "world",); + + log!(lvl, "hello {world}", world = "world"); + log!(lvl, "hello {world}", world = "world",); + } + + all_log_macros!("hello {world}", world = "world"); + all_log_macros!("hello {world}", world = "world",); + + all_log_macros!(target: "my_target", "hello {world}", world = "world"); + all_log_macros!(target: "my_target", "hello {world}", world = "world",); + + let logger = Logger; + + all_log_macros!(logger: logger, "hello {world}", world = "world"); + all_log_macros!(logger: logger, "hello {world}", world = "world",); + + all_log_macros!(logger: logger, target: "my_target", "hello {world}", world = "world"); + all_log_macros!(logger: logger, target: "my_target", "hello {world}", world = "world",); } #[test] -fn with_args_expr_context() { - match "cats" { - cats => info!("hello {}", cats), +fn inlined_args() { + let world = "world"; + + for lvl in log::Level::iter() { + log!(lvl, "hello {world}"); + log!(lvl, "hello {world}",); + + log!(target: "my_target", lvl, "hello {world}"); + log!(target: "my_target", lvl, "hello {world}",); + + log!(lvl, "hello {world}"); + log!(lvl, "hello {world}",); + } + + all_log_macros!("hello {world}"); + all_log_macros!("hello {world}",); + + all_log_macros!(target: "my_target", "hello {world}"); + all_log_macros!(target: "my_target", "hello {world}",); + + let logger = Logger; + + all_log_macros!(logger: logger, "hello {world}"); + all_log_macros!(logger: logger, "hello {world}",); + + all_log_macros!(logger: logger, target: "my_target", "hello {world}"); + all_log_macros!(logger: logger, target: "my_target", "hello {world}",); +} + +#[test] +fn enabled() { + let logger = Logger; + + for lvl in log::Level::iter() { + let _enabled = log_enabled!(lvl); + let _enabled = log_enabled!(target: "my_target", lvl); + let _enabled = log_enabled!(logger: logger, target: "my_target", lvl); + let _enabled = log_enabled!(logger: logger, lvl); + } +} + +#[test] +fn expr() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(lvl, "hello"); + + log!(logger: logger, lvl, "hello"); + } +} + +#[test] +#[cfg(feature = "kv")] +fn kv_no_args() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + log!(lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + + log!(logger: logger, target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + log!(logger: logger, lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + } + + all_log_macros!(target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + all_log_macros!(target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + all_log_macros!(cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + + all_log_macros!(logger: logger, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); + all_log_macros!(logger: logger, target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_expr_args() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + + log!(lvl, target = "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + log!(lvl, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + + log!(logger: logger, target: "my_target", lvl, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + + log!(logger: logger, lvl, target = "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + log!(logger: logger, lvl, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + } + + all_log_macros!(target: "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + all_log_macros!(target = "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + all_log_macros!(cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + + all_log_macros!(logger: logger, target: "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + all_log_macros!(logger: logger, target = "my_target", cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); + all_log_macros!(logger: logger, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_anonymous_args() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + log!(lvl, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + + log!(lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + + log!(logger: logger, target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + log!(logger: logger, lvl, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + + log!(logger: logger, lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + } + + all_log_macros!(target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + all_log_macros!(target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + all_log_macros!(cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + + all_log_macros!(logger: logger, target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + all_log_macros!(logger: logger, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); + all_log_macros!(logger: logger, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_named_args() { + let logger = Logger; + + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + log!(lvl, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + + log!(lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + + log!(logger: logger, target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + log!(logger: logger, lvl, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + + log!(logger: logger, lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + } + + all_log_macros!(target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + all_log_macros!(target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + all_log_macros!(cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + + all_log_macros!(logger: logger, target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + all_log_macros!(logger: logger, target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); + all_log_macros!(logger: logger, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_ident() { + let cat_1 = "chashu"; + let cat_2 = "nori"; + + all_log_macros!(cat_1, cat_2:%, cat_count = 2; "hello {world}", world = "world"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_expr_context() { + match "chashu" { + cat_1 => { + log::info!(target: "target", cat_1 = cat_1, cat_2 = "nori"; "hello {}", "cats"); + } }; } #[test] -fn with_named_args() { - let cats = "cats"; +fn implicit_named_args() { + let world = "world"; + + for lvl in log::Level::iter() { + log!(lvl, "hello {world}"); + log!(lvl, "hello {world}",); + + log!(target: "my_target", lvl, "hello {world}"); + log!(target: "my_target", lvl, "hello {world}",); + + log!(lvl, "hello {world}"); + log!(lvl, "hello {world}",); + } + + all_log_macros!("hello {world}"); + all_log_macros!("hello {world}",); + + all_log_macros!(target: "my_target", "hello {world}"); + all_log_macros!(target: "my_target", "hello {world}",); + + #[cfg(feature = "kv")] + all_log_macros!(target = "my_target"; "hello {world}"); + #[cfg(feature = "kv")] + all_log_macros!(target = "my_target"; "hello {world}",); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_implicit_named_args() { + let world = "world"; + + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}"); + + log!(lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}"); + } + + all_log_macros!(target: "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}"); + all_log_macros!(target = "my_target", cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}"); + all_log_macros!(cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_string_keys() { + for lvl in log::Level::iter() { + log!(target: "my_target", lvl, "also dogs" = "Fílos", "key/that-can't/be/an/ident" = "hi"; "hello {world}", world = "world"); + } + + all_log_macros!(target: "my_target", "also dogs" = "Fílos", "key/that-can't/be/an/ident" = "hi"; "hello {world}", world = "world"); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_common_value_types() { + all_log_macros!( + u8 = 42u8, + u16 = 42u16, + u32 = 42u32, + u64 = 42u64, + u128 = 42u128, + i8 = -42i8, + i16 = -42i16, + i32 = -42i32, + i64 = -42i64, + i128 = -42i128, + f32 = 4.2f32, + f64 = -4.2f64, + bool = true, + str = "string"; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_debug() { + all_log_macros!( + a:? = 42, + b:debug = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_display() { + all_log_macros!( + a:% = 42, + b:display = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_std")] +fn kv_error() { + all_log_macros!( + a:err = std::io::Error::new(std::io::ErrorKind::Other, "an error"); + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_sval")] +fn kv_sval() { + all_log_macros!( + a:sval = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_serde")] +fn kv_serde() { + all_log_macros!( + a:serde = 42; + "hello world" + ); +} + +#[test] +fn logger_short_lived() { + all_log_macros!(logger: Logger, "hello"); + all_log_macros!(logger: &Logger, "hello"); +} + +#[test] +fn logger_expr() { + all_log_macros!(logger: { + let logger = Logger; + logger + }, "hello"); +} + +/// Some and None (from Option) are used in the macros. +#[derive(Debug)] +enum Type { + Some, + None, +} - info!("hello {cats}", cats = cats); - info!("hello {cats}", cats = cats,); - info!("hello {cats}", cats = cats,); +#[test] +fn regression_issue_494() { + use self::Type::*; + all_log_macros!("some message: {:?}, {:?}", None, Some); }