diff options
-rw-r--r-- | .cargo/config | 1 | ||||
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | .editorconfig | 6 | ||||
-rw-r--r-- | .github/ISSUE_TEMPLATE.md | 6 | ||||
-rw-r--r-- | .github/pull_request_template.md | 3 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .rustfmt.toml | 2 | ||||
-rw-r--r-- | .travis.yml | 32 | ||||
-rw-r--r-- | .vimrc | 3 | ||||
-rw-r--r-- | CHANGELOG.md | 289 | ||||
-rw-r--r-- | Cargo.toml | 65 | ||||
-rw-r--r-- | Cargo.toml.orig | 63 | ||||
-rw-r--r-- | LICENSE-APACHE | 201 | ||||
-rw-r--r-- | LICENSE-MIT | 25 | ||||
-rw-r--r-- | LICENSE-MPL2 | 373 | ||||
-rw-r--r-- | Makefile | 73 | ||||
-rw-r--r-- | README.md | 117 | ||||
-rw-r--r-- | benches.txt | 1 | ||||
-rw-r--r-- | build.rs | 44 | ||||
-rw-r--r-- | examples/README.md | 5 | ||||
-rw-r--r-- | examples/common/mod.rs | 36 | ||||
-rw-r--r-- | examples/named.rs | 23 | ||||
-rw-r--r-- | examples/singlethread.rs | 29 | ||||
-rw-r--r-- | examples/struct-log-self.rs | 90 | ||||
-rw-r--r-- | src/key/dynamic.rs | 203 | ||||
-rw-r--r-- | src/key/dynamic_nostd.rs | 168 | ||||
-rw-r--r-- | src/key/mod.rs | 10 | ||||
-rw-r--r-- | src/key/static.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 3752 | ||||
-rw-r--r-- | src/tests.rs | 419 |
30 files changed, 6050 insertions, 0 deletions
diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..a4081e0 --- /dev/null +++ b/.cargo/config @@ -0,0 +1 @@ +paths = [ "." ] diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..d3ca584 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "af366446d935193e0b6b650549084f2a2a8f1fbc" + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e3b5903 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*.rs] +charset = utf-8 +indent_style = space +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..f9b7853 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,6 @@ +Have you checked [slog-rs gitter channel][slog-rs gitter] already? Especially +if you have a question, you might get an answer much faster there. + +Feel free fill an issue anyway! It's just a suggestion. + +[slog-rs gitter]: https://gitter.im/slog-rs/slog diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b860948 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +Make sure to: + +* [ ] Add an entry to CHANGELOG.md (if neccessary) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c623b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +tags +rusty-tags.vi diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..48b6f1b --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 80 +reorder_imports = true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..79ca28e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +language: rust +cache: + - cargo +rust: + - stable + - 1.23.0 + - beta + - nightly + +script: + # `patch` section is recent addition + - if [ "$TRAVIS_RUST_VERSION" != "1.23.0" ]; then make all ; fi + - if [ "$TRAVIS_RUST_VERSION" == "1.23.0" ]; then make build ; fi + - if [ "$TRAVIS_RUST_VERSION" != "1.23.0" ]; then make travistest ; fi + - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then make bench ; fi + - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo build --no-default-features; cargo test --no-default-features; fi + - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo test -p test_edition2018; fi + +env: + global: + - RUST_BACKTRACE=1 + matrix: + - + - RELEASE=true + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/87a331e1a21456b6e2ad + on_success: change # options: [always|never|change] default: always + on_failure: change # options: [always|never|change] default: always + on_start: false # default: false @@ -0,0 +1,3 @@ +let g:rustfmt_autosave = 1 + +autocmd BufEnter *.rs setlocal tabstop=4 shiftwidth=4 softtabstop=4 expandtab textwidth=80 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..73312c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,289 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## 2.7.0 - 2020-11-29 + +* Add #% for alternate display of the value part +* Implement `Eq` for dynamic `Key`s +* Add `emit_error` to `Serializer`, `#` for serializing foreign errors, and + `impl Value for std::io::Error` + +## 2.6.0 - 2019-10-28 + +* Add #? for pretty-debug printing the value part + +## 2.5.3 - ????-??-?? + +* Use fully qualified call syntax for `Logger::log` in macros + +## 2.5.2 - 2019-07-22 + +* Restored parsing of `Level` and `FilterLevel` truncated names + +## 2.5.1 - 2019-07-11 + +* Added parsing of `Level` and `FilterLevel` short names + +## 2.5.0 - 2019-07-11 + +* Added `FilterLevel::accepts` +* Added `as_str`, `as_short_str` and `Display` to `FilterLevel` + +## 2.4.1 - 2018-10-03 + +* disable support for i128/u128 types if rustc is old + +## 2.4.0 - 2018-09-19 + +* Implement Value for 128 bit integers +* Add support 2018-style macro imports + * **WARNING**: This is a breaking change that we couldn't avoid. Users using + explicitly macro import (like `#[macro_use(slog_o)]`) must add + `__slog_builtin` to the import list. +* Bump miminum supported Rust version to 1.26 + +## 2.3.3 - 2018-07-20 + +* `impl Value for SocketAddr` + +## 2.3.2 - 2018-07-20 + +* Revert broken changes: + * Make `?` and `%` formatters in `kv!` more flexible + * Export local inner macros to help with Rust 2018 testing + +## 2.3.0 - 2018-07-20 + +* Export local inner macros to help with Rust 2018 testing +* Stabilize `Record::new` +* Make `?` and `%` formatters in `kv!` more flexible + +## 2.2.3 - 2018-03-28 + +* Fix (again) problems introduced by `2.2.1` + +## 2.2.2 - 2018-03-26 + +* Fix problems introduced by `2.2.1` + +## 2.2.1 - 2018-03-24 + +* Add `is_x_enabled()` for queering (imprecise) log-level + +## 2.2.0 - 2018-02-13 +### Added + +* Support for named format arguments in format messages. They will now become + respectively named key-value pairs. + +## 2.1.0 - 2017-12-10 +### Added + +* Support for nested-values through `emit_serde`, behind `nested-values` feature flag, + disabled by default for backward compatibility. **Note**: Consider unstable for the time + being. +* Support for dynamic key (`String` vs `&'static str`), behind `dynamic-keys` feature + flag, disabled by default for backward compatibility. **Note**: Consider unstable for + the time being. + +## 2.0.12 - 2017-09-14 +### Changed + +* `#[allow(dead_code)` on unused log statements + +## 2.0.11 - 2017-09-13 +### Changed + +* Impl `Value` for `std::path::Display` + +## 2.0.10 - 2017-09-09 +### Changed + +* Remove unnecessary 'static bound on `FnValue` + +## 2.0.9 - 2017-08-23 +### Changed + +* Update README + +## 2.0.6 - 2017-05-27 +### Changed + +* Fix for https://github.com/rust-lang/rust/pull/42125 + +## 2.0.5 - 2017-05-15 +### Changed + +* Relicense under MPL/Apache/MIT + +## 2.0.4 - 2017-05-05 +### Fixed + +* Documentation improvements + +## 2.0.3 - 2017-05-05 +### Fixed + +* Documentation fixes + +## 2.0.2 - 2017-04-12 +### Fixed + +* Compile time logging level filtering + +## 2.0.0 - 2017-04-11 +### Changed (since v1; bigger picture) + +* Unified and simplified logging macros structure and ordering. +* Added logging Record `tags`. +* Refactored key-value pairs traits and structures and overall handling. + * It's now possible to `impl KV for MyStruct`. + * `kv!` can be used to create on stack key-value list. + * `KV`-implementing data can appear on the key-value list directly. +* Support chaining of `OwnedKVList`s. Existing `Logger` can be used as a `Drain` + to allow custom handling logic for a part of the logging hierarchy. +* Added associated `Ok` type to `Drain`. +* Support for `Drain`s unwind safety. +* Refactored `Record` to optimize performance on async operations. +* `slog-extra` has been renamed to `slog-async` since that's the only functionality it contained. +* `slog-stream` is obsoleted and won't be used in `slog v2` at all. It was a wrong abstraction. + `Decorators` functionality was moved to `slog-term`. +* `slog-term` provides open `Decorator` traits to allow multiple terminal / file writing backends. +* `slog-term` default `Decorator`s use `term` crate and should work correctly on all supported OSes. +* `DrainExt` has been removed and utility methods moved directly to `Drain` +* `slog-stdlog` utilizes `slog-scope` directly. +* Support for "typed" `Logger`s to allow squeezing last drops of performance possible, + at the cost of `T` in `Logger<T>`. + +## 2.0.0-3.1 - 2017-03-25 +### Added + +* Support for `fmt::Display` values with `%` in `kv!` + + +## 2.0.0-3.0 - 2017-03-25 +### Changed + +* Added support for own `KV` and `Value` implementations +* Streamlined the formatting syntax for `log!` and friends; **BREAKING** +* Lazy values need explicit `FnValue` wrapper; **BREKING** + +### Added + +* `kv!` macro + +## 2.0.0-2.2 - 2017-03-19 +### Fixes + +* Bunch of trait-related fixes + +## 2.0.0-2.1 - 2017-03-11 +### Fixed + +* Require `MapErr` and `Filter` to be `UnwindSafe` + +## 2.0.0-2.0 - 2017-03-11 +### Changed + +* Make `Logger::root` return "erased" version +* Introduce `Logger::root_typed` for "non-erased" `Logger` creation + +## 2.0.0-1.0 - 2017-02-23 + +### Fixed + +* `fmt::Debug` for `MutexDrainError` + +### Changed +* Parametrize `Logger` over the `Drain` it holds and introduce "erased" version +* Enforcing `UnwindSafe` `Drain`s for `Logger`s +* Refactored key-value pairs traits and structures +* Renamed some types +* Support chaining of `OwnedKVList`s +* Added associated `Ok` type to `Drain` +* Refactored `Record` to optimize performance on async + operations +* Minimal rustc version required: `1.15.0` +* `DrainExt` has been removed and utility methods moved directly to `Drain` + +### Added + +* Macros to create `OwnedKV` and `BorrowedKV` +* `Logger` implements `Drain` + +## 1.5.0 - 2017-01-19 +### Changed + +* Order of key-value pairs is now strictly defined + +### Added + +* `Logger` implements `Drain` + +### Deprecated + +* Creation of `OwnedKeyValueList` + +## 1.4.1 - 2017-01-19 +### Fixed + +* Fix an invalid syntax exposed by nightly rust change (Issue #103) + +## 1.4.0 - 2016-12-27 +### Changed + +* Updated documentation + +### Deprecated + +* `OwnedKeyValueList::id` + +## 1.3.2 - 2016-11-19 +### Added + +* `slog_o` as an alternative name for `o` + +## 1.3.1 - 2016-11-19 +### Fixed + +* Cargo publishing mistake. + +## 1.3.0 - 2016-10-31 +### Changed + +* **BREAKING**: Removed default `Send+Sync` from `Drain` + +## 1.2.1 - 2016-10-27 +### Added + +* `OwnedKeyValueList::id` for owned key value unique identification + +## 1.2.0 - 2016-10-21 +### Changed + +* **BREAKING**: `Serializer` takes `key : &'static str` now + +### Fixed + +* Corner cases in `info!(...)` and other macros + +## 1.1.0 - 2016-10-17 +### Changed + +* **BREAKING**: Rewrite handling of owned values. + +## 1.0.1 +### Fixed + +* `use std` in `o!` + +### Added + +* Implement `fmt::Debug` for `Logger` + +## 1.0.0 - 2016-09-21 + +First stable release. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1e27df1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,65 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "slog" +version = "2.7.0" +authors = ["Dawid Ciężarkiewicz <dpc@dpc.pw>"] +build = "build.rs" +autoexamples = true +description = "Structured, extensible, composable logging for Rust" +homepage = "https://github.com/slog-rs/slog" +documentation = "https://docs.rs/slog" +readme = "README.md" +keywords = ["log", "logging", "structured", "hierarchical"] +categories = ["development-tools::debugging"] +license = "MPL-2.0 OR MIT OR Apache-2.0" +repository = "https://github.com/slog-rs/slog" +[package.metadata.docs.rs] +features = ["std", "nested-values", "dynamic-keys"] +[profile.bench] +opt-level = 3 +lto = true +debug = false +debug-assertions = false + +[profile.release] +opt-level = 3 +lto = true +debug = false +debug-assertions = false + +[[example]] +name = "singlethread" +required-features = ["nothreads"] +[dependencies.erased-serde] +version = "0.3" +optional = true + +[features] +default = ["std"] +dynamic-keys = [] +max_level_debug = [] +max_level_error = [] +max_level_info = [] +max_level_off = [] +max_level_trace = [] +max_level_warn = [] +nested-values = ["erased-serde"] +nothreads = [] +release_max_level_debug = [] +release_max_level_error = [] +release_max_level_info = [] +release_max_level_off = [] +release_max_level_trace = [] +release_max_level_warn = [] +std = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..9fd4d6f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,63 @@ +[package] +name = "slog" +version = "2.7.0" +authors = ["Dawid Ciężarkiewicz <dpc@dpc.pw>"] +description = "Structured, extensible, composable logging for Rust" +keywords = ["log", "logging", "structured", "hierarchical"] +categories = ["development-tools::debugging"] +license = "MPL-2.0 OR MIT OR Apache-2.0" +documentation = "https://docs.rs/slog" +homepage = "https://github.com/slog-rs/slog" +repository = "https://github.com/slog-rs/slog" +readme = "README.md" +autoexamples = true + +build = "build.rs" + +[profile.release] +opt-level = 3 +debug = false +lto = true +debug-assertions = false + +[profile.bench] +opt-level = 3 +debug = false +lto = true +debug-assertions = false + +[features] +nested-values = ["erased-serde"] +dynamic-keys = [] +std = [] +nothreads = [] +default = ["std"] + +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 = [] + +[dependencies] +erased-serde = { version = "0.3", optional = true } + +[[example]] +name = "singlethread" +required-features = ["nothreads"] + +[package.metadata.docs.rs] +features = ["std", "nested-values", "dynamic-keys"] + +[workspace] +members = [ + "crates/test_edition2018", +] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..39d4bdb --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/LICENSE-MPL2 b/LICENSE-MPL2 new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/LICENSE-MPL2 @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c95eeee --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +PKG_NAME=$(shell grep name Cargo.toml | head -n 1 | awk -F \" '{print $$2}') +DOCS_DEFAULT_MODULE=$(subst -,_,$(PKG_NAME)) +ifeq (, $(shell which cargo-check 2> /dev/null)) +DEFAULT_TARGET=build +else +DEFAULT_TARGET=build +endif + +default: $(DEFAULT_TARGET) + +CARGO_FLAGS += -v + +ALL_TARGETS += build $(EXAMPLES) test doc +ifneq ($(RELEASE),) +$(info RELEASE BUILD: $(PKG_NAME)) +CARGO_FLAGS += --release +else +$(info DEBUG BUILD: $(PKG_NAME); use `RELEASE=true make [args]` for release build) +endif + +EXAMPLES = $(shell cd examples 2>/dev/null && ls *.rs 2>/dev/null | sed -e 's/.rs$$//g' ) + +all: $(ALL_TARGETS) + + +.PHONY: run test build doc clean clippy +run test build clean: + cargo $@ $(CARGO_FLAGS) + +check: + $(info Running check; use `make build` to actually build) + cargo $@ $(CARGO_FLAGS) + +clippy: + cargo build --features clippy + +.PHONY: bench +bench: + cargo $@ $(filter-out --release,$(CARGO_FLAGS)) + +.PHONY: travistest +travistest: test + +.PHONY: longtest +longtest: + @echo "Running longtest. Press Ctrl+C to stop at any time" + @sleep 2 + @i=0; while i=$$((i + 1)) && echo "Iteration $$i" && make test ; do :; done + +.PHONY: $(EXAMPLES) +named struct-log-self: + cargo build --example $@ $(CARGO_FLAGS) +singlethread: + cargo build --features nothreads --example $@ $(CARGO_FLAGS) + +.PHONY: doc +doc: FORCE + cargo doc + +.PHONY: publishdoc +publishdoc: + rm -rf target/doc + make doc + echo '<meta http-equiv="refresh" content="0;url='${DOCS_DEFAULT_MODULE}'/index.html">' > target/doc/index.html + ghp-import -n target/doc + git push -f origin gh-pages + +.PHONY: docview +docview: doc + xdg-open target/doc/$(PKG_NAME)/index.html + +.PHONY: FORCE +FORCE: diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdae195 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +<p align="center"> + + <a href="https://github.com/slog-rs/slog"> + <img src="https://cdn.rawgit.com/slog-rs/misc/master/media/slog.svg" alt="slog-rs logo"> + </a> + <br> + + <a href="https://travis-ci.org/slog-rs/slog"> + <img src="https://img.shields.io/travis/slog-rs/slog/master.svg" alt="Travis CI Build Status"> + </a> + + <a href="https://crates.io/crates/slog"> + <img src="https://img.shields.io/crates/d/slog.svg" alt="slog-rs on crates.io"> + </a> + + <a href="https://gitter.im/slog-rs/slog"> + <img src="https://img.shields.io/gitter/room/slog-rs/slog.svg" alt="slog-rs Gitter Chat"> + </a> + + <a href="https://docs.rs/releases/search?query=slog-"> + <img src="https://docs.rs/slog/badge.svg" alt="docs-rs: release versions documentation"> + </a> + <br> + <strong><a href="https://github.com/slog-rs/slog/wiki/Getting-started">Getting started</a></strong> + <a href="//github.com/slog-rs/slog/wiki/Introduction-to-structured-logging-with-slog">Introduction</a> + <a href="//github.com/slog-rs/slog/wiki/FAQ">FAQ</a> + <br> + <a href="https://crates.io/search?q=slog">Crate list</a> +</p> + +# slog-rs - The Logging for [Rust][rust] + +### Introduction (please read) + +`slog` is an ecosystem of reusable components for structured, extensible, +composable and contextual logging for [Rust][rust]. + +The ambition is to be The Logging Library for Rust. `slog` should accommodate a +variety of logging features and requirements. If there is a feature that you +need and standard `log` crate is missing, `slog` should have it. + +This power comes with a little steeper learning curve, so if you experience any +problems, **please join [slog-rs gitter] channel** to get up to speed. If you'd +like to take a quick, convenient route, consider using +[sloggers](https://docs.rs/sloggers/) wrapper library. + +While the code is reliable, the documentation sometimes could use an improvement. +Please report all issues and ideas. + +### Features & technical documentation + +Most of the interesting documentation is auto-generated and hosted on [https://docs.rs](https://docs.rs/slog/). + +Go to [docs.rs/slog](https://docs.rs/slog/) to read about features and APIs +(examples included). + +**Note**: `slog` is just a core, and the actual functionality is inside +many feature crates. To name a few: + +* [slog-term](https://docs.rs/slog-term/) for terminal output +* [slog-async](https://docs.rs/slog-async/) for asynchronous logging +* [slog-json](https://docs.rs/slog-json/) for logging JSON +* [slog-syslog](https://docs.rs/slog-syslog/) for logging to syslog +* [sloggers](https://docs.rs/sloggers/) for convenience methods (note: [3rd-party library](https://github.com/sile/sloggers)) + +There are many more slog feature crates. Search for [more slog features on +crates.io](https://crates.io/search?q=slog). It is easy to write and publish +new ones. Look through all the [existing crates using +slog](https://crates.io/crates/slog/reverse_dependencies) for examples and ideas. + +### Terminal output example + +`slog-term` is only one of many `slog` features - useful showcase, +multi-platform, and featuring eg. automatic TTY detection and colors. + +See following screenshot: same output in both compact and full output mode. + +![slog-rs terminal example output](http://i.imgur.com/mqrG8yL.png) + +## Using & help + +Please use [slog-rs gitter] channel to ask for help or discuss +slog features. + +See +[examples/features.rs](https://github.com/slog-rs/misc/blob/master/examples/features.rs) +for full quick code example overview. + +Read [Documentation](https://docs.rs/slog/) for details and features. + +To report a bug or ask for features use [github issues][issues]. + +[faq]: https://github.com/slog-rs/slog/wiki/FAQ +[wiki]: https://github.com/slog-rs/slog/wiki/ +[rust]: http://rust-lang.org +[slog-rs gitter]: https://gitter.im/slog-rs/slog +[issues]: //github.com/slog-rs/slog/issues + +## Slog community + +Slog related crates are hosted under [slog github +organization](https://github.com/slog-rs). + +Dawid Ciężarkiewicz is the original author and current maintainer of `slog` and +therefore self-appointed benevolent dictator over the project. When working on +slog Dawid follows and expects everyone to follow his [Code of +Conduct](https://github.com/dpc/public/blob/master/COC.md). + +Any particular repositories under slog ecosystem might be created, controlled, +maintained by other entities with various levels of autonomy. Lets work together +toward a common goal in a respectful and welcoming atmosphere! + +## Verification Recommendation + +To help with the maintaince, the ownership of this crate is potentially shared between multiple developers. +It is recommended to always use [cargo-crev](https://github.com/crev-dev/cargo-crev) +to verify the trustworthiness of each of your dependencies, including this one. diff --git a/benches.txt b/benches.txt new file mode 100644 index 0000000..aacf05a --- /dev/null +++ b/benches.txt @@ -0,0 +1 @@ +Benchmarks have been moved to: https://github.com/slog-rs/misc/tree/master/benches diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..14bf916 --- /dev/null +++ b/build.rs @@ -0,0 +1,44 @@ +use std::env; +use std::process::Command; +use std::str; + +// FIXME: replace it with '?' operator +macro_rules! try_opt { + ($e:expr) => { + match { $e } { + Some(e) => e, + None => return None, + } + }; +} + +fn rustc_minor_version() -> Option<u32> { + let rustc = try_opt!(env::var_os("RUSTC")); + let output = try_opt!(Command::new(rustc).arg("--version").output().ok()); + let version_str = try_opt!(str::from_utf8(&output.stdout).ok()); + let minor_str = try_opt!(version_str.split('.').nth(1)); + + minor_str.parse().ok() +} + +fn main() { + let minor = match rustc_minor_version() { + Some(m) => m, + None => return, + }; + + let target = env::var("TARGET").unwrap(); + let is_emscripten = target == "asmjs-unknown-emscripten" + || target == "wasm32-unknown-emscripten"; + + if minor >= 26 && !is_emscripten { + println!("cargo:rustc-cfg=integer128"); + } + + // workaround on macro bugs fixed in 1.20 + // + // https://github.com/rust-lang/rust/pull/42913 + if minor < 20 { + println!("cargo:rustc-cfg=macro_workaround"); + } +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..9079cf0 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +This directory contains some general examples on how to use `slog` + +More examples can be found in: https://github.com/slog-rs/misc/tree/master/examples + +Also, some functionality-specific examples can be found in respective crates implementing them. diff --git a/examples/common/mod.rs b/examples/common/mod.rs new file mode 100644 index 0000000..b83755d --- /dev/null +++ b/examples/common/mod.rs @@ -0,0 +1,36 @@ +use slog::*; +use std::{fmt, result}; + +pub struct PrintlnSerializer; + +impl Serializer for PrintlnSerializer { + fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> Result { + print!(", {}={}", key, val); + Ok(()) + } +} + +pub struct PrintlnDrain; + +impl Drain for PrintlnDrain { + type Ok = (); + type Err = (); + + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + + print!("{}", record.msg()); + + record + .kv() + .serialize(record, &mut PrintlnSerializer) + .unwrap(); + values.serialize(record, &mut PrintlnSerializer).unwrap(); + + println!(""); + Ok(()) + } +} diff --git a/examples/named.rs b/examples/named.rs new file mode 100644 index 0000000..15a12be --- /dev/null +++ b/examples/named.rs @@ -0,0 +1,23 @@ +//#![feature(trace_macros)] +#[macro_use] +extern crate slog; +use slog::{Fuse, Logger}; + +mod common; + +fn main() { + let log = Logger::root( + Fuse(common::PrintlnDrain), + o!("version" => "2") + ); + + //trace_macros!(true); + info!(log, "foo is {foo}", foo = 2; "a" => "b"); + info!(log, "foo is {foo} {bar}", bar=3, foo = 2; "a" => "b"); + info!(log, "foo is {foo} {bar} {baz}", bar=3, foo = 2, baz=4; "a" => "b"); + info!(log, "foo is {foo} {bar} {baz}", bar = 3, foo = 2, baz = 4;); + info!(log, "foo is {foo} {bar} {baz}", bar=3, foo = 2, baz=4); + info!(log, "foo is {foo} {bar} {baz}", bar=3, foo = 2, baz=4,); + info!(log, "formatted {num_entries} entries of {}", "something", num_entries = 2; "log-key" => true); + info!(log, "{first} {third} {second}", first = 1, second = 2, third=3; "forth" => 4, "fifth" => 5); +} diff --git a/examples/singlethread.rs b/examples/singlethread.rs new file mode 100644 index 0000000..1ca5689 --- /dev/null +++ b/examples/singlethread.rs @@ -0,0 +1,29 @@ +//#![feature(nothreads)] +#[macro_use] +extern crate slog; +use slog::{Fuse, Logger}; +use std::cell::RefCell; +use std::rc::Rc; + +mod common; + +#[derive(Clone)] +struct NoThreadSafeObject { + val: Rc<RefCell<usize>>, +} + +fn main() { + let log = Logger::root(Fuse(common::PrintlnDrain), o!("version" => "2")); + let obj = NoThreadSafeObject { + val: Rc::new(RefCell::new(4)), + }; + + // Move obj2 into a closure. Since it's !Send, this only works + // with nothreads feature. + let obj2 = obj.clone(); + let sublog = log.new(o!("obj.val" => slog::FnValue(move |_| { + format!("{}", obj2.val.borrow()) + }))); + + info!(sublog, "test"); +} diff --git a/examples/struct-log-self.rs b/examples/struct-log-self.rs new file mode 100644 index 0000000..f533766 --- /dev/null +++ b/examples/struct-log-self.rs @@ -0,0 +1,90 @@ +//! Example of how to implement `KV` for a struct +//! to conveniently log data associated with it. +#[macro_use] +extern crate slog; +use slog::*; + +mod common; + +struct Peer { + host: String, + port: u32, +} + +impl Peer { + fn new(host: String, port: u32) -> Self { + Peer { + host: host, + port: port, + } + } +} + +// `KV` can be implemented for a struct +impl KV for Peer { + fn serialize(&self, _record: &Record, serializer: &mut Serializer) -> Result { + serializer.emit_u32(Key::from("peer-port"), self.port)?; + serializer.emit_str(Key::from("peer-host"), &self.host)?; + Ok(()) + } +} + +struct Server { + _host: String, + _port: u32, + // One approach is to create new `Logger` with struct data + // and embedded it into struct itself. This works when struct is mostly + // immutable. + log: Logger, +} + +impl Server { + fn new(host: String, port: u32, log: Logger) -> Server { + let log = log.new(o!("server-host" => host.clone(), "server-port" => port)); + Server { + _host: host, + _port: port, + log: log, + } + } + + fn connection(&self, peer: &Peer) { + // Another approach is to add struct to a logging message when it's + // necessary. This might be necessary when struct data can change + // between different logging statements (not the case here for `Peer`). + info!(self.log, "new connection"; peer); + } +} + +struct PeerCounter { + count: usize, + log: Logger, +} + +impl PeerCounter { + fn new(log: Logger) -> Self { + PeerCounter { count: 0, log: log } + } + + // A hybrid approach with `Logger` with parent logging-context embedded into + // a `struct` and a helper function adding mutable fields. + fn log_info(&self, msg: &str, kv: BorrowedKV) { + info!(self.log, "{}", msg; "current-count" => self.count, kv); + } + + fn count(&mut self, peer: &Peer) { + self.count += 1; + self.log_info("counted peer", b!(peer)); + } +} + +fn main() { + let log = Logger::root(Fuse(common::PrintlnDrain), o!("build-id" => "7.3.3-abcdef")); + + let server = Server::new("localhost".into(), 12345, log.clone()); + + let peer = Peer::new("1.2.3.4".into(), 999); + server.connection(&peer); + let mut counter = PeerCounter::new(log); + counter.count(&peer); +} diff --git a/src/key/dynamic.rs b/src/key/dynamic.rs new file mode 100644 index 0000000..fe9ba7c --- /dev/null +++ b/src/key/dynamic.rs @@ -0,0 +1,203 @@ +#[cfg(not(feature = "std"))] +use alloc::borrow::Cow; +#[cfg(not(feature = "std"))] +use alloc::string::{String, ToString}; +#[cfg(not(feature = "std"))] +use core::cmp::PartialEq; +#[cfg(not(feature = "std"))] +use core::convert::{AsRef, From, Into}; +#[cfg(not(feature = "std"))] +use core::fmt; +#[cfg(not(feature = "std"))] +use core::hash::{Hash, Hasher}; +#[cfg(not(feature = "std"))] +use core::iter::{FromIterator, IntoIterator}; +#[cfg(not(feature = "std"))] +use core::ops::Deref; +#[cfg(not(feature = "std"))] +use core::str::FromStr; +#[cfg(feature = "std")] +use std::borrow::Cow; +#[cfg(feature = "std")] +use std::cmp::PartialEq; +#[cfg(feature = "std")] +use std::convert::{AsRef, From}; +#[cfg(feature = "std")] +use std::fmt; +#[cfg(feature = "std")] +use std::hash::{Hash, Hasher}; +#[cfg(feature = "std")] +use std::iter::{FromIterator, IntoIterator}; +#[cfg(feature = "std")] +use std::string::String; +#[cfg(feature = "std")] +use std::string::ToString; + +/// Opaque Key is a representation of a key. +/// +/// It is owned, and largely forms a contract for +/// key to follow. +#[derive(Clone, Eq)] +pub struct Key { + data: Cow<'static, str>, +} + +impl Key { + /// Returns the length of self. + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns the length of self. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Take as a `str` + pub fn as_str(&self) -> &str { + self.data.as_ref() + } + + /// Take as a reference + pub fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Default for Key { + fn default() -> Key { + Key { + data: Cow::Borrowed(""), + } + } +} + +impl Hash for Key { + fn hash<H: Hasher>(&self, state: &mut H) { + match self.data { + Cow::Borrowed(ref ptr) => ptr.hash(state), + Cow::Owned(ref ptr) => ptr.hash(state), + } + } +} + +impl From<&'static str> for Key { + #[inline(always)] + fn from(data: &'static str) -> Key { + Key { + data: Cow::Borrowed(data), + } + } +} + +impl From<String> for Key { + fn from(data: String) -> Key { + Key { + data: Cow::Owned(data), + } + } +} + +impl From<Key> for String { + fn from(s: Key) -> String { + s.to_string() + } +} + +impl FromIterator<char> for Key { + fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()), + } + } +} + +impl<'a> FromIterator<&'a char> for Key { + fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()), + } + } +} + +impl FromIterator<String> for Key { + fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()), + } + } +} + +impl<'a> FromIterator<&'a String> for Key { + fn from_iter<I: IntoIterator<Item = &'a String>>(iter: I) -> Key { + Key { + data: Cow::Owned( + iter.into_iter().map(|x| x.as_str()).collect::<String>(), + ), + } + } +} + +impl<'a> FromIterator<&'a str> for Key { + fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()), + } + } +} + +impl<'a> FromIterator<Cow<'a, str>> for Key { + fn from_iter<I: IntoIterator<Item = Cow<'a, str>>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()), + } + } +} + +impl PartialEq<str> for Key { + fn eq(&self, other: &str) -> bool { + self.as_ref().eq(other) + } +} + +impl PartialEq<&str> for Key { + fn eq(&self, other: &&str) -> bool { + self.as_ref().eq(*other) + } +} + +impl PartialEq<String> for Key { + fn eq(&self, other: &String) -> bool { + self.as_ref().eq(other.as_str()) + } +} + +impl PartialEq<Self> for Key { + fn eq(&self, other: &Self) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl AsRef<str> for Key { + fn as_ref<'a>(&'a self) -> &'a str { + self.data.as_ref() + } +} + +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.data { + Cow::Borrowed(ref ptr) => write!(f, "{}", ptr), + Cow::Owned(ref ptr) => write!(f, "{}", ptr), + } + } +} + +impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.data { + Cow::Borrowed(ref ptr) => write!(f, "{:?}", ptr), + Cow::Owned(ref ptr) => write!(f, "{:?}", ptr), + } + } +} diff --git a/src/key/dynamic_nostd.rs b/src/key/dynamic_nostd.rs new file mode 100644 index 0000000..97af971 --- /dev/null +++ b/src/key/dynamic_nostd.rs @@ -0,0 +1,168 @@ +use alloc::borrow::Cow; +use alloc::string::{String, ToString}; + +use core::convert::{From,Into,AsRef}; +use core::ops::Deref; +use core::str::FromStr; +use core::cmp::PartialEq; +use core::hash::{Hash,Hasher}; +use core::iter::{FromIterator, IntoIterator}; +use core::fmt; + +/// Opaque Key is a representation of a key. +/// +/// It is owned, and largely forms a contract for +/// key to follow. +#[derive(Clone, Eq)] +pub struct Key { + data: Cow<'static, str> +} + +impl Key { + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn as_str(&self) -> &str { + self.data.as_ref() + } + + pub fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Default for Key { + fn default() -> Key { + Key { + data: Cow::Borrowed("") + } + } +} + +impl Hash for Key { + fn hash<H: Hasher>(&self, state: &mut H) { + match self.data { + Cow::Borrowed(ref ptr) => ptr.hash(state), + Cow::Owned(ref ptr) => ptr.hash(state) + } + } +} + +impl From<&'static str> for Key { + #[inline(always)] + fn from(data: &'static str) -> Key { + Key { + data: Cow::Borrowed(data) + } + } +} + +impl From<String> for Key { + #[inline(always)] + fn from(data: String) -> Key { + Key{ + data: Cow::Owned(data) + } + } +} + +impl FromIterator<char> for Key { + fn from_iter<I: IntoIterator<Item=char>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()) + } + } +} + +impl<'a> FromIterator<&'a char> for Key { + fn from_iter<I: IntoIterator<Item=&'a char>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()) + } + } +} + +impl FromIterator<String> for Key { + fn from_iter<I: IntoIterator<Item=String>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()) + } + } +} + +impl<'a> FromIterator<&'a String> for Key { + fn from_iter<I: IntoIterator<Item=&'a String>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().map(|x| x.as_str()).collect::<String>()) + } + } +} + +impl<'a> FromIterator<&'a str> for Key { + fn from_iter<I: IntoIterator<Item=&'a str>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()) + } + } +} + +impl<'a> FromIterator<Cow<'a,str>> for Key { + fn from_iter<I: IntoIterator<Item=Cow<'a,str>>>(iter: I) -> Key { + Key { + data: Cow::Owned(iter.into_iter().collect::<String>()) + } + } +} + +impl PartialEq<str> for Key { + #[inline(always)] + fn eq(&self, other: &str) -> bool { + self.as_ref().eq(other) + } +} + +impl PartialEq<&str> for Key { + #[inline(always)] + fn eq(&self, other: &&str) -> bool { + self.as_ref().eq(*other) + } +} + +impl PartialEq<String> for Key { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_ref().eq(other.as_str()) + } +} + +impl PartialEq<Self> for Key { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_ref().eq(other.as_ref()) + } +} +impl AsRef<str> for Key { + #[inline(always)] + fn as_ref<'a>(&'a self) -> &'a str { + self.data.as_ref() + } +} +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.data { + Cow::Borrowed(ref ptr) => write!(f, "{}", ptr), + Cow::Owned(ref ptr) => write!(f, "{}", ptr) + } + } +} +impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.data { + Cow::Borrowed(ref ptr) => write!(f, "{:?}", ptr), + Cow::Owned(ref ptr) => write!(f, "{:?}", ptr) + } + } +} + diff --git a/src/key/mod.rs b/src/key/mod.rs new file mode 100644 index 0000000..3f54ed7 --- /dev/null +++ b/src/key/mod.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "dynamic-keys")] +mod dynamic; +#[cfg(feature = "dynamic-keys")] +pub use self::dynamic::Key; + +#[cfg(not(feature = "dynamic-keys"))] +#[path = "static.rs"] +mod static_; +#[cfg(not(feature = "dynamic-keys"))] +pub use self::static_::Key; diff --git a/src/key/static.rs b/src/key/static.rs new file mode 100644 index 0000000..0465ed5 --- /dev/null +++ b/src/key/static.rs @@ -0,0 +1,2 @@ +/// Key type +pub type Key = &'static str; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..904589e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3752 @@ +//! # Slog - Structured, extensible, composable logging for Rust +//! +//! `slog-rs` is an ecosystem of reusable components for structured, extensible, +//! composable logging for Rust. +//! +//! `slog` is `slog-rs`'s main crate providing core components shared between +//! all other parts of `slog-rs` ecosystem. +//! +//! This is auto-generated technical documentation of `slog`. For information +//! about project organization, development, help, etc. please see +//! [slog github page](https://github.com/slog-rs/slog) +//! +//! ## Core advantages over `log` crate +//! +//! * **extensible** - `slog` crate provides core functionality: a very basic +//! and portable standard feature-set based on open `trait`s. This allows +//! implementing new features that can be independently published. +//! * **composable** - `trait`s that `slog` exposes to provide extensibility +//! are designed to be easy to efficiently reuse and combine. By combining +//! different functionalities, each application can specify precisely when, +//! where, and how to process logging data from an application and its +//! dependencies. +//! * **flexible** - `slog` does not constrain logging to just one globally +//! registered backend. Parts of your application can handle logging +//! in a customized way, or completely independently. +//! * **structured** and both **human and machine readable** - By using the +//! key-value data format and retaining its type information, the meaning of logging +//! data is preserved. Data can be serialized to machine readable formats like +//! JSON and sent to data-mining systems for further analysis, etc. On the +//! other hand, when presenting on screen, logging information can be presented +//! in aesthetically pleasing and easy to understand ways. +//! * **contextual** - `slog`'s `Logger` objects carry set of key-value data +//! pairs that contain the context of logging - information that otherwise +//! would have to be repeated in every logging statement. +//! +//! ## `slog` features +//! +//! * performance oriented; read [what makes slog +//! fast](https://github.com/slog-rs/slog/wiki/What-makes-slog-fast) and see: +//! [slog bench log](https://github.com/dpc/slog-rs/wiki/Bench-log) +//! * lazily evaluation through closure values +//! * async IO support included: see [`slog-async` +//! crate](https://docs.rs/slog-async) +//! * `#![no_std]` support (with opt-out `std` cargo feature flag) +//! * support for named format arguments (e.g. `info!(logger, "printed {line_count} lines", line_count = 2);`) +//! for easy bridging between the human readable and machine-readable outputs +//! * tree-structured loggers +//! * modular, lightweight and very extensible +//! * tiny core crate that does not pull any dependencies +//! * feature-crates for specific functionality +//! * using `slog` in library does not force users of the library to use slog +//! (but provides additional functionality); see [example how to use +//! `slog` in library](https://github.com/slog-rs/example-lib) +//! * backwards and forwards compatibility with `log` crate: +//! see [`slog-stdlog` crate](https://docs.rs/slog-stdlog) +//! * convenience crates: +//! * logging-scopes for implicit `Logger` passing: see +//! [slog-scope crate](https://docs.rs/slog-scope) +//! * many existing core & community provided features: +//! * multiple outputs +//! * filtering control +//! * compile-time log level filter using cargo features (same as in `log` +//! crate) +//! * by level, msg, and any other meta-data +//! * [`slog-envlogger`](https://github.com/slog-rs/envlogger) - port of +//! `env_logger` +//! * terminal output, with color support: see [`slog-term` +//! crate](https://docs.rs/slog-term) +//! * [json](https://docs.rs/slog-json) +//! * [bunyan](https://docs.rs/slog-bunyan) +//! * [syslog](https://docs.rs/slog-syslog) +//! and [journald](https://docs.rs/slog-journald) support +//! * run-time configuration: +//! * run-time behavior change; +//! see [slog-atomic](https://docs.rs/slog-atomic) +//! * run-time configuration; see +//! [slog-config crate](https://docs.rs/slog-config) +//! +//! +//! [env_logger]: https://crates.io/crates/env_logger +//! +//! ## Notable details +//! +//! **Note:** At compile time `slog` by default removes trace and debug level +//! statements in release builds, and trace level records in debug builds. This +//! makes `trace` and `debug` level logging records practically free, which +//! should encourage using them freely. If you want to enable trace/debug +//! messages or raise the compile time logging level limit, use the following in +//! your `Cargo.toml`: +//! +//! ```norust +//! slog = { version = ... , +//! features = ["max_level_trace", "release_max_level_warn"] } +//! ``` +//! +//! Root drain (passed to `Logger::root`) must be one that does not ever return +//! errors. This forces user to pick error handing strategy. +//! `Drain::fuse()` or `Drain::ignore_res()`. +//! +//! [env_logger]: https://crates.io/crates/env_logger +//! [fn-overv]: https://github.com/dpc/slog-rs/wiki/Functional-overview +//! [atomic-switch]: https://docs.rs/slog-atomic/ +//! +//! ## Where to start +//! +//! [`Drain`](trait.Drain.html), [`Logger`](struct.Logger.html) and +//! [`log` macro](macro.log.html) are the most important elements of +//! slog. Make sure to read their respective documentation +//! +//! Typically the biggest problem is creating a `Drain` +//! +//! +//! ### Logging to the terminal +//! +//! ```ignore +//! #[macro_use] +//! extern crate slog; +//! extern crate slog_term; +//! extern crate slog_async; +//! +//! use slog::Drain; +//! +//! fn main() { +//! let decorator = slog_term::TermDecorator::new().build(); +//! let drain = slog_term::FullFormat::new(decorator).build().fuse(); +//! let drain = slog_async::Async::new(drain).build().fuse(); +//! +//! let _log = slog::Logger::root(drain, o!()); +//! } +//! ``` +//! +//! ### Logging to a file +//! +//! ```ignore +//! #[macro_use] +//! extern crate slog; +//! extern crate slog_term; +//! extern crate slog_async; +//! +//! use std::fs::OpenOptions; +//! use slog::Drain; +//! +//! fn main() { +//! let log_path = "target/your_log_file_path.log"; +//! let file = OpenOptions::new() +//! .create(true) +//! .write(true) +//! .truncate(true) +//! .open(log_path) +//! .unwrap(); +//! +//! let decorator = slog_term::PlainDecorator::new(file); +//! let drain = slog_term::FullFormat::new(decorator).build().fuse(); +//! let drain = slog_async::Async::new(drain).build().fuse(); +//! +//! let _log = slog::Logger::root(drain, o!()); +//! } +//! ``` +//! +//! You can consider using `slog-json` instead of `slog-term`. +//! `slog-term` only coincidently fits the role of a file output format. A +//! proper `slog-file` crate with suitable format, log file rotation and other +//! file-logging related features would be awesome. Contributions are welcome! +//! +//! ### Change logging level at runtime +//! +//! ```ignore +//! #[macro_use] +//! extern crate slog; +//! extern crate slog_term; +//! extern crate slog_async; +//! +//! use slog::Drain; +//! +//! use std::sync::{Arc, atomic}; +//! use std::sync::atomic::Ordering; +//! use std::result; +//! +//! /// Custom Drain logic +//! struct RuntimeLevelFilter<D>{ +//! drain: D, +//! on: Arc<atomic::AtomicBool>, +//! } +//! +//! impl<D> Drain for RuntimeLevelFilter<D> +//! where D : Drain { +//! type Ok = Option<D::Ok>; +//! type Err = Option<D::Err>; +//! +//! fn log(&self, +//! record: &slog::Record, +//! values: &slog::OwnedKVList) +//! -> result::Result<Self::Ok, Self::Err> { +//! let current_level = if self.on.load(Ordering::Relaxed) { +//! slog::Level::Trace +//! } else { +//! slog::Level::Info +//! }; +//! +//! if record.level().is_at_least(current_level) { +//! self.drain.log( +//! record, +//! values +//! ) +//! .map(Some) +//! .map_err(Some) +//! } else { +//! Ok(None) +//! } +//! } +//! } +//! +//! fn main() { +//! // atomic variable controlling logging level +//! let on = Arc::new(atomic::AtomicBool::new(false)); +//! +//! let decorator = slog_term::TermDecorator::new().build(); +//! let drain = slog_term::FullFormat::new(decorator).build(); +//! let drain = RuntimeLevelFilter { +//! drain: drain, +//! on: on.clone(), +//! }.fuse(); +//! let drain = slog_async::Async::new(drain).build().fuse(); +//! +//! let _log = slog::Logger::root(drain, o!()); +//! +//! // switch level in your code +//! on.store(true, Ordering::Relaxed); +//! } +//! ``` +//! +//! Why is this not an existing crate? Because there are multiple ways to +//! achieve the same result, and each application might come with its own +//! variation. Supporting a more general solution is a maintenance effort. +//! There is also nothing stopping anyone from publishing their own crate +//! implementing it. +//! +//! Alternative to the above approach is `slog-atomic` crate. It implements +//! swapping whole parts of `Drain` logging hierarchy. +//! +//! ## Examples & help +//! +//! Basic examples that are kept up-to-date are typically stored in +//! respective git repository, under `examples/` subdirectory. Eg. +//! [slog-term examples](https://github.com/slog-rs/term/tree/master/examples). +//! +//! [slog-rs wiki pages](https://github.com/slog-rs/slog/wiki) contain +//! some pages about `slog-rs` technical details. +//! +//! Source code of other [software using +//! slog-rs](https://crates.io/crates/slog/reverse_dependencies) can +//! be an useful reference. +//! +//! Visit [slog-rs gitter channel](https://gitter.im/slog-rs/slog) for immediate +//! help. +//! +//! ## Migrating from slog v1 to slog v2 +//! +//! ### Key-value pairs come now after format string +//! +//! ``` +//! #[macro_use] +//! extern crate slog; +//! +//! fn main() { +//! let drain = slog::Discard; +//! let root = slog::Logger::root(drain, o!()); +//! info!(root, "formatted: {}", 1; "log-key" => true); +//! } +//! ``` +//! +//! See more information about format at [`log`](macro.log.html). +//! +//! ### `slog-streamer` is gone +//! +//! Create simple terminal logger like this: +//! +//! ```ignore +//! #[macro_use] +//! extern crate slog; +//! extern crate slog_term; +//! extern crate slog_async; +//! +//! use slog::Drain; +//! +//! fn main() { +//! let decorator = slog_term::TermDecorator::new().build(); +//! let drain = slog_term::FullFormat::new(decorator).build().fuse(); +//! let drain = slog_async::Async::new(drain).build().fuse(); +//! +//! let _log = slog::Logger::root(drain, o!()); +//! } +//! ``` +//! +//! +//! ### Logging macros now takes ownership of values. +//! +//! Pass them by reference: `&x`. +//! +// }}} + +// {{{ Imports & meta +#![warn(missing_docs)] +#![no_std] + +#[cfg(not(feature = "std"))] +extern crate alloc; +#[macro_use] +#[cfg(feature = "std")] +extern crate std; + +mod key; +pub use self::key::Key; +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(not(feature = "std"))] +use alloc::rc::Rc; +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[cfg(feature = "nested-values")] +extern crate erased_serde; + +use core::{convert, fmt, result}; +use core::str::FromStr; +#[cfg(feature = "std")] +use std::boxed::Box; +#[cfg(feature = "std")] +use std::panic::{RefUnwindSafe, UnwindSafe}; +#[cfg(feature = "std")] +use std::rc::Rc; +#[cfg(feature = "std")] +use std::string::String; +#[cfg(feature = "std")] +use std::sync::Arc; +// }}} + +// {{{ Macros +/// Macro for building group of key-value pairs: +/// [`OwnedKV`](struct.OwnedKV.html) +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let _root = slog::Logger::root( +/// drain, +/// o!("key1" => "value1", "key2" => "value2") +/// ); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! o( + ($($args:tt)*) => { + $crate::OwnedKV(kv!($($args)*)) + }; +); + +/// Macro for building group of key-value pairs (alias) +/// +/// Use in case of macro name collisions +#[macro_export(local_inner_macros)] +macro_rules! slog_o( + ($($args:tt)*) => { + $crate::OwnedKV(slog_kv!($($args)*)) + }; +); + +/// Macro for building group of key-value pairs in +/// [`BorrowedKV`](struct.BorrowedKV.html) +/// +/// In most circumstances using this macro directly is unnecessary and `info!` +/// and other wrappers over `log!` should be used instead. +#[macro_export(local_inner_macros)] +macro_rules! b( + ($($args:tt)*) => { + $crate::BorrowedKV(&kv!($($args)*)) + }; +); + +/// Alias of `b` +#[macro_export(local_inner_macros)] +macro_rules! slog_b( + ($($args:tt)*) => { + $crate::BorrowedKV(&slog_kv!($($args)*)) + }; +); + +/// Macro for build `KV` implementing type +/// +/// You probably want to use `o!` or `b!` instead. +#[macro_export(local_inner_macros)] +macro_rules! kv( + (@ $args_ready:expr; $k:expr => %$v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => %$v:expr, $($args:tt)* ) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => #%$v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => #%$v:expr, $($args:tt)* ) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => ?$v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:?}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => ?$v:expr, $($args:tt)* ) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:?}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => #?$v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => #?$v:expr, $($args:tt)* ) => { + kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => #$v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, $crate::ErrorValue($v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => $v:expr) => { + kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => $v:expr, $($args:tt)* ) => { + kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $kv:expr) => { + kv!(@ ($kv, $args_ready); ) + }; + (@ $args_ready:expr; $kv:expr, $($args:tt)* ) => { + kv!(@ ($kv, $args_ready); $($args)* ) + }; + (@ $args_ready:expr; ) => { + $args_ready + }; + (@ $args_ready:expr;, ) => { + $args_ready + }; + ($($args:tt)*) => { + kv!(@ (); $($args)*) + }; +); + +/// Alias of `kv` +#[macro_export(local_inner_macros)] +macro_rules! slog_kv( + (@ $args_ready:expr; $k:expr => %$v:expr) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => %$v:expr, $($args:tt)* ) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => #%$v:expr) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => #%$v:expr, $($args:tt)* ) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => ?$v:expr) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:?}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => ?$v:expr, $($args:tt)* ) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:?}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => #?$v:expr) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => #?$v:expr, $($args:tt)* ) => { + slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $k:expr => $v:expr) => { + slog_kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); ) + }; + (@ $args_ready:expr; $k:expr => $v:expr, $($args:tt)* ) => { + slog_kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); $($args)* ) + }; + (@ $args_ready:expr; $slog_kv:expr) => { + slog_kv!(@ ($slog_kv, $args_ready); ) + }; + (@ $args_ready:expr; $slog_kv:expr, $($args:tt)* ) => { + slog_kv!(@ ($slog_kv, $args_ready); $($args)* ) + }; + (@ $args_ready:expr; ) => { + $args_ready + }; + (@ $args_ready:expr;, ) => { + $args_ready + }; + ($($args:tt)*) => { + slog_kv!(@ (); $($args)*) + }; +); + +#[macro_export(local_inner_macros)] +/// Create `RecordStatic` at the given code location +macro_rules! record_static( + ($lvl:expr, $tag:expr,) => { record_static!($lvl, $tag) }; + ($lvl:expr, $tag:expr) => {{ + static LOC : $crate::RecordLocation = $crate::RecordLocation { + file: __slog_builtin!(@file), + line: __slog_builtin!(@line), + column: __slog_builtin!(@column), + function: "", + module: __slog_builtin!(@module_path), + }; + $crate::RecordStatic { + location : &LOC, + level: $lvl, + tag : $tag, + } + }}; +); + +#[macro_export(local_inner_macros)] +/// Create `RecordStatic` at the given code location (alias) +macro_rules! slog_record_static( + ($lvl:expr, $tag:expr,) => { slog_record_static!($lvl, $tag) }; + ($lvl:expr, $tag:expr) => {{ + static LOC : $crate::RecordLocation = $crate::RecordLocation { + file: __slog_builtin!(@file), + line: __slog_builtin!(@line), + column: __slog_builtin!(@column), + function: "", + module: __slog_builtin!(@module_path), + }; + $crate::RecordStatic { + location : &LOC, + level: $lvl, + tag: $tag, + } + }}; +); + +#[macro_export(local_inner_macros)] +/// Create `Record` at the given code location +/// +/// Note that this requires that `lvl` and `tag` are compile-time constants. If +/// you need them to *not* be compile-time, such as when recreating a `Record` +/// from a serialized version, use `Record::new` instead. +macro_rules! record( + ($lvl:expr, $tag:expr, $args:expr, $b:expr,) => { + record!($lvl, $tag, $args, $b) + }; + ($lvl:expr, $tag:expr, $args:expr, $b:expr) => {{ + #[allow(dead_code)] + static RS : $crate::RecordStatic<'static> = record_static!($lvl, $tag); + $crate::Record::new(&RS, $args, $b) + }}; +); + +#[macro_export(local_inner_macros)] +/// Create `Record` at the given code location (alias) +macro_rules! slog_record( + ($lvl:expr, $tag:expr, $args:expr, $b:expr,) => { + slog_record!($lvl, $tag, $args, $b) + }; + ($lvl:expr, $tag:expr, $args:expr, $b:expr) => {{ + static RS : $crate::RecordStatic<'static> = slog_record_static!($lvl, + $tag); + $crate::Record::new(&RS, $args, $b) + }}; +); + +/// Log message a logging record +/// +/// Use wrappers `error!`, `warn!` etc. instead +/// +/// The `max_level_*` and `release_max_level*` cargo features can be used to +/// statically disable logging at various levels. See [slog notable +/// details](index.html#notable-details) +/// +/// Use [version with longer name](macro.slog_log.html) if you want to prevent +/// clash with legacy `log` crate macro names. +/// +/// ## Supported invocations +/// +/// ### Simple +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let root = slog::Logger::root( +/// drain, +/// o!("key1" => "value1", "key2" => "value2") +/// ); +/// info!(root, "test info log"; "log-key" => true); +/// } +/// ``` +/// +/// Note that `"key" => value` part is optional: +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let root = slog::Logger::root( +/// drain, o!("key1" => "value1", "key2" => "value2") +/// ); +/// info!(root, "test info log"); +/// } +/// ``` +/// +/// ### Formatting support: +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let root = slog::Logger::root(drain, +/// o!("key1" => "value1", "key2" => "value2") +/// ); +/// info!(root, "formatted {num_entries} entries of {}", "something", num_entries = 2; "log-key" => true); +/// } +/// ``` +/// +/// Note: +/// +/// * `;` is used to separate message arguments and key value pairs. +/// * message behaves like `format!`/`format_args!` +/// * Named arguments to messages will be added to key-value pairs as well! +/// +/// `"key" => value` part is optional: +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let root = slog::Logger::root( +/// drain, o!("key1" => "value1", "key2" => "value2") +/// ); +/// info!(root, "formatted: {}", 1); +/// } +/// ``` +/// +/// Use formatting support wisely. Prefer named arguments, so the associated +/// data is not "lost" by becoming an untyped string in the message. +/// +/// ### Tags +/// +/// All above versions can be supplemented with a tag - string literal prefixed +/// with `#`. +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let root = slog::Logger::root(drain, +/// o!("key1" => "value1", "key2" => "value2") +/// ); +/// let ops = 3; +/// info!( +/// root, +/// #"performance-metric", "thread speed"; "ops_per_sec" => ops +/// ); +/// } +/// ``` +/// +/// See `Record::tag()` for more information about tags. +/// +/// ### Own implementations of `KV` and `Value` +/// +/// List of key value pairs is a comma separated list of key-values. Typically, +/// a designed syntax is used in form of `k => v` where `k` can be any type +/// that implements `Value` type. +/// +/// It's possible to directly specify type that implements `KV` trait without +/// `=>` syntax. +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// +/// use slog::*; +/// +/// fn main() { +/// struct MyKV; +/// struct MyV; +/// +/// impl KV for MyKV { +/// fn serialize(&self, +/// _record: &Record, +/// serializer: &mut Serializer) +/// -> Result { +/// serializer.emit_u32("MyK", 16) +/// } +/// } +/// +/// impl Value for MyV { +/// fn serialize(&self, +/// _record: &Record, +/// key : Key, +/// serializer: &mut Serializer) +/// -> Result { +/// serializer.emit_u32("MyKV", 16) +/// } +/// } +/// +/// let drain = slog::Discard; +/// +/// let root = slog::Logger::root(drain, o!(MyKV)); +/// +/// info!( +/// root, +/// "testing MyV"; "MyV" => MyV +/// ); +/// } +/// ``` +/// +/// ### `fmt::Display` and `fmt::Debug` values +/// +/// Value of any type that implements `std::fmt::Display` can be prefixed with +/// `%` in `k => v` expression to use its text representation returned by +/// `format_args!("{}", v)`. This is especially useful for errors. Not that +/// this does not allocate any `String` since it operates on `fmt::Arguments`. +/// You can also use the `#%` prefix to use the "alternate" form of formatting, +/// represented by the `{:#}` formatting specifier. +/// +/// Similarly to use `std::fmt::Debug` value can be prefixed with `?`, +/// or pretty-printed with `#?`. +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// use std::fmt::Write; +/// +/// fn main() { +/// let drain = slog::Discard; +/// let log = slog::Logger::root(drain, o!()); +/// +/// let mut output = String::new(); +/// +/// if let Err(e) = write!(&mut output, "write to string") { +/// error!(log, "write failed"; "err" => %e); +/// } +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! log( + // `2` means that `;` was already found + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr) => { + $crate::Logger::log(&$l, &record!($lvl, $tag, &__slog_builtin!(@format_args $msg_fmt, $($fmt)*), b!($($kv)*))) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr,) => { + log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr;) => { + log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $($args:tt)*) => { + log!(2 @ { $($fmt)* }, { $($kv)* $($args)*}, $l, $lvl, $tag, $msg_fmt) + }; + // `1` means that we are still looking for `;` + // -- handle named arguments to format string + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr) => { + log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr;) => { + log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr,) => { + log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr; $($args:tt)*) => { + log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr, $($args:tt)*) => { + log!(1 @ { $($fmt)* $k = $v, }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + // -- look for `;` termination + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr,) => { + log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr) => { + log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, ; $($args:tt)*) => { + log!(1 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt; $($args)*) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr; $($args:tt)*) => { + log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + // -- must be normal argument to format string + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $f:tt $($args:tt)*) => { + log!(1 @ { $($fmt)* $f }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + ($l:expr, $lvl:expr, $tag:expr, $($args:tt)*) => { + if $lvl.as_usize() <= $crate::__slog_static_max_level().as_usize() { + log!(1 @ { }, { }, $l, $lvl, $tag, $($args)*) + } + }; +); + +/// Log message a logging record (alias) +/// +/// Prefer [shorter version](macro.log.html), unless it clashes with +/// existing `log` crate macro. +/// +/// See [`log`](macro.log.html) for documentation. +/// +/// ``` +/// #[macro_use(slog_o,slog_b,slog_record,slog_record_static,slog_log,slog_info,slog_kv,__slog_builtin)] +/// extern crate slog; +/// +/// fn main() { +/// let log = slog::Logger::root(slog::Discard, slog_o!()); +/// +/// slog_info!(log, "some interesting info"; "where" => "right here"); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! slog_log( + // `2` means that `;` was already found + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr) => { + $crate::Logger::log(&$l, &slog_record!($lvl, $tag, &__slog_builtin!(@format_args $msg_fmt, $($fmt)*), slog_b!($($kv)*))) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr,) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr;) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (2 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $($args:tt)*) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* $($args)*}, $l, $lvl, $tag, $msg_fmt) + }; + // `1` means that we are still looking for `;` + // -- handle named arguments to format string + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr) => { + slog_log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr;) => { + slog_log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr,) => { + slog_log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr; $($args:tt)*) => { + slog_log!(2 @ { $($fmt)* $k = $v }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $k:ident = $v:expr, $($args:tt)*) => { + slog_log!(1 @ { $($fmt)* $k = $v, }, { $($kv)* __slog_builtin!(@stringify $k) => $v, }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + // -- look for `;` termination + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr,) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, ; $($args:tt)*) => { + slog_log!(1 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt; $($args)*) + }; + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr; $($args:tt)*) => { + slog_log!(2 @ { $($fmt)* }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + // -- must be normal argument to format string + (1 @ { $($fmt:tt)* }, { $($kv:tt)* }, $l:expr, $lvl:expr, $tag:expr, $msg_fmt:expr, $f:tt $($args:tt)*) => { + slog_log!(1 @ { $($fmt)* $f }, { $($kv)* }, $l, $lvl, $tag, $msg_fmt, $($args)*) + }; + ($l:expr, $lvl:expr, $tag:expr, $($args:tt)*) => { + if $lvl.as_usize() <= $crate::__slog_static_max_level().as_usize() { + slog_log!(1 @ { }, { }, $l, $lvl, $tag, $($args)*) + } + }; +); +/// Log critical level record +/// +/// See `log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! crit( + ($l:expr, #$tag:expr, $($args:tt)+) => { + log!($l, $crate::Level::Critical, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + log!($l, $crate::Level::Critical, "", $($args)+) + }; +); + +/// Log critical level record (alias) +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_crit( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Critical, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Critical, "", $($args)+) + }; +); + +/// Log error level record +/// +/// See `log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! error( + ($l:expr, #$tag:expr, $($args:tt)+) => { + log!($l, $crate::Level::Error, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + log!($l, $crate::Level::Error, "", $($args)+) + }; +); + +/// Log error level record +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_error( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Error, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Error, "", $($args)+) + }; +); + +/// Log warning level record +/// +/// See `log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! warn( + ($l:expr, #$tag:expr, $($args:tt)+) => { + log!($l, $crate::Level::Warning, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + log!($l, $crate::Level::Warning, "", $($args)+) + }; +); + +/// Log warning level record (alias) +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_warn( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Warning, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Warning, "", $($args)+) + }; +); + +/// Log info level record +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! info( + ($l:expr, #$tag:expr, $($args:tt)*) => { + log!($l, $crate::Level::Info, $tag, $($args)*) + }; + ($l:expr, $($args:tt)*) => { + log!($l, $crate::Level::Info, "", $($args)*) + }; +); + +/// Log info level record (alias) +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_info( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Info, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Info, "", $($args)+) + }; +); + +/// Log debug level record +/// +/// See `log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! debug( + ($l:expr, #$tag:expr, $($args:tt)+) => { + log!($l, $crate::Level::Debug, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + log!($l, $crate::Level::Debug, "", $($args)+) + }; +); + +/// Log debug level record (alias) +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_debug( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Debug, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Debug, "", $($args)+) + }; +); + +/// Log trace level record +/// +/// See `log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! trace( + ($l:expr, #$tag:expr, $($args:tt)+) => { + log!($l, $crate::Level::Trace, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + log!($l, $crate::Level::Trace, "", $($args)+) + }; +); + +/// Log trace level record (alias) +/// +/// Prefer shorter version, unless it clashes with +/// existing `log` crate macro. +/// +/// See `slog_log` for documentation. +#[macro_export(local_inner_macros)] +macro_rules! slog_trace( + ($l:expr, #$tag:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Trace, $tag, $($args)+) + }; + ($l:expr, $($args:tt)+) => { + slog_log!($l, $crate::Level::Trace, "", $($args)+) + }; + ($($args:tt)+) => { + slog_log!($crate::Level::Trace, $($args)+) + }; +); + +/// Helper macro for using the built-in macros inside of +/// exposed macros with `local_inner_macros` attribute. +#[doc(hidden)] +#[macro_export] +macro_rules! __slog_builtin { + (@format_args $($t:tt)*) => ( format_args!($($t)*) ); + (@stringify $($t:tt)*) => ( stringify!($($t)*) ); + (@file) => ( file!() ); + (@line) => ( line!() ); + (@column) => ( column!() ); + (@module_path) => ( module_path!() ); +} + +// }}} + +// {{{ Logger +/// Logging handle used to execute logging statements +/// +/// In an essence `Logger` instance holds two pieces of information: +/// +/// * drain - destination where to forward logging `Record`s for +/// processing. +/// * context - list of key-value pairs associated with it. +/// +/// The root `Logger` is created with a `Drain` that will be cloned to every +/// member of its hierarchy. +/// +/// Child `Logger`s are built from existing ones, and inherit their key-value +/// pairs, which can be supplemented with additional pairs. +/// +/// Cloning existing loggers and creating new ones is cheap. Loggers can be +/// freely passed around the code and between threads. +/// +/// `Logger`s are `Sync+Send` - there's no need to synchronize accesses to them, +/// as they can accept logging records from multiple threads at once. They can +/// be sent to any thread. Because of that they require the `Drain` to be +/// `Sync+Send` as well. Not all `Drain`s are `Sync` or `Send` but they can +/// often be made so by wrapping in a `Mutex` and/or `Arc`. +/// +/// `Logger` implements `Drain` trait. Any logging `Record` delivered to +/// a `Logger` functioning as a `Drain` will be delivered to its `Drain` +/// with existing key-value pairs appended to the `Logger`'s key-value pairs. +/// By itself, it is effectively very similar to `Logger` being an ancestor +/// of `Logger` that originated the logging `Record`. Combined with other +/// `Drain`s, this allows custom processing logic for a sub-tree of a whole logging +/// tree. +/// +/// Logger is parametrized over type of a `Drain` associated with it (`D`). It +/// default to type-erased version so `Logger` without any type annotation +/// means `Logger<Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>>`. See +/// `Logger::root_typed` and `Logger::to_erased` for more information. +#[derive(Clone)] +pub struct Logger<D = Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>> +where + D: SendSyncUnwindSafeDrain<Ok = (), Err = Never>, +{ + drain: D, + list: OwnedKVList, +} + +impl<D> Logger<D> +where + D: SendSyncUnwindSafeDrain<Ok = (), Err = Never>, +{ + /// Build a root `Logger` + /// + /// Root logger starts a new tree associated with a given `Drain`. Root + /// logger drain must return no errors. See `Drain::ignore_res()` and + /// `Drain::fuse()`. + /// + /// All children and their children (and so on), form one logging tree + /// sharing a common drain. See `Logger::new`. + /// + /// This version (as opposed to `Logger:root_typed`) will take `drain` and + /// made it into `Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>`. + /// This is typically the most convenient way to work with `Logger`s. + /// + /// Use `o!` macro to build `OwnedKV` object. + /// + /// ``` + /// #[macro_use] + /// extern crate slog; + /// + /// fn main() { + /// let _root = slog::Logger::root( + /// slog::Discard, + /// o!("key1" => "value1", "key2" => "value2"), + /// ); + /// } + /// ``` + pub fn root<T>(drain: D, values: OwnedKV<T>) -> Logger + where + D: 'static + SendSyncRefUnwindSafeDrain<Err = Never, Ok = ()>, + T: SendSyncRefUnwindSafeKV + 'static, + { + Logger { + drain: Arc::new(drain) + as Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>, + list: OwnedKVList::root(values), + } + } + + /// Build a root `Logger` that retains `drain` type + /// + /// Unlike `Logger::root`, this constructor retains the type of a `drain`, + /// which allows highest performance possible by eliminating indirect call + /// on `Drain::log`, and allowing monomorphization of `Logger` and `Drain` + /// objects. + /// + /// If you don't understand the implications, you should probably just + /// ignore it. + /// + /// See `Logger:into_erased` and `Logger::to_erased` for conversion from + /// type returned by this function to version that would be returned by + /// `Logger::root`. + pub fn root_typed<T>(drain: D, values: OwnedKV<T>) -> Logger<D> + where + D: 'static + SendSyncUnwindSafeDrain<Err = Never, Ok = ()> + Sized, + T: SendSyncRefUnwindSafeKV + 'static, + { + Logger { + drain: drain, + list: OwnedKVList::root(values), + } + } + + /// Build a child logger + /// + /// Child logger inherits all existing key-value pairs from its parent and + /// supplements them with additional ones. + /// + /// Use `o!` macro to build `OwnedKV` object. + /// + /// ### Drain cloning (`D : Clone` requirement) + /// + /// All children, their children and so on, form one tree sharing a + /// common drain. This drain, will be `Clone`d when this method is called. + /// That is why `Clone` must be implemented for `D` in `Logger<D>::new`. + /// + /// For some `Drain` types `Clone` is cheap or even free (a no-op). This is + /// the case for any `Logger` returned by `Logger::root` and its children. + /// + /// When using `Logger::root_typed`, it's possible that cloning might be + /// expensive, or even impossible. + /// + /// The reason why wrapping in an `Arc` is not done internally, and exposed + /// to the user is performance. Calling `Drain::log` through an `Arc` is + /// tiny bit slower than doing it directly. + /// + /// ``` + /// #[macro_use] + /// extern crate slog; + /// + /// fn main() { + /// let root = slog::Logger::root(slog::Discard, + /// o!("key1" => "value1", "key2" => "value2")); + /// let _log = root.new(o!("key" => "value")); + /// } + #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] + pub fn new<T>(&self, values: OwnedKV<T>) -> Logger<D> + where + T: SendSyncRefUnwindSafeKV + 'static, + D: Clone, + { + Logger { + drain: self.drain.clone(), + list: OwnedKVList::new(values, self.list.node.clone()), + } + } + + /// Log one logging `Record` + /// + /// Use specific logging functions instead. See `log!` macro + /// documentation. + #[inline] + pub fn log(&self, record: &Record) { + let _ = self.drain.log(record, &self.list); + } + + /// Get list of key-value pairs assigned to this `Logger` + pub fn list(&self) -> &OwnedKVList { + &self.list + } + + /// Convert to default, "erased" type: + /// `Logger<Arc<SendSyncUnwindSafeDrain>>` + /// + /// Useful to adapt `Logger<D : Clone>` to an interface expecting + /// `Logger<Arc<...>>`. + /// + /// Note that calling on a `Logger<Arc<...>>` will convert it to + /// `Logger<Arc<Arc<...>>>` which is not optimal. This might be fixed when + /// Rust gains trait implementation specialization. + pub fn into_erased( + self, + ) -> Logger<Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>> + where + D: SendRefUnwindSafeDrain + 'static, + { + Logger { + drain: Arc::new(self.drain) + as Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>, + list: self.list, + } + } + + /// Create a copy with "erased" type + /// + /// See `into_erased` + pub fn to_erased( + &self, + ) -> Logger<Arc<SendSyncRefUnwindSafeDrain<Ok = (), Err = Never>>> + where + D: SendRefUnwindSafeDrain + 'static + Clone, + { + self.clone().into_erased() + } +} + +impl<D> fmt::Debug for Logger<D> +where + D: SendSyncUnwindSafeDrain<Ok = (), Err = Never>, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "Logger{:?}", self.list)); + Ok(()) + } +} + +impl<D> Drain for Logger<D> +where + D: SendSyncUnwindSafeDrain<Ok = (), Err = Never>, +{ + type Ok = (); + type Err = Never; + + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + let chained = OwnedKVList { + node: Arc::new(MultiListNode { + next_node: values.node.clone(), + node: self.list.node.clone(), + }), + }; + self.drain.log(record, &chained) + } + + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.drain.is_enabled(level) + } +} + +// {{{ Drain +/// Logging drain +/// +/// `Drain`s typically mean destination for logs, but `slog` generalizes the +/// term. +/// +/// `Drain`s are responsible for handling logging statements (`Record`s) from +/// `Logger`s associated with them: filtering, modifying, formatting +/// and writing the log records into given destination(s). +/// +/// It's a typical pattern to parametrize `Drain`s over `Drain` traits to allow +/// composing `Drain`s. +/// +/// Implementing this trait allows writing custom `Drain`s. Slog users should +/// not be afraid of implementing their own `Drain`s. Any custom log handling +/// logic should be implemented as a `Drain`. +pub trait Drain { + /// Type returned by this drain + /// + /// It can be useful in some circumstances, but rarely. It will probably + /// default to `()` once https://github.com/rust-lang/rust/issues/29661 is + /// stable. + type Ok; + /// Type of potential errors that can be returned by this `Drain` + type Err; + /// Handle one logging statement (`Record`) + /// + /// Every logging `Record` built from a logging statement (eg. + /// `info!(...)`), and key-value lists of a `Logger` it was executed on + /// will be passed to the root drain registered during `Logger::root`. + /// + /// Typically `Drain`s: + /// + /// * pass this information (or not) to the sub-logger(s) (filters) + /// * format and write the information to a destination (writers) + /// * deal with the errors returned from the sub-logger(s) + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err>; + + /// **Avoid**: Check if messages at the specified log level are **maybe** + /// enabled for this logger. + /// + /// The purpose of it so to allow **imprecise** detection if a given logging + /// level has any chance of actually being logged. This might be used + /// to explicitly skip needless computation. + /// + /// **It is best effort, can return false positives, but not false negatives.** + /// + /// The logger is still free to ignore records even if the level is enabled, + /// so an enabled level doesn't necessarily guarantee that the record will + /// actually be logged. + /// + /// This function is somewhat needless, and is better expressed by using + /// lazy values (see `FnValue`). A `FnValue` is more precise and does not + /// require additional (potentially recursive) calls to do something that + /// `log` will already do anyways (making decision if something should be + /// logged or not). + /// + /// ``` + /// # #[macro_use] + /// # extern crate slog; + /// # use slog::*; + /// # fn main() { + /// let logger = Logger::root(Discard, o!()); + /// if logger.is_enabled(Level::Debug) { + /// let num = 5.0f64; + /// let sqrt = num.sqrt(); + /// debug!(logger, "Sqrt"; "num" => num, "sqrt" => sqrt); + /// } + /// # } + /// ``` + #[inline] + fn is_enabled(&self, level: Level) -> bool { + level.as_usize() <= ::__slog_static_max_level().as_usize() + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_critical_enabled(&self) -> bool { + self.is_enabled(Level::Critical) + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_error_enabled(&self) -> bool { + self.is_enabled(Level::Error) + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_warning_enabled(&self) -> bool { + self.is_enabled(Level::Warning) + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_info_enabled(&self) -> bool { + self.is_enabled(Level::Info) + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_debug_enabled(&self) -> bool { + self.is_enabled(Level::Debug) + } + + /// **Avoid**: See `is_enabled` + #[inline] + fn is_trace_enabled(&self) -> bool { + self.is_enabled(Level::Trace) + } + + /// Pass `Drain` through a closure, eg. to wrap + /// into another `Drain`. + /// + /// ``` + /// #[macro_use] + /// extern crate slog; + /// use slog::*; + /// + /// fn main() { + /// let _drain = Discard.map(Fuse); + /// } + /// ``` + fn map<F, R>(self, f: F) -> R + where + Self: Sized, + F: FnOnce(Self) -> R, + { + f(self) + } + + /// Filter logging records passed to `Drain` + /// + /// Wrap `Self` in `Filter` + /// + /// This will convert `self` to a `Drain` that ignores `Record`s + /// for which `f` returns false. + fn filter<F>(self, f: F) -> Filter<Self, F> + where + Self: Sized, + F: FilterFn, + { + Filter::new(self, f) + } + + /// Filter logging records passed to `Drain` (by level) + /// + /// Wrap `Self` in `LevelFilter` + /// + /// This will convert `self` to a `Drain` that ignores `Record`s of + /// logging lever smaller than `level`. + fn filter_level(self, level: Level) -> LevelFilter<Self> + where + Self: Sized, + { + LevelFilter(self, level) + } + + /// Map logging errors returned by this drain + /// + /// `f` is a closure that takes `Drain::Err` returned by a given + /// drain, and returns new error of potentially different type + fn map_err<F, E>(self, f: F) -> MapError<Self, E> + where + Self: Sized, + F: MapErrFn<Self::Err, E>, + { + MapError::new(self, f) + } + + /// Ignore results returned by this drain + /// + /// Wrap `Self` in `IgnoreResult` + fn ignore_res(self) -> IgnoreResult<Self> + where + Self: Sized, + { + IgnoreResult::new(self) + } + + /// Make `Self` panic when returning any errors + /// + /// Wrap `Self` in `Map` + fn fuse(self) -> Fuse<Self> + where + Self::Err: fmt::Debug, + Self: Sized, + { + self.map(Fuse) + } +} + +impl<'a, D: Drain + 'a> Drain for &'a D { + type Ok = D::Ok; + type Err = D::Err; + #[inline] + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + (**self).log(record, values) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + (**self).is_enabled(level) + } +} + +impl<'a, D: Drain + 'a> Drain for &'a mut D { + type Ok = D::Ok; + type Err = D::Err; + #[inline] + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + (**self).log(record, values) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + (**self).is_enabled(level) + } +} + +#[cfg(feature = "std")] +/// `Send + Sync + UnwindSafe` bound +/// +/// This type is used to enforce `Drain`s associated with `Logger`s +/// are thread-safe. +pub trait SendSyncUnwindSafe: Send + Sync + UnwindSafe {} + +#[cfg(feature = "std")] +impl<T> SendSyncUnwindSafe for T +where + T: Send + Sync + UnwindSafe + ?Sized, +{ +} + +#[cfg(feature = "std")] +/// `Drain + Send + Sync + UnwindSafe` bound +/// +/// This type is used to enforce `Drain`s associated with `Logger`s +/// are thread-safe. +pub trait SendSyncUnwindSafeDrain: Drain + Send + Sync + UnwindSafe {} + +#[cfg(feature = "std")] +impl<T> SendSyncUnwindSafeDrain for T +where + T: Drain + Send + Sync + UnwindSafe + ?Sized, +{ +} + +#[cfg(feature = "std")] +/// `Drain + Send + Sync + RefUnwindSafe` bound +/// +/// This type is used to enforce `Drain`s associated with `Logger`s +/// are thread-safe. +pub trait SendSyncRefUnwindSafeDrain: Drain + Send + Sync + RefUnwindSafe {} + +#[cfg(feature = "std")] +impl<T> SendSyncRefUnwindSafeDrain for T +where + T: Drain + Send + Sync + RefUnwindSafe + ?Sized, +{ +} + +#[cfg(feature = "std")] +/// Function that can be used in `MapErr` drain +pub trait MapErrFn<EI, EO> + : 'static + Sync + Send + UnwindSafe + RefUnwindSafe + Fn(EI) -> EO { +} + +#[cfg(feature = "std")] +impl<T, EI, EO> MapErrFn<EI, EO> for T +where + T: 'static + + Sync + + Send + + ?Sized + + UnwindSafe + + RefUnwindSafe + + Fn(EI) -> EO, +{ +} + +#[cfg(feature = "std")] +/// Function that can be used in `Filter` drain +pub trait FilterFn + : 'static + Sync + Send + UnwindSafe + RefUnwindSafe + Fn(&Record) -> bool { +} + +#[cfg(feature = "std")] +impl<T> FilterFn for T +where + T: 'static + + Sync + + Send + + ?Sized + + UnwindSafe + + RefUnwindSafe + + Fn(&Record) -> bool, +{ +} + +#[cfg(not(feature = "std"))] +/// `Drain + Send + Sync + UnwindSafe` bound +/// +/// This type is used to enforce `Drain`s associated with `Logger`s +/// are thread-safe. +pub trait SendSyncUnwindSafeDrain: Drain + Send + Sync {} + +#[cfg(not(feature = "std"))] +impl<T> SendSyncUnwindSafeDrain for T +where + T: Drain + Send + Sync + ?Sized, +{ +} + +#[cfg(not(feature = "std"))] +/// `Drain + Send + Sync + RefUnwindSafe` bound +/// +/// This type is used to enforce `Drain`s associated with `Logger`s +/// are thread-safe. +pub trait SendSyncRefUnwindSafeDrain: Drain + Send + Sync {} + +#[cfg(not(feature = "std"))] +impl<T> SendSyncRefUnwindSafeDrain for T +where + T: Drain + Send + Sync + ?Sized, +{ +} + +#[cfg(feature = "std")] +/// `Drain + Send + RefUnwindSafe` bound +pub trait SendRefUnwindSafeDrain: Drain + Send + RefUnwindSafe {} + +#[cfg(feature = "std")] +impl<T> SendRefUnwindSafeDrain for T +where + T: Drain + Send + RefUnwindSafe + ?Sized, +{ +} + +#[cfg(not(feature = "std"))] +/// `Drain + Send + RefUnwindSafe` bound +pub trait SendRefUnwindSafeDrain: Drain + Send {} + +#[cfg(not(feature = "std"))] +impl<T> SendRefUnwindSafeDrain for T +where + T: Drain + Send + ?Sized, +{ +} + +#[cfg(not(feature = "std"))] +/// Function that can be used in `MapErr` drain +pub trait MapErrFn<EI, EO>: 'static + Sync + Send + Fn(EI) -> EO {} + +#[cfg(not(feature = "std"))] +impl<T, EI, EO> MapErrFn<EI, EO> for T +where + T: 'static + Sync + Send + ?Sized + Fn(EI) -> EO, +{ +} + +#[cfg(not(feature = "std"))] +/// Function that can be used in `Filter` drain +pub trait FilterFn: 'static + Sync + Send + Fn(&Record) -> bool {} + +#[cfg(not(feature = "std"))] +impl<T> FilterFn for T +where + T: 'static + Sync + Send + ?Sized + Fn(&Record) -> bool, +{ +} + +impl<D: Drain + ?Sized> Drain for Box<D> { + type Ok = D::Ok; + type Err = D::Err; + fn log( + &self, + record: &Record, + o: &OwnedKVList, + ) -> result::Result<Self::Ok, D::Err> { + (**self).log(record, o) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + (**self).is_enabled(level) + } +} + +impl<D: Drain + ?Sized> Drain for Arc<D> { + type Ok = D::Ok; + type Err = D::Err; + fn log( + &self, + record: &Record, + o: &OwnedKVList, + ) -> result::Result<Self::Ok, D::Err> { + (**self).log(record, o) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + (**self).is_enabled(level) + } +} + +/// `Drain` discarding everything +/// +/// `/dev/null` of `Drain`s +#[derive(Debug, Copy, Clone)] +pub struct Discard; + +impl Drain for Discard { + type Ok = (); + type Err = Never; + fn log(&self, _: &Record, _: &OwnedKVList) -> result::Result<(), Never> { + Ok(()) + } + #[inline] + fn is_enabled(&self, _1: Level) -> bool { + false + } +} + +/// `Drain` filtering records +/// +/// Wraps another `Drain` and passes `Record`s to it, only if they satisfy a +/// given condition. +#[derive(Debug, Clone)] +pub struct Filter<D: Drain, F>(pub D, pub F) +where + F: Fn(&Record) -> bool + 'static + Send + Sync; + +impl<D: Drain, F> Filter<D, F> +where + F: FilterFn, +{ + /// Create `Filter` wrapping given `drain` + pub fn new(drain: D, cond: F) -> Self { + Filter(drain, cond) + } +} + +impl<D: Drain, F> Drain for Filter<D, F> +where + F: FilterFn, +{ + type Ok = Option<D::Ok>; + type Err = D::Err; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + if (self.1)(record) { + Ok(Some(self.0.log(record, logger_values)?)) + } else { + Ok(None) + } + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + /* + * This is one of the reasons we can't guarantee the value is actually logged. + * The filter function is given dynamic control over whether or not the record is logged + * and could filter stuff out even if the log level is supposed to be enabled + */ + self.0.is_enabled(level) + } +} + +/// `Drain` filtering records by `Record` logging level +/// +/// Wraps a drain and passes records to it, only +/// if their level is at least given level. +/// +/// TODO: Remove this type. This drain is a special case of `Filter`, but +/// because `Filter` can not use static dispatch ATM due to Rust limitations +/// that will be lifted in the future, it is a standalone type. +/// Reference: https://github.com/rust-lang/rust/issues/34511 +#[derive(Debug, Clone)] +pub struct LevelFilter<D: Drain>(pub D, pub Level); + +impl<D: Drain> LevelFilter<D> { + /// Create `LevelFilter` + pub fn new(drain: D, level: Level) -> Self { + LevelFilter(drain, level) + } +} + +impl<D: Drain> Drain for LevelFilter<D> { + type Ok = Option<D::Ok>; + type Err = D::Err; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + if record.level().is_at_least(self.1) { + Ok(Some(self.0.log(record, logger_values)?)) + } else { + Ok(None) + } + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + level.is_at_least(self.1) && self.0.is_enabled(level) + } +} + +/// `Drain` mapping error returned by another `Drain` +/// +/// See `Drain::map_err` for convenience function. +pub struct MapError<D: Drain, E> { + drain: D, + // eliminated dynamic dispatch, after rust learns `-> impl Trait` + map_fn: Box<MapErrFn<D::Err, E, Output = E>>, +} + +impl<D: Drain, E> MapError<D, E> { + /// Create `Filter` wrapping given `drain` + pub fn new<F>(drain: D, map_fn: F) -> Self + where + F: MapErrFn<<D as Drain>::Err, E>, + { + MapError { + drain: drain, + map_fn: Box::new(map_fn), + } + } +} + +impl<D: Drain, E> Drain for MapError<D, E> { + type Ok = D::Ok; + type Err = E; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + self.drain + .log(record, logger_values) + .map_err(|e| (self.map_fn)(e)) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.drain.is_enabled(level) + } +} + +/// `Drain` duplicating records into two other `Drain`s +/// +/// Can be nested for more than two outputs. +#[derive(Debug, Clone)] +pub struct Duplicate<D1: Drain, D2: Drain>(pub D1, pub D2); + +impl<D1: Drain, D2: Drain> Duplicate<D1, D2> { + /// Create `Duplicate` + pub fn new(drain1: D1, drain2: D2) -> Self { + Duplicate(drain1, drain2) + } +} + +impl<D1: Drain, D2: Drain> Drain for Duplicate<D1, D2> { + type Ok = (D1::Ok, D2::Ok); + type Err = ( + result::Result<D1::Ok, D1::Err>, + result::Result<D2::Ok, D2::Err>, + ); + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + let res1 = self.0.log(record, logger_values); + let res2 = self.1.log(record, logger_values); + + match (res1, res2) { + (Ok(o1), Ok(o2)) => Ok((o1, o2)), + (r1, r2) => Err((r1, r2)), + } + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.0.is_enabled(level) || self.1.is_enabled(level) + } +} + +/// `Drain` panicking on error +/// +/// `Logger` requires a root drain to handle all errors (`Drain::Error == ()`), +/// `Fuse` will wrap a `Drain` and panic if it returns any errors. +/// +/// Note: `Drain::Err` must implement `Display` (for displaying on panic). It's +/// easy to create your own `Fuse` drain if this requirement can't be fulfilled. +#[derive(Debug, Clone)] +pub struct Fuse<D: Drain>(pub D) +where + D::Err: fmt::Debug; + +impl<D: Drain> Fuse<D> +where + D::Err: fmt::Debug, +{ + /// Create `Fuse` wrapping given `drain` + pub fn new(drain: D) -> Self { + Fuse(drain) + } +} + +impl<D: Drain> Drain for Fuse<D> +where + D::Err: fmt::Debug, +{ + type Ok = (); + type Err = Never; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Never> { + let _ = self.0 + .log(record, logger_values) + .unwrap_or_else(|e| panic!("slog::Fuse Drain: {:?}", e)); + Ok(()) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.0.is_enabled(level) + } +} + +/// `Drain` ignoring result +/// +/// `Logger` requires a root drain to handle all errors (`Drain::Err=()`), and +/// returns nothing (`Drain::Ok=()`) `IgnoreResult` will ignore any result +/// returned by the `Drain` it wraps. +#[derive(Clone)] +pub struct IgnoreResult<D: Drain> { + drain: D, +} + +impl<D: Drain> IgnoreResult<D> { + /// Create `IgnoreResult` wrapping `drain` + pub fn new(drain: D) -> Self { + IgnoreResult { drain: drain } + } +} + +impl<D: Drain> Drain for IgnoreResult<D> { + type Ok = (); + type Err = Never; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<(), Never> { + let _ = self.drain.log(record, logger_values); + Ok(()) + } + + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.drain.is_enabled(level) + } +} + +/// Error returned by `Mutex<D : Drain>` +#[cfg(feature = "std")] +#[derive(Clone)] +pub enum MutexDrainError<D: Drain> { + /// Error acquiring mutex + Mutex, + /// Error returned by drain + Drain(D::Err), +} + +#[cfg(feature = "std")] +impl<D> fmt::Debug for MutexDrainError<D> +where + D: Drain, + D::Err: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + match *self { + MutexDrainError::Mutex => write!(f, "MutexDrainError::Mutex"), + MutexDrainError::Drain(ref e) => e.fmt(f), + } + } +} + +#[cfg(feature = "std")] +impl<D> std::error::Error for MutexDrainError<D> +where + D: Drain, + D::Err: fmt::Debug + fmt::Display + std::error::Error, +{ + fn description(&self) -> &str { + match *self { + MutexDrainError::Mutex => "Mutex acquire failed", + MutexDrainError::Drain(ref e) => e.description(), + } + } + + fn cause(&self) -> Option<&std::error::Error> { + match *self { + MutexDrainError::Mutex => None, + MutexDrainError::Drain(ref e) => Some(e), + } + } +} + +#[cfg(feature = "std")] +impl<'a, D: Drain> From<std::sync::PoisonError<std::sync::MutexGuard<'a, D>>> + for MutexDrainError<D> { + fn from( + _: std::sync::PoisonError<std::sync::MutexGuard<'a, D>>, + ) -> MutexDrainError<D> { + MutexDrainError::Mutex + } +} + +#[cfg(feature = "std")] +impl<D: Drain> fmt::Display for MutexDrainError<D> +where + D::Err: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + match *self { + MutexDrainError::Mutex => write!(f, "MutexError"), + MutexDrainError::Drain(ref e) => write!(f, "{}", e), + } + } +} + +#[cfg(feature = "std")] +impl<D: Drain> Drain for std::sync::Mutex<D> { + type Ok = D::Ok; + type Err = MutexDrainError<D>; + fn log( + &self, + record: &Record, + logger_values: &OwnedKVList, + ) -> result::Result<Self::Ok, Self::Err> { + let d = self.lock()?; + d.log(record, logger_values).map_err(MutexDrainError::Drain) + } + #[inline] + fn is_enabled(&self, level: Level) -> bool { + self.lock().ok().map_or(true, |lock| lock.is_enabled(level)) + } +} +// }}} + +// {{{ Level & FilterLevel +/// Official capitalized logging (and logging filtering) level names +/// +/// In order of `as_usize()`. +pub static LOG_LEVEL_NAMES: [&'static str; 7] = + ["OFF", "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"]; + +/// Official capitalized logging (and logging filtering) short level names +/// +/// In order of `as_usize()`. +pub static LOG_LEVEL_SHORT_NAMES: [&'static str; 7] = + ["OFF", "CRIT", "ERRO", "WARN", "INFO", "DEBG", "TRCE"]; + +/// Logging level associated with a logging `Record` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum Level { + /// Critical + Critical, + /// Error + Error, + /// Warning + Warning, + /// Info + Info, + /// Debug + Debug, + /// Trace + Trace, +} + +/// Logging filtering level +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum FilterLevel { + /// Log nothing + Off, + /// Log critical level only + Critical, + /// Log only error level and above + Error, + /// Log only warning level and above + Warning, + /// Log only info level and above + Info, + /// Log only debug level and above + Debug, + /// Log everything + Trace, +} + +impl Level { + /// Convert to `str` from `LOG_LEVEL_SHORT_NAMES` + pub fn as_short_str(&self) -> &'static str { + LOG_LEVEL_SHORT_NAMES[self.as_usize()] + } + + /// Convert to `str` from `LOG_LEVEL_NAMES` + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[self.as_usize()] + } + + /// Cast `Level` to ordering integer + /// + /// `Critical` is the smallest and `Trace` the biggest value + #[inline] + pub fn as_usize(&self) -> usize { + match *self { + Level::Critical => 1, + Level::Error => 2, + Level::Warning => 3, + Level::Info => 4, + Level::Debug => 5, + Level::Trace => 6, + } + } + + /// Get a `Level` from an `usize` + /// + /// This complements `as_usize` + #[inline] + pub fn from_usize(u: usize) -> Option<Level> { + match u { + 1 => Some(Level::Critical), + 2 => Some(Level::Error), + 3 => Some(Level::Warning), + 4 => Some(Level::Info), + 5 => Some(Level::Debug), + 6 => Some(Level::Trace), + _ => None, + } + } +} + +impl FilterLevel { + /// Convert to `str` from `LOG_LEVEL_SHORT_NAMES` + pub fn as_short_str(&self) -> &'static str { + LOG_LEVEL_SHORT_NAMES[self.as_usize()] + } + + /// Convert to `str` from `LOG_LEVEL_NAMES` + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[self.as_usize()] + } + + /// Convert to `usize` value + /// + /// `Off` is 0, and `Trace` 6 + #[inline] + pub fn as_usize(&self) -> usize { + match *self { + FilterLevel::Off => 0, + FilterLevel::Critical => 1, + FilterLevel::Error => 2, + FilterLevel::Warning => 3, + FilterLevel::Info => 4, + FilterLevel::Debug => 5, + FilterLevel::Trace => 6, + } + } + + /// Get a `FilterLevel` from an `usize` + /// + /// This complements `as_usize` + #[inline] + pub fn from_usize(u: usize) -> Option<FilterLevel> { + match u { + 0 => Some(FilterLevel::Off), + 1 => Some(FilterLevel::Critical), + 2 => Some(FilterLevel::Error), + 3 => Some(FilterLevel::Warning), + 4 => Some(FilterLevel::Info), + 5 => Some(FilterLevel::Debug), + 6 => Some(FilterLevel::Trace), + _ => None, + } + } + + /// Maximum logging level (log everything) + #[inline] + pub fn max() -> Self { + FilterLevel::Trace + } + + /// Minimum logging level (log nothing) + #[inline] + pub fn min() -> Self { + FilterLevel::Off + } + + /// Check if message with given level should be logged + pub fn accepts(self, level: Level) -> bool { + self.as_usize() >= level.as_usize() + } +} + +impl FromStr for Level { + type Err = (); + fn from_str(name: &str) -> core::result::Result<Level, ()> { + index_of_log_level_name(name) + .and_then(|idx| Level::from_usize(idx)) + .ok_or(()) + } +} + +impl FromStr for FilterLevel { + type Err = (); + fn from_str(name: &str) -> core::result::Result<FilterLevel, ()> { + index_of_log_level_name(name) + .and_then(|idx| FilterLevel::from_usize(idx)) + .ok_or(()) + } +} + +fn index_of_log_level_name(name: &str) -> Option<usize> { + index_of_str_ignore_case(&LOG_LEVEL_NAMES, name) + .or_else(|| index_of_str_ignore_case(&LOG_LEVEL_SHORT_NAMES, name)) +} + +fn index_of_str_ignore_case(haystack: &[&str], needle: &str) -> Option<usize> { + if needle.is_empty() { + return None; + } + haystack.iter() + // This will never panic because haystack has only ASCII characters + .map(|hay| &hay[..needle.len().min(hay.len())]) + .position(|hay| hay.eq_ignore_ascii_case(needle)) +} + +impl fmt::Display for Level { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_short_str()) + } +} + +impl fmt::Display for FilterLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_short_str()) + } +} + +impl Level { + /// Returns true if `self` is at least `level` logging level + #[inline] + pub fn is_at_least(&self, level: Self) -> bool { + self.as_usize() <= level.as_usize() + } +} + +#[test] +fn level_at_least() { + assert!(Level::Debug.is_at_least(Level::Debug)); + assert!(Level::Debug.is_at_least(Level::Trace)); + assert!(!Level::Debug.is_at_least(Level::Info)); +} + +#[test] +fn filter_level_sanity() { + assert!(Level::Critical.as_usize() > FilterLevel::Off.as_usize()); + assert!(Level::Critical.as_usize() == FilterLevel::Critical.as_usize()); + assert!(Level::Trace.as_usize() == FilterLevel::Trace.as_usize()); +} + +#[test] +fn level_from_str() { + refute_from_str::<Level>("off"); + assert_from_str(Level::Critical, "critical"); + assert_from_str(Level::Critical, "crit"); + assert_from_str(Level::Error, "error"); + assert_from_str(Level::Error, "erro"); + assert_from_str(Level::Warning, "warn"); + assert_from_str(Level::Info, "info"); + assert_from_str(Level::Debug, "debug"); + assert_from_str(Level::Debug, "debg"); + assert_from_str(Level::Trace, "trace"); + assert_from_str(Level::Trace, "trce"); + + assert_from_str(Level::Info, "Info"); + assert_from_str(Level::Info, "INFO"); + assert_from_str(Level::Info, "iNfO"); + + refute_from_str::<Level>(""); + assert_from_str(Level::Info, "i"); + assert_from_str(Level::Info, "in"); + assert_from_str(Level::Info, "inf"); + refute_from_str::<Level>("infor"); + + refute_from_str::<Level>("?"); + refute_from_str::<Level>("info "); + refute_from_str::<Level>(" info"); + refute_from_str::<Level>("desinfo"); +} + +#[test] +fn filter_level_from_str() { + assert_from_str(FilterLevel::Off, "off"); + assert_from_str(FilterLevel::Critical, "critical"); + assert_from_str(FilterLevel::Critical, "crit"); + assert_from_str(FilterLevel::Error, "error"); + assert_from_str(FilterLevel::Error, "erro"); + assert_from_str(FilterLevel::Warning, "warn"); + assert_from_str(FilterLevel::Info, "info"); + assert_from_str(FilterLevel::Debug, "debug"); + assert_from_str(FilterLevel::Debug, "debg"); + assert_from_str(FilterLevel::Trace, "trace"); + assert_from_str(FilterLevel::Trace, "trce"); + + assert_from_str(FilterLevel::Info, "Info"); + assert_from_str(FilterLevel::Info, "INFO"); + assert_from_str(FilterLevel::Info, "iNfO"); + + refute_from_str::<FilterLevel>(""); + assert_from_str(FilterLevel::Info, "i"); + assert_from_str(FilterLevel::Info, "in"); + assert_from_str(FilterLevel::Info, "inf"); + refute_from_str::<FilterLevel>("infor"); + + refute_from_str::<FilterLevel>("?"); + refute_from_str::<FilterLevel>("info "); + refute_from_str::<FilterLevel>(" info"); + refute_from_str::<FilterLevel>("desinfo"); +} + +#[cfg(test)] +fn assert_from_str<T>(expected: T, level_str: &str) + where + T: FromStr + fmt::Debug + PartialEq, + T::Err: fmt::Debug { + let result = T::from_str(level_str); + + let actual = result.unwrap_or_else(|e| { + panic!("Failed to parse filter level '{}': {:?}", level_str, e) + }); + assert_eq!(expected, actual, "Invalid filter level parsed from '{}'", level_str); +} + +#[cfg(test)] +fn refute_from_str<T>(level_str: &str) + where + T: FromStr + fmt::Debug { + let result = T::from_str(level_str); + + if let Ok(level) = result { + panic!("Parsing filter level '{}' succeeded: {:?}", level_str, level) + } +} + +#[cfg(feature = "std")] +#[test] +fn level_to_string_and_from_str_are_compatible() { + assert_to_string_from_str(Level::Critical); + assert_to_string_from_str(Level::Error); + assert_to_string_from_str(Level::Warning); + assert_to_string_from_str(Level::Info); + assert_to_string_from_str(Level::Debug); + assert_to_string_from_str(Level::Trace); +} + +#[cfg(feature = "std")] +#[test] +fn filter_level_to_string_and_from_str_are_compatible() { + assert_to_string_from_str(FilterLevel::Off); + assert_to_string_from_str(FilterLevel::Critical); + assert_to_string_from_str(FilterLevel::Error); + assert_to_string_from_str(FilterLevel::Warning); + assert_to_string_from_str(FilterLevel::Info); + assert_to_string_from_str(FilterLevel::Debug); + assert_to_string_from_str(FilterLevel::Trace); +} + +#[cfg(all(test, feature = "std"))] +fn assert_to_string_from_str<T>(expected: T) + where + T: std::string::ToString + FromStr + PartialEq + fmt::Debug, + <T as FromStr>::Err: fmt::Debug { + let string = expected.to_string(); + + let actual = T::from_str(&string) + .expect(&format!("Failed to parse string representation of {:?}", expected)); + + assert_eq!(expected, actual, "Invalid value parsed from string representation of {:?}", actual); +} + +#[test] +fn filter_level_accepts_tests() { + assert_eq!(true, FilterLevel::Warning.accepts(Level::Error)); + assert_eq!(true, FilterLevel::Warning.accepts(Level::Warning)); + assert_eq!(false, FilterLevel::Warning.accepts(Level::Info)); + assert_eq!(false, FilterLevel::Off.accepts(Level::Critical)); +} +// }}} + +// {{{ Record +#[doc(hidden)] +#[derive(Clone, Copy)] +pub struct RecordLocation { + /// File + pub file: &'static str, + /// Line + pub line: u32, + /// Column (currently not implemented) + pub column: u32, + /// Function (currently not implemented) + pub function: &'static str, + /// Module + pub module: &'static str, +} +/// Information that can be static in the given record thus allowing to optimize +/// record creation to be done mostly at compile-time. +/// +/// This should be constructed via the `record_static!` macro. +pub struct RecordStatic<'a> { + /// Code location + #[doc(hidden)] + pub location: &'a RecordLocation, + /// Tag + #[doc(hidden)] + pub tag: &'a str, + /// Logging level + #[doc(hidden)] + pub level: Level, +} + +/// One logging record +/// +/// Corresponds to one logging statement like `info!(...)` and carries all its +/// data: eg. message, immediate key-value pairs and key-value pairs of `Logger` +/// used to execute it. +/// +/// Record is passed to a `Logger`, which delivers it to its own `Drain`, +/// where actual logging processing is implemented. +pub struct Record<'a> { + rstatic: &'a RecordStatic<'a>, + msg: &'a fmt::Arguments<'a>, + kv: BorrowedKV<'a>, +} + +impl<'a> Record<'a> { + /// Create a new `Record` + /// + /// Most of the time, it is slightly more performant to construct a `Record` + /// via the `record!` macro because it enforces that the *entire* + /// `RecordStatic` is built at compile-time. + /// + /// Use this if runtime record creation is a requirement, as is the case with + /// [slog-async](https://docs.rs/slog-async/latest/slog_async/struct.Async.html), + /// for example. + #[inline] + pub fn new( + s: &'a RecordStatic<'a>, + msg: &'a fmt::Arguments<'a>, + kv: BorrowedKV<'a>, + ) -> Self { + Record { + rstatic: s, + msg: msg, + kv: kv, + } + } + + /// Get a log record message + pub fn msg(&self) -> &fmt::Arguments { + self.msg + } + + /// Get record logging level + pub fn level(&self) -> Level { + self.rstatic.level + } + + /// Get line number + pub fn line(&self) -> u32 { + self.rstatic.location.line + } + + /// Get line number + pub fn location(&self) -> &RecordLocation { + self.rstatic.location + } + + /// Get error column + pub fn column(&self) -> u32 { + self.rstatic.location.column + } + + /// Get file path + pub fn file(&self) -> &'static str { + self.rstatic.location.file + } + + /// Get tag + /// + /// Tag is information that can be attached to `Record` that is not meant + /// to be part of the normal key-value pairs, but only as an ad-hoc control + /// flag for quick lookup in the `Drain`s. As such should be used carefully + /// and mostly in application code (as opposed to libraries) - where tag + /// meaning across the system can be coordinated. When used in libraries, + /// make sure to prefix it with something reasonably distinct, like create + /// name. + pub fn tag(&self) -> &str { + self.rstatic.tag + } + + /// Get module + pub fn module(&self) -> &'static str { + self.rstatic.location.module + } + + /// Get function (placeholder) + /// + /// There's currently no way to obtain that information + /// in Rust at compile time, so it is not implemented. + /// + /// It will be implemented at first opportunity, and + /// it will not be considered a breaking change. + pub fn function(&self) -> &'static str { + self.rstatic.location.function + } + + /// Get key-value pairs + pub fn kv(&self) -> BorrowedKV { + BorrowedKV(self.kv.0) + } +} +// }}} + +// {{{ Serializer + +#[cfg(macro_workaround)] +macro_rules! impl_default_as_fmt{ + (#[$m:meta] $($t:tt)+) => { + #[$m] + impl_default_as_fmt!($($t)*); + }; + ($t:ty => $f:ident) => { + #[allow(missing_docs)] + fn $f(&mut self, key : Key, val : $t) + -> Result { + self.emit_arguments(key, &format_args!("{}", val)) + } + }; +} + +#[cfg(not(macro_workaround))] +macro_rules! impl_default_as_fmt{ + ($(#[$m:meta])* $t:ty => $f:ident) => { + $(#[$m])* + fn $f(&mut self, key : Key, val : $t) + -> Result { + self.emit_arguments(key, &format_args!("{}", val)) + } + }; +} + +/// This is a workaround to be able to pass &mut Serializer, from +/// `Serializer::emit_serde` default implementation. `&Self` can't be casted to +/// `&Serializer` (without : Sized, which break object safety), but it can be +/// used as <T: Serializer>. +#[cfg(feature = "nested-values")] +struct SerializerForward<'a, T: 'a + ?Sized>(&'a mut T); + +#[cfg(feature = "nested-values")] +impl<'a, T: Serializer + 'a + ?Sized> Serializer for SerializerForward<'a, T> { + fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> Result { + self.0.emit_arguments(key, val) + } + + #[cfg(feature = "nested-values")] + fn emit_serde(&mut self, _key: Key, _value: &SerdeValue) -> Result { + panic!(); + } +} + +/// Serializer +/// +/// Drains using `Format` will internally use +/// types implementing this trait. +pub trait Serializer { + impl_default_as_fmt! { + /// Emit `usize` + usize => emit_usize + } + impl_default_as_fmt! { + /// Emit `isize` + isize => emit_isize + } + impl_default_as_fmt! { + /// Emit `bool` + bool => emit_bool + } + impl_default_as_fmt! { + /// Emit `char` + char => emit_char + } + impl_default_as_fmt! { + /// Emit `u8` + u8 => emit_u8 + } + impl_default_as_fmt! { + /// Emit `i8` + i8 => emit_i8 + } + impl_default_as_fmt! { + /// Emit `u16` + u16 => emit_u16 + } + impl_default_as_fmt! { + /// Emit `i16` + i16 => emit_i16 + } + impl_default_as_fmt! { + /// Emit `u32` + u32 => emit_u32 + } + impl_default_as_fmt! { + /// Emit `i32` + i32 => emit_i32 + } + impl_default_as_fmt! { + /// Emit `f32` + f32 => emit_f32 + } + impl_default_as_fmt! { + /// Emit `u64` + u64 => emit_u64 + } + impl_default_as_fmt! { + /// Emit `i64` + i64 => emit_i64 + } + impl_default_as_fmt! { + /// Emit `f64` + f64 => emit_f64 + } + impl_default_as_fmt! { + /// Emit `u128` + #[cfg(integer128)] + u128 => emit_u128 + } + impl_default_as_fmt! { + /// Emit `i128` + #[cfg(integer128)] + i128 => emit_i128 + } + impl_default_as_fmt! { + /// Emit `&str` + &str => emit_str + } + + /// Emit `()` + fn emit_unit(&mut self, key: Key) -> Result { + self.emit_arguments(key, &format_args!("()")) + } + + /// Emit `None` + fn emit_none(&mut self, key: Key) -> Result { + self.emit_arguments(key, &format_args!("")) + } + + /// Emit `fmt::Arguments` + /// + /// This is the only method that has to implemented, but for performance and + /// to retain type information most serious `Serializer`s will want to + /// implement all other methods as well. + fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> Result; + + /// Emit a value implementing + /// [`serde::Serialize`](https://docs.rs/serde/1/serde/trait.Serialize.html) + /// + /// This is especially useful for composite values, eg. structs as Json values, or sequences. + /// + /// To prevent pulling-in `serde` dependency, this is an extension behind a + /// `serde` feature flag. + /// + /// The value needs to implement `SerdeValue`. + #[cfg(feature = "nested-values")] + fn emit_serde(&mut self, key: Key, value: &SerdeValue) -> Result { + value.serialize_fallback(key, &mut SerializerForward(self)) + } + + /// Emit a type implementing `std::error::Error` + /// + /// Error values are a bit special as their `Display` implementation doesn't show full + /// information about the type but must be retrieved using `source()`. This can be used + /// for formatting sources of errors differntly. + /// + /// The default implementation of this method formats the sources separated with `: `. + /// Serializers are encouraged to take advantage of the type information and format it as + /// appropriate. + /// + /// This method is only available in `std` because the `Error` trait is not available + /// without `std`. + #[cfg(feature = "std")] + fn emit_error(&mut self, key: Key, error: &(std::error::Error + 'static)) -> Result { + self.emit_arguments(key, &format_args!("{}", ErrorAsFmt(error))) + } +} + +/// Serializer to closure adapter. +/// +/// Formats all arguments as `fmt::Arguments` and passes them to a given closure. +struct AsFmtSerializer<F>(pub F) +where + F: for<'a> FnMut(Key, fmt::Arguments<'a>) -> Result; + +impl<F> Serializer for AsFmtSerializer<F> +where + F: for<'a> FnMut(Key, fmt::Arguments<'a>) -> Result, +{ + fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> Result { + (self.0)(key, *val) + } +} + +/// A helper for formatting std::error::Error types by joining sources with `: ` +/// +/// This avoids allocation in the default implementation of `Serializer::emit_error()`. +/// This is only enabled with `std` as the trait is only available there. +#[cfg(feature = "std")] +struct ErrorAsFmt<'a>(pub &'a (std::error::Error + 'static)); + +#[cfg(feature = "std")] +impl<'a> fmt::Display for ErrorAsFmt<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // For backwards compatibility + // This is fine because we don't need downcasting + #![allow(deprecated)] + write!(f, "{}", self.0)?; + let mut error = self.0.cause(); + while let Some(source) = error { + write!(f, ": {}", source)?; + error = source.cause(); + } + Ok(()) + } +} + +// }}} + +// {{{ serde +/// A value that can be serialized via serde +/// +/// This is useful for implementing nested values, like sequences or structures. +#[cfg(feature = "nested-values")] +pub trait SerdeValue: erased_serde::Serialize + Value { + /// Serialize the value in a way that is compatible with `slog::Serializer`s + /// that do not support serde. + /// + /// The implementation should *not* call `slog::Serialize::serialize` + /// on itself, as it will lead to infinite recursion. + /// + /// Default implementation is provided, but it returns error, so use it + /// only for internal types in systems and libraries where `serde` is always + /// enabled. + fn serialize_fallback( + &self, + _key: Key, + _serializer: &mut Serializer, + ) -> Result<()> { + Err(Error::Other) + } + + /// Convert to `erased_serialize::Serialize` of the underlying value, + /// so `slog::Serializer`s can use it to serialize via `serde`. + fn as_serde(&self) -> &erased_serde::Serialize; + + /// Convert to a boxed value that can be sent across threads + /// + /// This enables functionality like `slog-async` and similar. + fn to_sendable(&self) -> Box<SerdeValue + Send + 'static>; +} + +// }}} + +// {{{ Value +/// Value that can be serialized +/// +/// Types that implement this type implement custom serialization in the +/// structured part of the log macros. Without an implementation of `Value` for +/// your type you must emit using either the `?` "debug", `#?` "pretty-debug", +/// `%` "display", `#%` "alternate display" or [`SerdeValue`](trait.SerdeValue.html) +/// (if you have the `nested-values` feature enabled) formatters. +/// +/// # Example +/// +/// ``` +/// use slog::{Key, Value, Record, Result, Serializer}; +/// struct MyNewType(i64); +/// +/// impl Value for MyNewType { +/// fn serialize(&self, _rec: &Record, key: Key, serializer: &mut Serializer) -> Result { +/// serializer.emit_i64(key, self.0) +/// } +/// } +/// ``` +/// +/// See also [`KV`](trait.KV.html) for formatting both the key and value. +pub trait Value { + /// Serialize self into `Serializer` + /// + /// Structs implementing this trait should generally + /// only call respective methods of `serializer`. + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result; +} + +impl<'a, V> Value for &'a V +where + V: Value + ?Sized, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + (*self).serialize(record, key, serializer) + } +} + +macro_rules! impl_value_for{ + ($t:ty, $f:ident) => { + impl Value for $t { + fn serialize(&self, + _record : &Record, + key : Key, + serializer : &mut Serializer + ) -> Result { + serializer.$f(key, *self) + } + } + }; +} + +impl_value_for!(usize, emit_usize); +impl_value_for!(isize, emit_isize); +impl_value_for!(bool, emit_bool); +impl_value_for!(char, emit_char); +impl_value_for!(u8, emit_u8); +impl_value_for!(i8, emit_i8); +impl_value_for!(u16, emit_u16); +impl_value_for!(i16, emit_i16); +impl_value_for!(u32, emit_u32); +impl_value_for!(i32, emit_i32); +impl_value_for!(f32, emit_f32); +impl_value_for!(u64, emit_u64); +impl_value_for!(i64, emit_i64); +impl_value_for!(f64, emit_f64); +#[cfg(integer128)] +impl_value_for!(u128, emit_u128); +#[cfg(integer128)] +impl_value_for!(i128, emit_i128); + +impl Value for () { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_unit(key) + } +} + +impl Value for str { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_str(key, self) + } +} + +impl<'a> Value for fmt::Arguments<'a> { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_arguments(key, self) + } +} + +impl Value for String { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_str(key, self.as_str()) + } +} + +impl<T: Value> Value for Option<T> { + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + match *self { + Some(ref s) => s.serialize(record, key, serializer), + None => serializer.emit_none(key), + } + } +} + +impl<T> Value for Box<T> +where + T: Value + ?Sized, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, key, serializer) + } +} +impl<T> Value for Arc<T> +where + T: Value + ?Sized, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, key, serializer) + } +} + +impl<T> Value for Rc<T> +where + T: Value, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, key, serializer) + } +} + +impl<T> Value for core::num::Wrapping<T> +where + T: Value, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + self.0.serialize(record, key, serializer) + } +} + +#[cfg(feature = "std")] +impl<'a> Value for std::path::Display<'a> { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_arguments(key, &format_args!("{}", *self)) + } +} + +#[cfg(feature = "std")] +impl Value for std::net::SocketAddr { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_arguments(key, &format_args!("{}", self)) + } +} + +#[cfg(feature = "std")] +impl Value for std::io::Error { + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_error(key, self) + } +} + +/// Explicit lazy-closure `Value` +pub struct FnValue<V: Value, F>(pub F) +where + F: for<'c, 'd> Fn(&'c Record<'d>) -> V; + +impl<'a, V: 'a + Value, F> Value for FnValue<V, F> +where + F: 'a + for<'c, 'd> Fn(&'c Record<'d>) -> V, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + (self.0)(record).serialize(record, key, serializer) + } +} + +#[deprecated(note = "Renamed to `PushFnValueSerializer`")] +/// Old name of `PushFnValueSerializer` +pub type PushFnSerializer<'a> = PushFnValueSerializer<'a>; + +/// Handle passed to `PushFnValue` closure +/// +/// It makes sure only one value is serialized, and will automatically emit +/// `()` if nothing else was serialized. +pub struct PushFnValueSerializer<'a> { + record: &'a Record<'a>, + key: Key, + serializer: &'a mut Serializer, + done: bool, +} + +impl<'a> PushFnValueSerializer<'a> { + #[deprecated(note = "Renamed to `emit`")] + /// Emit a value + pub fn serialize<'b, S: 'b + Value>(self, s: S) -> Result { + self.emit(s) + } + + /// Emit a value + /// + /// This consumes `self` to prevent serializing one value multiple times + pub fn emit<'b, S: 'b + Value>(mut self, s: S) -> Result { + self.done = true; + s.serialize(self.record, self.key.clone(), self.serializer) + } +} + +impl<'a> Drop for PushFnValueSerializer<'a> { + fn drop(&mut self) { + if !self.done { + // unfortunately this gives no change to return serialization errors + let _ = self.serializer.emit_unit(self.key.clone()); + } + } +} + +/// Lazy `Value` that writes to Serializer +/// +/// It's more ergonomic for closures used as lazy values to return type +/// implementing `Serialize`, but sometimes that forces an allocation (eg. +/// `String`s) +/// +/// In some cases it might make sense for another closure form to be used - one +/// taking a serializer as an argument, which avoids lifetimes / allocation +/// issues. +/// +/// Generally this method should be used if it avoids a big allocation of +/// `Serialize`-implementing type in performance-critical logging statement. +/// +/// ``` +/// #[macro_use] +/// extern crate slog; +/// use slog::{PushFnValue, Logger, Discard}; +/// +/// fn main() { +/// // Create a logger with a key-value printing +/// // `file:line` string value for every logging statement. +/// // `Discard` `Drain` used for brevity. +/// let root = Logger::root(Discard, o!( +/// "source_location" => PushFnValue(|record , s| { +/// s.serialize( +/// format_args!( +/// "{}:{}", +/// record.file(), +/// record.line(), +/// ) +/// ) +/// }) +/// )); +/// } +/// ``` +pub struct PushFnValue<F>(pub F) +where + F: 'static + + for<'c, 'd> Fn(&'c Record<'d>, PushFnValueSerializer<'c>) -> Result; + +impl<F> Value for PushFnValue<F> +where + F: 'static + + for<'c, 'd> Fn(&'c Record<'d>, PushFnValueSerializer<'c>) -> Result, +{ + fn serialize( + &self, + record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + let ser = PushFnValueSerializer { + record: record, + key: key, + serializer: serializer, + done: false, + }; + (self.0)(record, ser) + } +} + +/// A wrapper struct for serializing errors +/// +/// This struct can be used to wrap types that don't implement `slog::Value` but +/// do implement `std::error::Error` so that they can be logged. +/// This is usually not used directly but using `#error` in the macros. +/// +/// This struct is only available in `std` because the `Error` trait is not available +/// without `std`. +#[cfg(feature = "std")] +pub struct ErrorValue<E: std::error::Error>(pub E); + +#[cfg(feature = "std")] +impl<E> Value for ErrorValue<E> +where + E: 'static + std::error::Error, +{ + fn serialize( + &self, + _record: &Record, + key: Key, + serializer: &mut Serializer, + ) -> Result { + serializer.emit_error(key, &self.0) + } +} + +// }}} + +// {{{ KV +/// Key-value pair(s) for log events +/// +/// Zero, one or more key value pairs chained together +/// +/// Any logging data must implement this trait for slog to be able to use it, +/// although slog comes with default implementations within its macros (the +/// `=>` and `kv!` portions of the log macros). +/// +/// If you don't use this trait, you must emit your structured data by +/// specifying both key and value in each log event: +/// +/// ```ignore +/// info!(logger, "my event"; "type_key" => %my_val); +/// ``` +/// +/// If you implement this trait, that can become: +/// +/// ```ignore +/// info!(logger, "my event"; my_val); +/// ``` +/// +/// Types implementing this trait can emit multiple key-value pairs, and can +/// customize their structured representation. The order of emitting them +/// should be consistent with the way key-value pair hierarchy is traversed: +/// from data most specific to the logging context to the most general one. Or +/// in other words: from newest to oldest. +/// +/// Implementers are are responsible for calling the `emit_*` methods on the +/// `Serializer` passed in, the `Record` can be used to make display decisions +/// based on context, but for most plain-value structs you will just call +/// `emit_*`. +/// +/// # Example +/// +/// ``` +/// use slog::{KV, Record, Result, Serializer}; +/// +/// struct MyNewType(i64); +/// +/// impl KV for MyNewType { +/// fn serialize(&self, _rec: &Record, serializer: &mut Serializer) -> Result { +/// serializer.emit_i64("my_new_type", self.0) +/// } +/// } +/// ``` +/// +/// See also [`Value`](trait.Value.html), which allows you to customize just +/// the right hand side of the `=>` structure macro, and (if you have the +/// `nested-values` feature enabled) [`SerdeValue`](trait.SerdeValue.html) +/// which allows emitting anything serde can emit. +pub trait KV { + /// Serialize self into `Serializer` + /// + /// `KV` should call respective `Serializer` methods + /// for each key-value pair it contains. + fn serialize(&self, record: &Record, serializer: &mut Serializer) + -> Result; +} + +impl<'a, T> KV for &'a T +where + T: KV, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, serializer) + } +} + +#[cfg(feature = "nothreads")] +/// Thread-local safety bound for `KV` +/// +/// This type is used to enforce `KV`s stored in `Logger`s are thread-safe. +pub trait SendSyncRefUnwindSafeKV: KV {} + +#[cfg(feature = "nothreads")] +impl<T> SendSyncRefUnwindSafeKV for T where T: KV + ?Sized {} + +#[cfg(all(not(feature = "nothreads"), feature = "std"))] +/// This type is used to enforce `KV`s stored in `Logger`s are thread-safe. +pub trait SendSyncRefUnwindSafeKV: KV + Send + Sync + RefUnwindSafe {} + +#[cfg(all(not(feature = "nothreads"), feature = "std"))] +impl<T> SendSyncRefUnwindSafeKV for T where T: KV + Send + Sync + RefUnwindSafe + ?Sized {} + +#[cfg(all(not(feature = "nothreads"), not(feature = "std")))] +/// This type is used to enforce `KV`s stored in `Logger`s are thread-safe. +pub trait SendSyncRefUnwindSafeKV: KV + Send + Sync {} + +#[cfg(all(not(feature = "nothreads"), not(feature = "std")))] +impl<T> SendSyncRefUnwindSafeKV for T where T: KV + Send + Sync + ?Sized {} + +/// Single pair `Key` and `Value` +pub struct SingleKV<V>(pub Key, pub V) +where + V: Value; + +#[cfg(feature = "dynamic-keys")] +impl<V: Value> From<(String, V)> for SingleKV<V> { + fn from(x: (String, V)) -> SingleKV<V> { + SingleKV(Key::from(x.0), x.1) + } +} +#[cfg(feature = "dynamic-keys")] +impl<V: Value> From<(&'static str, V)> for SingleKV<V> { + fn from(x: (&'static str, V)) -> SingleKV<V> { + SingleKV(Key::from(x.0), x.1) + } +} +#[cfg(not(feature = "dynamic-keys"))] +impl<V: Value> From<(&'static str, V)> for SingleKV<V> { + fn from(x: (&'static str, V)) -> SingleKV<V> { + SingleKV(x.0, x.1) + } +} + +impl<V> KV for SingleKV<V> +where + V: Value, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + self.1.serialize(record, self.0.clone(), serializer) + } +} + +impl KV for () { + fn serialize( + &self, + _record: &Record, + _serializer: &mut Serializer, + ) -> Result { + Ok(()) + } +} + +impl<T: KV, R: KV> KV for (T, R) { + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + try!(self.0.serialize(record, serializer)); + self.1.serialize(record, serializer) + } +} + +impl<T> KV for Box<T> +where + T: KV + ?Sized, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, serializer) + } +} + +impl<T> KV for Arc<T> +where + T: KV + ?Sized, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + (**self).serialize(record, serializer) + } +} + +impl<T> KV for OwnedKV<T> +where + T: SendSyncRefUnwindSafeKV + ?Sized, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + self.0.serialize(record, serializer) + } +} + +impl<'a> KV for BorrowedKV<'a> { + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + self.0.serialize(record, serializer) + } +} +// }}} + +// {{{ OwnedKV +/// Owned KV +/// +/// "Owned" means that the contained data (key-value pairs) can belong +/// to a `Logger` and thus must be thread-safe (`'static`, `Send`, `Sync`) +/// +/// Zero, one or more owned key-value pairs. +/// +/// Can be constructed with [`o!` macro](macro.o.html). +pub struct OwnedKV<T>( + #[doc(hidden)] + /// The exact details of that it are not considered public + /// and stable API. `slog_o` or `o` macro should be used + /// instead to create `OwnedKV` instances. + pub T, +) +where + T: SendSyncRefUnwindSafeKV + ?Sized; +// }}} + +// {{{ BorrowedKV +/// Borrowed `KV` +/// +/// "Borrowed" means that the data is only a temporary +/// referenced (`&T`) and can't be stored directly. +/// +/// Zero, one or more borrowed key-value pairs. +/// +/// Can be constructed with [`b!` macro](macro.b.html). +pub struct BorrowedKV<'a>( + /// The exact details of it function are not + /// considered public and stable API. `log` and other + /// macros should be used instead to create + /// `BorrowedKV` instances. + #[doc(hidden)] + pub &'a KV, +); + +// }}} + +// {{{ OwnedKVList +struct OwnedKVListNode<T> +where + T: SendSyncRefUnwindSafeKV + 'static, +{ + next_node: Arc<SendSyncRefUnwindSafeKV + 'static>, + kv: T, +} + +struct MultiListNode { + next_node: Arc<SendSyncRefUnwindSafeKV + 'static>, + node: Arc<SendSyncRefUnwindSafeKV + 'static>, +} + +/// Chain of `SyncMultiSerialize`-s of a `Logger` and its ancestors +#[derive(Clone)] +pub struct OwnedKVList { + node: Arc<SendSyncRefUnwindSafeKV + 'static>, +} + +impl<T> KV for OwnedKVListNode<T> +where + T: SendSyncRefUnwindSafeKV + 'static, +{ + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + try!(self.kv.serialize(record, serializer)); + try!(self.next_node.serialize(record, serializer)); + + Ok(()) + } +} + +impl KV for MultiListNode { + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + try!(self.next_node.serialize(record, serializer)); + try!(self.node.serialize(record, serializer)); + + Ok(()) + } +} + +impl KV for OwnedKVList { + fn serialize( + &self, + record: &Record, + serializer: &mut Serializer, + ) -> Result { + try!(self.node.serialize(record, serializer)); + + Ok(()) + } +} + +impl fmt::Debug for OwnedKVList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "(")); + let mut i = 0; + + { + let mut as_str_ser = AsFmtSerializer(|key, _val| { + if i != 0 { + try!(write!(f, ", ")); + } + + try!(write!(f, "{}", key)); + i += 1; + Ok(()) + }); + let record_static = record_static!(Level::Trace, ""); + + try!( + self.node + .serialize( + &Record::new( + &record_static, + &format_args!(""), + BorrowedKV(&STATIC_TERMINATOR_UNIT) + ), + &mut as_str_ser + ) + .map_err(|_| fmt::Error) + ); + } + + try!(write!(f, ")")); + Ok(()) + } +} + +impl OwnedKVList { + /// New `OwnedKVList` node without a parent (root) + fn root<T>(values: OwnedKV<T>) -> Self + where + T: SendSyncRefUnwindSafeKV + 'static, + { + OwnedKVList { + node: Arc::new(OwnedKVListNode { + next_node: Arc::new(()), + kv: values.0, + }), + } + } + + /// New `OwnedKVList` node with an existing parent + fn new<T>( + values: OwnedKV<T>, + next_node: Arc<SendSyncRefUnwindSafeKV + 'static>, + ) -> Self + where + T: SendSyncRefUnwindSafeKV + 'static, + { + OwnedKVList { + node: Arc::new(OwnedKVListNode { + next_node: next_node, + kv: values.0, + }), + } + } +} + +impl<T> convert::From<OwnedKV<T>> for OwnedKVList +where + T: SendSyncRefUnwindSafeKV + 'static, +{ + fn from(from: OwnedKV<T>) -> Self { + OwnedKVList::root(from) + } +} +// }}} + +// {{{ Error +#[derive(Debug)] +#[cfg(feature = "std")] +/// Serialization Error +pub enum Error { + /// `io::Error` (not available in ![no_std] mode) + Io(std::io::Error), + /// `fmt::Error` + Fmt(std::fmt::Error), + /// Other error + Other, +} + +#[derive(Debug)] +#[cfg(not(feature = "std"))] +/// Serialization Error +pub enum Error { + /// `fmt::Error` + Fmt(core::fmt::Error), + /// Other error + Other, +} + +/// Serialization `Result` +pub type Result<T = ()> = result::Result<T, Error>; + +#[cfg(feature = "std")] +impl From<std::io::Error> for Error { + fn from(err: std::io::Error) -> Error { + Error::Io(err) + } +} + +impl From<core::fmt::Error> for Error { + fn from(_: core::fmt::Error) -> Error { + Error::Other + } +} + +#[cfg(feature = "std")] +impl From<Error> for std::io::Error { + fn from(e: Error) -> std::io::Error { + match e { + Error::Io(e) => e, + Error::Fmt(_) => std::io::Error::new( + std::io::ErrorKind::Other, + "formatting error", + ), + Error::Other => { + std::io::Error::new(std::io::ErrorKind::Other, "other error") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::Io(ref e) => e.description(), + Error::Fmt(_) => "formatting error", + Error::Other => "serialization error", + } + } + + fn cause(&self) -> Option<&std::error::Error> { + match *self { + Error::Io(ref e) => Some(e), + Error::Fmt(ref e) => Some(e), + Error::Other => None, + } + } +} + +#[cfg(feature = "std")] +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> std::fmt::Result { + match *self { + Error::Io(ref e) => e.fmt(fmt), + Error::Fmt(ref e) => e.fmt(fmt), + Error::Other => fmt.write_str("Other serialization error"), + } + } +} +// }}} + +// {{{ Misc +/// This type is here just to abstract away lack of `!` type support in stable +/// rust during time of the release. It will be switched to `!` at some point +/// and `Never` should not be considered "stable" API. +#[doc(hidden)] +pub type Never = private::NeverStruct; + +mod private { + #[doc(hidden)] + #[derive(Clone, Debug)] + pub struct NeverStruct(()); +} + +/// This is not part of "stable" API +#[doc(hidden)] +pub static STATIC_TERMINATOR_UNIT: () = (); + +#[allow(unknown_lints)] +#[allow(inline_always)] +#[inline(always)] +#[doc(hidden)] +/// Not an API +/// +/// Generally it's a bad idea to depend on static logging level +/// in your code. Use closures to perform operations lazily +/// only when logging actually takes place. +pub fn __slog_static_max_level() -> FilterLevel { + if !cfg!(debug_assertions) { + if cfg!(feature = "release_max_level_off") { + return FilterLevel::Off; + } else if cfg!(feature = "release_max_level_error") { + return FilterLevel::Error; + } else if cfg!(feature = "release_max_level_warn") { + return FilterLevel::Warning; + } else if cfg!(feature = "release_max_level_info") { + return FilterLevel::Info; + } else if cfg!(feature = "release_max_level_debug") { + return FilterLevel::Debug; + } else if cfg!(feature = "release_max_level_trace") { + return FilterLevel::Trace; + } + } + if cfg!(feature = "max_level_off") { + FilterLevel::Off + } else if cfg!(feature = "max_level_error") { + FilterLevel::Error + } else if cfg!(feature = "max_level_warn") { + FilterLevel::Warning + } else if cfg!(feature = "max_level_info") { + FilterLevel::Info + } else if cfg!(feature = "max_level_debug") { + FilterLevel::Debug + } else if cfg!(feature = "max_level_trace") { + FilterLevel::Trace + } else { + if !cfg!(debug_assertions) { + FilterLevel::Info + } else { + FilterLevel::Debug + } + } +} + +// }}} + +// {{{ Slog v1 Compat +#[deprecated(note = "Renamed to `Value`")] +/// Compatibility name to ease upgrading from `slog v1` +pub type Serialize = Value; + +#[deprecated(note = "Renamed to `PushFnValue`")] +/// Compatibility name to ease upgrading from `slog v1` +pub type PushLazy<T> = PushFnValue<T>; + +#[deprecated(note = "Renamed to `PushFnValueSerializer`")] +/// Compatibility name to ease upgrading from `slog v1` +pub type ValueSerializer<'a> = PushFnValueSerializer<'a>; + +#[deprecated(note = "Renamed to `OwnedKVList`")] +/// Compatibility name to ease upgrading from `slog v1` +pub type OwnedKeyValueList = OwnedKVList; + +#[deprecated(note = "Content of ser module moved to main namespace")] +/// Compatibility name to ease upgrading from `slog v1` +pub mod ser { + #[allow(deprecated)] + pub use super::{OwnedKeyValueList, PushLazy, Serialize, Serializer, + ValueSerializer}; +} +// }}} + +// {{{ Test +#[cfg(test)] +mod tests; + +// }}} + +// vim: foldmethod=marker foldmarker={{{,}}} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..95c4c38 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,419 @@ +use {Discard, Logger, Never, KV, Drain, OwnedKVList, Record, AsFmtSerializer}; + +// Separate module to test lack of imports +mod no_imports { + use {Discard, Logger}; + /// ensure o! macro expands without error inside a module + #[test] + fn test_o_macro_expansion() { + let _ = Logger::root(Discard, o!("a" => "aa")); + } + /// ensure o! macro expands without error inside a module + #[test] + fn test_slog_o_macro_expansion() { + let _ = Logger::root(Discard, slog_o!("a" => "aa")); + } +} + +#[cfg(feature = "std")] +mod std_only { + use super::super::*; + use std; + + #[derive(Clone)] + struct CheckError; + + impl Drain for CheckError { + type Ok = (); + type Err = Never; + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> std::result::Result<Self::Ok, Self::Err> { + struct ErrorSerializer(String); + + impl Serializer for ErrorSerializer { + fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> Result { + use core::fmt::Write; + + assert!(key == "error"); + self.0.write_fmt(*val).unwrap(); + Ok(()) + } + } + + let mut serializer = ErrorSerializer(String::new()); + values.serialize(record, &mut serializer).unwrap(); + assert_eq!( + serializer.0, + format!("{}", record.msg()) + ); + Ok(()) + } + } + + #[derive(Debug)] + struct TestError<E=std::string::ParseError>(&'static str, Option<E>); + + impl TestError { + fn new(message: &'static str) -> Self { + TestError(message, None) + } + } + + impl<E> fmt::Display for TestError<E> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl<E: std::error::Error + 'static> std::error::Error for TestError<E> { + #[allow(deprecated)] + fn cause(&self) -> Option<&std::error::Error> { + self.1.as_ref().map(|error| error as _) + } + + #[allow(deprecated)] + fn description(&self) -> &str { + "test error" + } + } + + #[test] + fn logger_fmt_debug_sanity() { + let root = Logger::root(Discard, o!("a" => "aa")); + let log = root.new(o!("b" => "bb", "c" => "cc")); + + assert_eq!(format!("{:?}", log), "Logger(c, b, a)"); + } + + #[test] + fn multichain() { + #[derive(Clone)] + struct CheckOwned; + + impl Drain for CheckOwned { + type Ok = (); + type Err = Never; + fn log( + &self, + record: &Record, + values: &OwnedKVList, + ) -> std::result::Result<Self::Ok, Self::Err> { + assert_eq!( + format!("{}", record.msg()), + format!("{:?}", values) + ); + Ok(()) + } + } + + let root = Logger::root(CheckOwned, o!("a" => "aa")); + let log = root.new(o!("b1" => "bb", "b2" => "bb")); + + info!(log, "(b2, b1, a)"); + + let log = Logger::root(log, o!("c" => "cc")); + info!(log, "(c, b2, b1, a)"); + let log = Logger::root(log, o!("d1" => "dd", "d2" => "dd")); + info!(log, "(d2, d1, c, b2, b1, a)"); + } + + #[test] + fn error_fmt_no_source() { + let logger = Logger::root(CheckError, o!("error" => #TestError::new("foo"))); + info!(logger, "foo"); + } + + #[test] + fn error_fmt_single_source() { + let logger = Logger::root(CheckError, o!("error" => #TestError("foo", Some(TestError::new("bar"))))); + info!(logger, "foo: bar"); + } + + #[test] + fn error_fmt_two_sources() { + let logger = Logger::root(CheckError, o!("error" => #TestError("foo", Some(TestError("bar", Some(TestError::new("baz"))))))); + info!(logger, "foo: bar: baz"); + } + + #[test] + fn ioerror_impls_value() { + let logger = Logger::root(Discard, o!()); + info!(logger, "not found"; "error" => std::io::Error::from(std::io::ErrorKind::NotFound)); + // compiles? + info!(logger, "not found"; "error" => #std::io::Error::from(std::io::ErrorKind::NotFound)); + } +} + +#[test] +fn expressions() { + use super::{Record, Result, Serializer, KV}; + + struct Foo; + + impl Foo { + fn bar(&self) -> u32 { + 1 + } + } + + struct X { + foo: Foo, + } + + let log = Logger::root(Discard, o!("version" => env!("CARGO_PKG_VERSION"))); + + let foo = Foo; + let r = X { foo: foo }; + + warn!(log, "logging message"); + slog_warn!(log, "logging message"); + + info!(log, #"with tag", "logging message"); + slog_info!(log, #"with tag", "logging message"); + + warn!(log, "logging message"; "a" => "b"); + slog_warn!(log, "logging message"; "a" => "b"); + + warn!(log, "logging message bar={}", r.foo.bar()); + slog_warn!(log, "logging message bar={}", r.foo.bar()); + + warn!( + log, + "logging message bar={} foo={}", + r.foo.bar(), + r.foo.bar() + ); + slog_warn!( + log, + "logging message bar={} foo={}", + r.foo.bar(), + r.foo.bar() + ); + + // trailing comma check + warn!( + log, + "logging message bar={} foo={}", + r.foo.bar(), + r.foo.bar(), + ); + slog_warn!( + log, + "logging message bar={} foo={}", + r.foo.bar(), + r.foo.bar(), + ); + + warn!(log, "logging message bar={}", r.foo.bar(); "x" => 1); + slog_warn!(log, "logging message bar={}", r.foo.bar(); "x" => 1); + + // trailing comma check + warn!(log, "logging message bar={}", r.foo.bar(); "x" => 1,); + slog_warn!(log, "logging message bar={}", r.foo.bar(); "x" => 1,); + + warn!(log, + "logging message bar={}", r.foo.bar(); "x" => 1, "y" => r.foo.bar()); + slog_warn!(log, + "logging message bar={}", r.foo.bar(); + "x" => 1, "y" => r.foo.bar()); + + warn!(log, "logging message bar={}", r.foo.bar(); "x" => r.foo.bar()); + slog_warn!(log, "logging message bar={}", r.foo.bar(); "x" => r.foo.bar()); + + warn!(log, "logging message bar={}", r.foo.bar(); + "x" => r.foo.bar(), "y" => r.foo.bar()); + slog_warn!(log, + "logging message bar={}", r.foo.bar(); + "x" => r.foo.bar(), "y" => r.foo.bar()); + + // trailing comma check + warn!(log, + "logging message bar={}", r.foo.bar(); + "x" => r.foo.bar(), "y" => r.foo.bar(),); + slog_warn!(log, + "logging message bar={}", r.foo.bar(); + "x" => r.foo.bar(), "y" => r.foo.bar(),); + + { + #[derive(Clone)] + struct K; + + impl KV for K { + fn serialize( + &self, + _record: &Record, + _serializer: &mut Serializer, + ) -> Result { + Ok(()) + } + } + + let x = K; + + let _log = log.new(o!(x.clone())); + let _log = log.new(o!("foo" => "bar", x.clone())); + let _log = log.new(o!("foo" => "bar", x.clone(), x.clone())); + let _log = log.new( + slog_o!("foo" => "bar", x.clone(), x.clone(), "aaa" => "bbb"), + ); + + info!(log, "message"; "foo" => "bar", &x, &x, "aaa" => "bbb"); + } + + info!( + log, + "message {}", + { 3 + 3; 2}; + "foo" => "bar", + "foo" => { 3 + 3; 2}, + "aaa" => "bbb"); +} + +#[cfg(integer128)] +#[test] +fn integer_128_types() { + let log = Logger::root(Discard, o!("version" => env!("CARGO_PKG_VERSION"))); + + info!(log, "i128 = {}", 42i128; "foo" => 7i128); + info!(log, "u128 = {}", 42u128; "foo" => 7u128); +} + +#[test] +fn expressions_fmt() { + let log = Logger::root(Discard, o!("version" => env!("CARGO_PKG_VERSION"))); + + let f = "f"; + let d = (1, 2); + + info!(log, "message"; "f" => %f, "d" => ?d); +} + +#[cfg(feature = "std")] +#[test] +fn display_and_alternate_display() { + use core::fmt; + use core::cell::Cell; + + struct Example; + + impl fmt::Display for Example { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.alternate() { + f.write_str("alternate") + } else { + f.write_str("normal") + } + } + } + + #[derive(Clone, Default)] + struct CheckExample; + + impl Drain for CheckExample { + type Ok = (); + type Err = Never; + + fn log(&self, record: &Record, values: &OwnedKVList) -> Result<(), Never> { + let mut checked_n = false; + let mut checked_a = false; + { + let mut serializer = AsFmtSerializer(|key, fmt_args| { + if key == "n" { + assert_eq!(format!("{}", fmt_args), "normal"); + checked_n = true; + } else if key == "a" { + assert_eq!(format!("{}", fmt_args), "alternate"); + checked_a = true; + } else { + panic!("Unexpected key: {}", key); + } + Ok(()) + }); + + record.kv.serialize(record, &mut serializer).unwrap(); + } + + assert!(checked_n, "Expected the normal formatter to be used"); + assert!(checked_a, "Expected the alternate formatter to be used"); + + Ok(()) + } + } + + let log = Logger::root(CheckExample, o!()); + + info!(log, ""; "n" => %Example, "a" => #%Example); +} + +#[test] +fn makers() { + use ::*; + let drain = Duplicate( + Discard.filter(|r| r.level().is_at_least(Level::Info)), + Discard.filter_level(Level::Warning), + ).map(Fuse); + let _log = Logger::root( + Arc::new(drain), + o!("version" => env!("CARGO_PKG_VERSION")), + ); +} + +#[test] +fn simple_logger_erased() { + use ::*; + + fn takes_arced_drain(_l: Logger) {} + + let drain = Discard.filter_level(Level::Warning).map(Fuse); + let log = + Logger::root_typed(drain, o!("version" => env!("CARGO_PKG_VERSION"))); + + takes_arced_drain(log.to_erased()); +} + +#[test] +fn logger_to_erased() { + use ::*; + + fn takes_arced_drain(_l: Logger) {} + + let drain = Duplicate( + Discard.filter(|r| r.level().is_at_least(Level::Info)), + Discard.filter_level(Level::Warning), + ).map(Fuse); + let log = + Logger::root_typed(drain, o!("version" => env!("CARGO_PKG_VERSION"))); + + takes_arced_drain(log.into_erased()); +} + +#[test] +fn logger_by_ref() { + use ::*; + let drain = Discard.filter_level(Level::Warning).map(Fuse); + let log = Logger::root_typed(drain, o!("version" => env!("CARGO_PKG_VERSION"))); + let f = "f"; + let d = (1, 2); + info!(&log, "message"; "f" => %f, "d" => ?d); +} + +#[test] +fn test_never_type_clone() { + // We just want to make sure that this compiles + fn _do_not_run() { + let x: Never = panic!("Can't actually construct a Never type here!"); + let y = x.clone(); + } + // Always pass if we compiled +} + +#[cfg(feature = "std")] +#[test] +fn can_hash_keys() { + use std::collections::HashSet; + use Key; + let tab: HashSet<Key> = ["foo"].iter().map(|&k| k.into()).collect(); +} |