├── .github └── workflows │ ├── ci.yml │ ├── rust-clippy.yml │ └── security-audit.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock.msrv ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── SECURITY.md ├── appveyor.yml ├── benches ├── common │ └── mod.rs ├── junk_modules.rs ├── no_color.rs ├── quiet.rs ├── real_calls.rs ├── skip_module.rs └── util │ └── mod.rs ├── examples ├── clap.rs ├── docopt.rs ├── large-example │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── bar.rs │ │ ├── bin │ │ ├── another.rs │ │ └── yet │ │ │ └── main.rs │ │ ├── foo.rs │ │ ├── lib.rs │ │ └── main.rs ├── structopt.rs └── timestamp.rs ├── rustfmt.toml ├── src └── lib.rs └── tests ├── debug_level.rs ├── debug_level_log.rs ├── error_level.rs ├── info_level.rs ├── module_inclusion.rs ├── no_levels_output.rs ├── pty_closed.rs ├── quiet_trace_level.rs ├── trace_level.rs ├── utils └── mod.rs └── warn_level.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | env: 9 | CARGO_TERM_COLOR: always 10 | jobs: 11 | msrv: 12 | strategy: 13 | matrix: 14 | rust: 15 | - version: stable 16 | command: test 17 | args: "" 18 | - version: 1.48.0 19 | command: check 20 | args: "--locked" 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: install Rust 25 | run: | 26 | rustup toolchain install ${{ matrix.rust.version }} --profile minimal --allow-downgrade 27 | rustup default ${{ matrix.rust.version }} 28 | - run: cp Cargo.lock.msrv Cargo.lock 29 | if: ${{ matrix.rust.version}} == 1.48.0 30 | - name: cargo check/test 31 | run: | 32 | cargo --version 33 | cargo ${{ matrix.rust.command }} ${{ matrix.rust.args }} 34 | 35 | test: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: install Rust 40 | run: rustup toolchain install stable --profile minimal --component rustfmt,clippy --allow-downgrade 41 | - name: cargo fmt 42 | run: cargo fmt --check 43 | - name: cargo clippy 44 | run: cargo clippy --all-features -- --deny warnings 45 | - name: install tarpaulin 46 | uses: baptiste0928/cargo-install@v2 47 | with: 48 | crate: cargo-tarpaulin 49 | - name: cargo tarpaulin 50 | run: cargo tarpaulin --engine llvm --follow-exec --post-test-delay 10 51 | 52 | bench: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | - name: install Rust 57 | run: rustup toolchain install nightly --profile minimal 58 | - name: cargo bench 59 | run: cargo +nightly bench --all-features -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: rust-clippy analyze 11 | 12 | on: 13 | push: 14 | branches: [ "master" ] 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ "master" ] 18 | schedule: 19 | - cron: '24 14 * * 2' 20 | 21 | jobs: 22 | rust-clippy-analyze: 23 | name: Run rust-clippy analyzing 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | security-events: write 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | 32 | - name: Install Rust toolchain 33 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | components: clippy 38 | override: true 39 | 40 | - name: Install required cargo 41 | run: cargo install clippy-sarif sarif-fmt 42 | 43 | - name: Run rust-clippy 44 | run: 45 | cargo clippy 46 | --all-features 47 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 48 | continue-on-error: true 49 | 50 | - name: Upload analysis results to GitHub 51 | uses: github/codeql-action/upload-sarif@v1 52 | with: 53 | sarif_file: rust-clippy-results.sarif 54 | wait-for-processing: true 55 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | jobs: 8 | security_audit: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions-rs/audit-check@v1 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## 0.6.0 4 | ### Changed 5 | - Bumped the minimum Rust version to 1.48.0 6 | * [RUSTSEC-2021-0145](https://rustsec.org/advisories/RUSTSEC-2021-0145) fixed 7 | 8 | ## 0.5.3 9 | ### Changed 10 | - Moved the timestamps feature behind the enabled-by-default crate feature `timestamps`. 11 | 12 | ### Fixed 13 | - Added missing `show_module_names` field to `StdErrLog` debug implementation. 14 | 15 | ## 0.5.2 16 | ### Changed 17 | - Bumped the minimum Rust version to 1.36.0 18 | * [RUSTSEC-2022-0006](https://rustsec.org/advisories/RUSTSEC-2022-0006.html) fixed 19 | This is a semver break but given the age of the Rust versions and changes that have 20 | happened in the log crate, we'll do it. 21 | - Make it easier to specify debug levels with a number or enum. 22 | 23 | ## 0.5.1 24 | ### Added 25 | - added `.show_module_names()` to include module name in output 26 | 27 | ## 0.5.0 28 | ### Changed 29 | - Bumped minimum Rust version to 1.31.0 30 | * 1.16.0 fails on current version of some deps and some crates.io fetches 31 | * Log crate 0.4.11 raised its MSRV to 1.31.0 32 | - Updated and checked minimum version of all deps 33 | - Switched to rust edition 2018 34 | - Improved CI testing 35 | 36 | ## 0.4.3 37 | 38 | ### Changed 39 | - only check if stderr is a tty for the auto color choice, all other 40 | choices pass through without modification 41 | - bump termcolor to 1.0 42 | 43 | ## 0.4.2 44 | ### Added 45 | - add the ability to not show logging levels via `.show_levels()` 46 | 47 | ### Fixed 48 | - auto color choice (the default) will now disable colors if stderr 49 | is not connected to a tty by default 50 | 51 | ## 0.4.1 52 | 53 | ### Added 54 | 55 | - Support millisecond timestamp resolution 56 | 57 | ## 0.4.0 58 | 59 | ### Added 60 | 61 | - Timestamp now implements FromStr to make arg parsers quicker to implement 62 | - Added a StructOpt example to the crate documentation and examples 63 | 64 | ### Changed 65 | 66 | - Improved clap example and added Timestamp support 67 | - Full use and depend on log 0.4.1. 68 | 69 | ## 0.3.0 70 | 71 | ### Added 72 | 73 | - a large example under examples to detail how module level logging 74 | works 75 | 76 | ### Changed 77 | 78 | - bump minimum Rust version to 1.16.0 79 | - allow all log 0.3.x releases 80 | - fix situations where including `a::b` also included `a::baz` 81 | 82 | ## 0.2.4 83 | 84 | ### Changed 85 | 86 | - pinned log to 0.3.8 or older to retain Rust 1.13.0 support 87 | 88 | ## 0.2.3 89 | 90 | ### Added 91 | 92 | - clap example 93 | - support timestamps with microsecond and nanosecond granularity 94 | 95 | ### Changed 96 | 97 | - improved performance (https://github.com/cardoe/stderrlog-rs/pull/2) 98 | - improved docopt example in README 99 | - migrated from time to chrono 100 | 101 | ### Thanks 102 | 103 | - Thanks to Zachary Dremann for the performance 104 | improvements and the nudge to improve the docs. 105 | -------------------------------------------------------------------------------- /Cargo.lock.msrv: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "android_system_properties" 5 | version = "0.1.5" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 8 | dependencies = [ 9 | "libc", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.11.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 17 | dependencies = [ 18 | "winapi", 19 | ] 20 | 21 | [[package]] 22 | name = "atty" 23 | version = "0.2.14" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 26 | dependencies = [ 27 | "hermit-abi 0.1.19", 28 | "libc", 29 | "winapi", 30 | ] 31 | 32 | [[package]] 33 | name = "autocfg" 34 | version = "1.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 37 | 38 | [[package]] 39 | name = "bitflags" 40 | version = "1.3.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 43 | 44 | [[package]] 45 | name = "bumpalo" 46 | version = "3.11.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 49 | 50 | [[package]] 51 | name = "cc" 52 | version = "1.0.74" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" 55 | 56 | [[package]] 57 | name = "cfg-if" 58 | version = "1.0.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 61 | 62 | [[package]] 63 | name = "chrono" 64 | version = "0.4.22" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" 67 | dependencies = [ 68 | "iana-time-zone", 69 | "js-sys", 70 | "num-integer", 71 | "num-traits", 72 | "wasm-bindgen", 73 | "winapi", 74 | ] 75 | 76 | [[package]] 77 | name = "clap" 78 | version = "2.33.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "826bf7bc84f9435630275cb8e802a4a0ec792b615969934bd16d42ffed10f207" 81 | dependencies = [ 82 | "ansi_term", 83 | "atty", 84 | "bitflags", 85 | "strsim 0.8.0", 86 | "textwrap 0.11.0", 87 | "unicode-width", 88 | "vec_map", 89 | ] 90 | 91 | [[package]] 92 | name = "clap" 93 | version = "3.2.23" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" 96 | dependencies = [ 97 | "atty", 98 | "bitflags", 99 | "clap_lex", 100 | "indexmap", 101 | "once_cell", 102 | "strsim 0.10.0", 103 | "termcolor", 104 | "textwrap 0.16.0", 105 | ] 106 | 107 | [[package]] 108 | name = "clap_lex" 109 | version = "0.2.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 112 | dependencies = [ 113 | "os_str_bytes", 114 | ] 115 | 116 | [[package]] 117 | name = "codespan-reporting" 118 | version = "0.11.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 121 | dependencies = [ 122 | "termcolor", 123 | "unicode-width", 124 | ] 125 | 126 | [[package]] 127 | name = "core-foundation-sys" 128 | version = "0.8.3" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 131 | 132 | [[package]] 133 | name = "cxx" 134 | version = "1.0.80" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" 137 | dependencies = [ 138 | "cc", 139 | "cxxbridge-flags", 140 | "cxxbridge-macro", 141 | "link-cplusplus", 142 | ] 143 | 144 | [[package]] 145 | name = "cxx-build" 146 | version = "1.0.80" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" 149 | dependencies = [ 150 | "cc", 151 | "codespan-reporting", 152 | "once_cell", 153 | "proc-macro2", 154 | "quote", 155 | "scratch", 156 | "syn", 157 | ] 158 | 159 | [[package]] 160 | name = "cxxbridge-flags" 161 | version = "1.0.80" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" 164 | 165 | [[package]] 166 | name = "cxxbridge-macro" 167 | version = "1.0.80" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" 170 | dependencies = [ 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "docopt" 178 | version = "1.1.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" 181 | dependencies = [ 182 | "lazy_static", 183 | "regex", 184 | "serde", 185 | "strsim 0.10.0", 186 | ] 187 | 188 | [[package]] 189 | name = "errno" 190 | version = "0.3.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 193 | dependencies = [ 194 | "errno-dragonfly", 195 | "libc", 196 | "windows-sys 0.48.0", 197 | ] 198 | 199 | [[package]] 200 | name = "errno-dragonfly" 201 | version = "0.1.2" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 204 | dependencies = [ 205 | "cc", 206 | "libc", 207 | ] 208 | 209 | [[package]] 210 | name = "hashbrown" 211 | version = "0.12.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 214 | 215 | [[package]] 216 | name = "heck" 217 | version = "0.3.3" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 220 | dependencies = [ 221 | "unicode-segmentation", 222 | ] 223 | 224 | [[package]] 225 | name = "hermit-abi" 226 | version = "0.1.19" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 229 | dependencies = [ 230 | "libc", 231 | ] 232 | 233 | [[package]] 234 | name = "hermit-abi" 235 | version = "0.3.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 238 | 239 | [[package]] 240 | name = "iana-time-zone" 241 | version = "0.1.53" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 244 | dependencies = [ 245 | "android_system_properties", 246 | "core-foundation-sys", 247 | "iana-time-zone-haiku", 248 | "js-sys", 249 | "wasm-bindgen", 250 | "winapi", 251 | ] 252 | 253 | [[package]] 254 | name = "iana-time-zone-haiku" 255 | version = "0.1.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 258 | dependencies = [ 259 | "cxx", 260 | "cxx-build", 261 | ] 262 | 263 | [[package]] 264 | name = "indexmap" 265 | version = "1.9.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 268 | dependencies = [ 269 | "autocfg", 270 | "hashbrown", 271 | ] 272 | 273 | [[package]] 274 | name = "io-lifetimes" 275 | version = "1.0.11" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 278 | dependencies = [ 279 | "hermit-abi 0.3.1", 280 | "libc", 281 | "windows-sys 0.48.0", 282 | ] 283 | 284 | [[package]] 285 | name = "is-terminal" 286 | version = "0.4.7" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 289 | dependencies = [ 290 | "hermit-abi 0.3.1", 291 | "io-lifetimes", 292 | "rustix", 293 | "windows-sys 0.48.0", 294 | ] 295 | 296 | [[package]] 297 | name = "js-sys" 298 | version = "0.3.60" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 301 | dependencies = [ 302 | "wasm-bindgen", 303 | ] 304 | 305 | [[package]] 306 | name = "lazy_static" 307 | version = "1.4.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 310 | 311 | [[package]] 312 | name = "libc" 313 | version = "0.2.137" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 316 | 317 | [[package]] 318 | name = "link-cplusplus" 319 | version = "1.0.7" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" 322 | dependencies = [ 323 | "cc", 324 | ] 325 | 326 | [[package]] 327 | name = "linux-raw-sys" 328 | version = "0.3.8" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 331 | 332 | [[package]] 333 | name = "log" 334 | version = "0.4.17" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 337 | dependencies = [ 338 | "cfg-if", 339 | ] 340 | 341 | [[package]] 342 | name = "num-integer" 343 | version = "0.1.45" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 346 | dependencies = [ 347 | "autocfg", 348 | "num-traits", 349 | ] 350 | 351 | [[package]] 352 | name = "num-traits" 353 | version = "0.2.15" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 356 | dependencies = [ 357 | "autocfg", 358 | ] 359 | 360 | [[package]] 361 | name = "once_cell" 362 | version = "1.14.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" 365 | 366 | [[package]] 367 | name = "os_str_bytes" 368 | version = "6.3.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" 371 | 372 | [[package]] 373 | name = "proc-macro-error" 374 | version = "1.0.4" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 377 | dependencies = [ 378 | "proc-macro-error-attr", 379 | "proc-macro2", 380 | "quote", 381 | "syn", 382 | "version_check", 383 | ] 384 | 385 | [[package]] 386 | name = "proc-macro-error-attr" 387 | version = "1.0.4" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 390 | dependencies = [ 391 | "proc-macro2", 392 | "quote", 393 | "version_check", 394 | ] 395 | 396 | [[package]] 397 | name = "proc-macro2" 398 | version = "1.0.47" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 401 | dependencies = [ 402 | "unicode-ident", 403 | ] 404 | 405 | [[package]] 406 | name = "quote" 407 | version = "1.0.21" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 410 | dependencies = [ 411 | "proc-macro2", 412 | ] 413 | 414 | [[package]] 415 | name = "regex" 416 | version = "1.6.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 419 | dependencies = [ 420 | "regex-syntax", 421 | ] 422 | 423 | [[package]] 424 | name = "regex-syntax" 425 | version = "0.6.27" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 428 | 429 | [[package]] 430 | name = "rustix" 431 | version = "0.37.3" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" 434 | dependencies = [ 435 | "bitflags", 436 | "errno", 437 | "io-lifetimes", 438 | "libc", 439 | "linux-raw-sys", 440 | "windows-sys 0.45.0", 441 | ] 442 | 443 | [[package]] 444 | name = "scratch" 445 | version = "1.0.2" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" 448 | 449 | [[package]] 450 | name = "serde" 451 | version = "1.0.147" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 454 | dependencies = [ 455 | "serde_derive", 456 | ] 457 | 458 | [[package]] 459 | name = "serde_derive" 460 | version = "1.0.147" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 463 | dependencies = [ 464 | "proc-macro2", 465 | "quote", 466 | "syn", 467 | ] 468 | 469 | [[package]] 470 | name = "stderrlog" 471 | version = "0.5.5-alpha.0" 472 | dependencies = [ 473 | "chrono", 474 | "clap 3.2.23", 475 | "docopt", 476 | "is-terminal", 477 | "libc", 478 | "log", 479 | "serde", 480 | "structopt", 481 | "termcolor", 482 | "thread_local", 483 | ] 484 | 485 | [[package]] 486 | name = "strsim" 487 | version = "0.8.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 490 | 491 | [[package]] 492 | name = "strsim" 493 | version = "0.10.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 496 | 497 | [[package]] 498 | name = "structopt" 499 | version = "0.3.26" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 502 | dependencies = [ 503 | "clap 2.33.4", 504 | "lazy_static", 505 | "structopt-derive", 506 | ] 507 | 508 | [[package]] 509 | name = "structopt-derive" 510 | version = "0.4.18" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 513 | dependencies = [ 514 | "heck", 515 | "proc-macro-error", 516 | "proc-macro2", 517 | "quote", 518 | "syn", 519 | ] 520 | 521 | [[package]] 522 | name = "syn" 523 | version = "1.0.103" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 526 | dependencies = [ 527 | "proc-macro2", 528 | "quote", 529 | "unicode-ident", 530 | ] 531 | 532 | [[package]] 533 | name = "termcolor" 534 | version = "1.1.3" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 537 | dependencies = [ 538 | "winapi-util", 539 | ] 540 | 541 | [[package]] 542 | name = "textwrap" 543 | version = "0.11.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 546 | dependencies = [ 547 | "unicode-width", 548 | ] 549 | 550 | [[package]] 551 | name = "textwrap" 552 | version = "0.16.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 555 | 556 | [[package]] 557 | name = "thread_local" 558 | version = "1.1.4" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 561 | dependencies = [ 562 | "once_cell", 563 | ] 564 | 565 | [[package]] 566 | name = "unicode-ident" 567 | version = "1.0.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 570 | 571 | [[package]] 572 | name = "unicode-segmentation" 573 | version = "1.10.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" 576 | 577 | [[package]] 578 | name = "unicode-width" 579 | version = "0.1.10" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 582 | 583 | [[package]] 584 | name = "vec_map" 585 | version = "0.8.2" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 588 | 589 | [[package]] 590 | name = "version_check" 591 | version = "0.9.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 594 | 595 | [[package]] 596 | name = "wasm-bindgen" 597 | version = "0.2.83" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 600 | dependencies = [ 601 | "cfg-if", 602 | "wasm-bindgen-macro", 603 | ] 604 | 605 | [[package]] 606 | name = "wasm-bindgen-backend" 607 | version = "0.2.83" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 610 | dependencies = [ 611 | "bumpalo", 612 | "log", 613 | "once_cell", 614 | "proc-macro2", 615 | "quote", 616 | "syn", 617 | "wasm-bindgen-shared", 618 | ] 619 | 620 | [[package]] 621 | name = "wasm-bindgen-macro" 622 | version = "0.2.83" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 625 | dependencies = [ 626 | "quote", 627 | "wasm-bindgen-macro-support", 628 | ] 629 | 630 | [[package]] 631 | name = "wasm-bindgen-macro-support" 632 | version = "0.2.83" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 635 | dependencies = [ 636 | "proc-macro2", 637 | "quote", 638 | "syn", 639 | "wasm-bindgen-backend", 640 | "wasm-bindgen-shared", 641 | ] 642 | 643 | [[package]] 644 | name = "wasm-bindgen-shared" 645 | version = "0.2.83" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 648 | 649 | [[package]] 650 | name = "winapi" 651 | version = "0.3.9" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 654 | dependencies = [ 655 | "winapi-i686-pc-windows-gnu", 656 | "winapi-x86_64-pc-windows-gnu", 657 | ] 658 | 659 | [[package]] 660 | name = "winapi-i686-pc-windows-gnu" 661 | version = "0.4.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 664 | 665 | [[package]] 666 | name = "winapi-util" 667 | version = "0.1.5" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 670 | dependencies = [ 671 | "winapi", 672 | ] 673 | 674 | [[package]] 675 | name = "winapi-x86_64-pc-windows-gnu" 676 | version = "0.4.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 679 | 680 | [[package]] 681 | name = "windows-sys" 682 | version = "0.45.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 685 | dependencies = [ 686 | "windows-targets 0.42.2", 687 | ] 688 | 689 | [[package]] 690 | name = "windows-sys" 691 | version = "0.48.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 694 | dependencies = [ 695 | "windows-targets 0.48.0", 696 | ] 697 | 698 | [[package]] 699 | name = "windows-targets" 700 | version = "0.42.2" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 703 | dependencies = [ 704 | "windows_aarch64_gnullvm 0.42.2", 705 | "windows_aarch64_msvc 0.42.2", 706 | "windows_i686_gnu 0.42.2", 707 | "windows_i686_msvc 0.42.2", 708 | "windows_x86_64_gnu 0.42.2", 709 | "windows_x86_64_gnullvm 0.42.2", 710 | "windows_x86_64_msvc 0.42.2", 711 | ] 712 | 713 | [[package]] 714 | name = "windows-targets" 715 | version = "0.48.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 718 | dependencies = [ 719 | "windows_aarch64_gnullvm 0.48.0", 720 | "windows_aarch64_msvc 0.48.0", 721 | "windows_i686_gnu 0.48.0", 722 | "windows_i686_msvc 0.48.0", 723 | "windows_x86_64_gnu 0.48.0", 724 | "windows_x86_64_gnullvm 0.48.0", 725 | "windows_x86_64_msvc 0.48.0", 726 | ] 727 | 728 | [[package]] 729 | name = "windows_aarch64_gnullvm" 730 | version = "0.42.2" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 733 | 734 | [[package]] 735 | name = "windows_aarch64_gnullvm" 736 | version = "0.48.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 739 | 740 | [[package]] 741 | name = "windows_aarch64_msvc" 742 | version = "0.42.2" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 745 | 746 | [[package]] 747 | name = "windows_aarch64_msvc" 748 | version = "0.48.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 751 | 752 | [[package]] 753 | name = "windows_i686_gnu" 754 | version = "0.42.2" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 757 | 758 | [[package]] 759 | name = "windows_i686_gnu" 760 | version = "0.48.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 763 | 764 | [[package]] 765 | name = "windows_i686_msvc" 766 | version = "0.42.2" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 769 | 770 | [[package]] 771 | name = "windows_i686_msvc" 772 | version = "0.48.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 775 | 776 | [[package]] 777 | name = "windows_x86_64_gnu" 778 | version = "0.42.2" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 781 | 782 | [[package]] 783 | name = "windows_x86_64_gnu" 784 | version = "0.48.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 787 | 788 | [[package]] 789 | name = "windows_x86_64_gnullvm" 790 | version = "0.42.2" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 793 | 794 | [[package]] 795 | name = "windows_x86_64_gnullvm" 796 | version = "0.48.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 799 | 800 | [[package]] 801 | name = "windows_x86_64_msvc" 802 | version = "0.42.2" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 805 | 806 | [[package]] 807 | name = "windows_x86_64_msvc" 808 | version = "0.48.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 811 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Doug Goldstein "] 3 | description = "Logger that logs to stderr based on verbosity specified" 4 | documentation = "https://docs.rs/stderrlog/" 5 | homepage = "https://github.com/cardoe/stderrlog-rs" 6 | keywords = ["log", "loglevel", "logging", "stderr", "stderrlog"] 7 | categories = ["development-tools::debugging"] 8 | license = "MIT OR Apache-2.0" 9 | name = "stderrlog" 10 | readme = "README.md" 11 | repository = "https://github.com/cardoe/stderrlog-rs" 12 | version = "0.6.0" 13 | edition = "2018" 14 | # This metadata field is ignored by the actual MSRV, but it is useful for newer 15 | # Rust versions and crates.io to know the MSRV 16 | rust-version = "1.48" 17 | 18 | [badges] 19 | appveyor = { repository = "cardoe/stderrlog-rs", branch = "master", service = "github" } 20 | travis-ci = { repository = "cardoe/stderrlog-rs", branch = "master" } 21 | is-it-maintained-issue-resolution = { repository = "cardoe/stderrlog-rs" } 22 | is-it-maintained-open-issues = { repository = "cardoe/stderrlog-rs" } 23 | maintenance = { status = "passively-maintained" } 24 | 25 | [dependencies] 26 | is-terminal = "0.4.7" 27 | chrono = { version = "0.4.10", optional = true, default-features = false, features = ["clock", "std", "wasmbind"] } 28 | log = { version = "0.4.11", features = ["std"] } 29 | termcolor = "~1.1" 30 | thread_local = "~1.1" 31 | 32 | [dev-dependencies] 33 | clap = { version = "3", features = ["cargo"] } 34 | docopt = "1.1" 35 | serde = { version = "1.0", features = ["derive"] } 36 | libc = "0.2.18" 37 | structopt = "0.3.20" 38 | 39 | [features] 40 | default = ["timestamps"] 41 | timestamps = ["chrono"] 42 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Doug Goldstein 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://travis-ci.org/cardoe/stderrlog-rs.svg?branch=master)](https://travis-ci.org/cardoe/stderrlog-rs) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/no8slwtoy5va0w4g/branch/master?svg=true)](https://ci.appveyor.com/project/cardoe/stderrlog-rs/branch/master) 3 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/cardoe/stderrlog-rs.svg)](http://isitmaintained.com/project/cardoe/stderrlog-rs "Average time to resolve an issue") 4 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/cardoe/stderrlog-rs.svg)](http://isitmaintained.com/project/cardoe/stderrlog-rs "Percentage of issues still open") 5 | [![Rust version]( https://img.shields.io/badge/rust-1.48.0+-blue.svg)]() 6 | [![Documentation](https://docs.rs/stderrlog/badge.svg)](https://docs.rs/stderrlog) 7 | [![Latest version](https://img.shields.io/crates/v/stderrlog.svg)](https://crates.io/crates/stderrlog) 8 | [![All downloads](https://img.shields.io/crates/d/stderrlog.svg)](https://crates.io/crates/stderrlog) 9 | [![Downloads of latest version](https://img.shields.io/crates/dv/stderrlog.svg)](https://crates.io/crates/stderrlog) 10 | 11 | Logger that aims to provide a simple case of 12 | [env_logger](https://crates.io/crates/env_logger) that just 13 | logs to `stderr` based on verbosity. 14 | 15 | ## Documentation 16 | 17 | For a working example for [StructOpt](https::/crates.io/crates/structopt), 18 | [clap](https://crates.io/crates/clap), and 19 | [docopt](https://crates.io/crates/docopt) please see the 20 | [crate level documentation](https://docs.rs/stderrlog/*/stderrlog/). 21 | 22 | For example binaries showing how 23 | [module level logging](https://github.com/cardoe/stderrlog-rs/tree/master/examples/large-example) works, please see the `large-example` crate in `examples/`. 24 | 25 | ## Supported Versions 26 | 27 | * `stderrlog` 0.6.x supports 28 | 1) Rust 1.48.0 and newer 29 | 2) `log` >= 0.4.11 30 | * `stderrlog` 0.5.x supports 31 | 1) Rust 1.36.0 and newer 32 | 2) `log` >= 0.4.11 33 | * `stderrlog` 0.4.x supports 34 | 1) Rust 1.16.0 and newer 35 | 2) `log` >= 0.4.1 36 | * `stderrlog` 0.3.x supports 37 | 1) Rust 1.16.0 and newer 38 | 2) `log` 0.3.x 39 | * `stderrlog` 0.2.x supports 40 | 1) Rust 1.13.0 and newer 41 | 2) `log` >= 0.3.0, < 0.3.9 42 | 43 | ## Usage 44 | 45 | Add this to your `Cargo.toml`: 46 | 47 | ```toml 48 | [dependencies] 49 | stderrlog = "0.4" 50 | ``` 51 | 52 | and this to your main(): 53 | 54 | ```rust 55 | stderrlog::new().verbosity(args.flag_v).quiet(args.flag_q).init().unwrap(); 56 | ``` 57 | 58 | where your args struct is defined as: 59 | 60 | ```rust 61 | struct Args { 62 | flag_v: usize, 63 | flag_q: bool, 64 | ... 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.5.x | :white_check_mark: | 8 | | < 0.5 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Please email the address listed at [https://github.com/cardoe](https://github.com/cardoe) 13 | and include the name of the crate and the fact that its security related in the subject. 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | # When this was added there were revocation check failures when using the 3 | # libcurl backend as libcurl checks by default, but rustup doesn't provide the 4 | # switch to turn this off. Switch to Hyper which looks to not check for 5 | # revocation by default like libcurl does. 6 | RUSTUP_USE_HYPER: 1 7 | CARGO_HTTP_CHECK_REVOKE: false 8 | matrix: 9 | - TARGET: x86_64-pc-windows-gnu 10 | MSYS2_BITS: 64 11 | - TARGET: i686-pc-windows-gnu 12 | MSYS2_BITS: 32 13 | - TARGET: x86_64-pc-windows-msvc 14 | - TARGET: i686-pc-windows-msvc 15 | install: 16 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 17 | - rustup-init.exe -y --default-host %TARGET% 18 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 19 | - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin 20 | - rustc -V 21 | - cargo -V 22 | 23 | build: false 24 | 25 | test_script: 26 | - cargo test --target %TARGET% 27 | -------------------------------------------------------------------------------- /benches/common/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | 3 | use super::{init_logger, util::with_redirected_stderr}; 4 | use log::*; 5 | use std::panic; 6 | use test::Bencher; 7 | 8 | #[bench] 9 | fn simple_string(b: &mut Bencher) { 10 | init_logger(); 11 | with_redirected_stderr(panic::AssertUnwindSafe(|| { 12 | b.iter(|| { 13 | debug!("Whoami"); 14 | }); 15 | })); 16 | } 17 | 18 | #[bench] 19 | fn complex_format(b: &mut Bencher) { 20 | init_logger(); 21 | with_redirected_stderr(panic::AssertUnwindSafe(|| { 22 | b.iter(|| { 23 | debug!( 24 | "{}, {:#?}, {:b}", 25 | 0.1f64, 26 | vec![99, 1, 5, 100, 1, 0, 8], 27 | 0xffb1aa 28 | ) 29 | }) 30 | })); 31 | } 32 | 33 | #[bench] 34 | fn long_line(b: &mut Bencher) { 35 | let mut long_data = vec![]; 36 | for _ in 0..1_000 { 37 | long_data.push("this is an item in a long list"); 38 | } 39 | 40 | init_logger(); 41 | with_redirected_stderr(panic::AssertUnwindSafe(|| { 42 | b.iter(|| { 43 | debug!("long data: {:?}", long_data); 44 | }) 45 | })); 46 | } 47 | -------------------------------------------------------------------------------- /benches/junk_modules.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod common; 4 | mod util; 5 | 6 | use std::sync; 7 | 8 | static INIT_LOGGER: sync::Once = sync::ONCE_INIT; 9 | 10 | fn init_logger() { 11 | INIT_LOGGER.call_once(|| { 12 | let mut other_modules = Vec::new(); 13 | for i in 0..10_000 { 14 | other_modules.push(format!("other::mod{}", i)); 15 | } 16 | other_modules.push(format!("{}::common", module_path!())); 17 | stderrlog::StdErrLog::new() 18 | .timestamp(stderrlog::Timestamp::Second) 19 | .verbosity(10) 20 | .modules(other_modules) 21 | .init() 22 | .unwrap(); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /benches/no_color.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod common; 4 | mod util; 5 | 6 | use std::sync; 7 | 8 | static INIT_LOGGER: sync::Once = sync::ONCE_INIT; 9 | 10 | fn init_logger() { 11 | INIT_LOGGER.call_once(|| { 12 | stderrlog::StdErrLog::new() 13 | .timestamp(stderrlog::Timestamp::Second) 14 | .verbosity(10) 15 | .color(termcolor::ColorChoice::Never) 16 | .module(module_path!()) 17 | .init() 18 | .unwrap(); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /benches/quiet.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod common; 4 | mod util; 5 | 6 | use std::sync; 7 | 8 | static INIT_LOGGER: sync::Once = sync::ONCE_INIT; 9 | 10 | fn init_logger() { 11 | INIT_LOGGER.call_once(|| { 12 | stderrlog::StdErrLog::new() 13 | .timestamp(stderrlog::Timestamp::Second) 14 | .verbosity(10) 15 | .quiet(true) 16 | .module(module_path!()) 17 | .init() 18 | .unwrap(); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /benches/real_calls.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod common; 4 | mod util; 5 | 6 | use std::sync; 7 | 8 | static INIT_LOGGER: sync::Once = sync::ONCE_INIT; 9 | 10 | fn init_logger() { 11 | INIT_LOGGER.call_once(|| { 12 | stderrlog::StdErrLog::new() 13 | .timestamp(stderrlog::Timestamp::Second) 14 | .verbosity(10) 15 | .module(module_path!()) 16 | .init() 17 | .unwrap(); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /benches/skip_module.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod common; 4 | mod util; 5 | 6 | use std::sync; 7 | 8 | static INIT_LOGGER: sync::Once = sync::ONCE_INIT; 9 | 10 | fn init_logger() { 11 | INIT_LOGGER.call_once(|| { 12 | let mut other_modules = Vec::new(); 13 | for i in 0..10_000 { 14 | other_modules.push(format!("other::mod{}", i)); 15 | } 16 | stderrlog::StdErrLog::new() 17 | .timestamp(stderrlog::Timestamp::Second) 18 | .verbosity(10) 19 | .modules(other_modules) 20 | .init() 21 | .unwrap(); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /benches/util/mod.rs: -------------------------------------------------------------------------------- 1 | use libc; 2 | use std::fs::File; 3 | use std::os::unix::io::AsRawFd; 4 | use std::panic; 5 | 6 | pub fn with_redirected_stderr(f: F) -> T 7 | where 8 | F: FnOnce() -> T, 9 | F: panic::UnwindSafe, 10 | { 11 | let file = File::create("/dev/null").unwrap(); 12 | let fd = file.as_raw_fd(); 13 | let orig_fd = unsafe { 14 | let orig_fd = libc::dup(2); 15 | assert!(orig_fd >= 0); 16 | let rc = libc::dup2(fd, 2); 17 | assert!(rc >= 0); 18 | orig_fd 19 | }; 20 | drop(file); 21 | let result = panic::catch_unwind(f); 22 | unsafe { 23 | libc::dup2(orig_fd, 2); 24 | libc::close(orig_fd); 25 | } 26 | match result { 27 | Ok(t) => t, 28 | Err(e) => panic::resume_unwind(e), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/clap.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use clap::{crate_version, App, Arg}; 10 | use log::*; 11 | use std::str::FromStr; 12 | 13 | fn main() { 14 | let m = App::new("stderrlog example") 15 | .version(crate_version!()) 16 | .arg( 17 | Arg::with_name("verbosity") 18 | .short('v') 19 | .multiple(true) 20 | .help("Increase message verbosity"), 21 | ) 22 | .arg( 23 | Arg::with_name("quiet") 24 | .short('q') 25 | .help("Silence all output"), 26 | ) 27 | .arg( 28 | Arg::with_name("timestamp") 29 | .short('t') 30 | .help("prepend log lines with a timestamp") 31 | .takes_value(true) 32 | .possible_values(&["none", "sec", "ms", "us", "ns"]), 33 | ) 34 | .get_matches(); 35 | 36 | let verbose = m.occurrences_of("verbosity") as usize; 37 | let quiet = m.is_present("quiet"); 38 | let ts = m 39 | .value_of("timestamp") 40 | .map(|v| { 41 | stderrlog::Timestamp::from_str(v).unwrap_or_else(|_| { 42 | clap::Error::raw( 43 | clap::ErrorKind::InvalidValue, 44 | "invalid value for 'timestamp'", 45 | ) 46 | .exit() 47 | }) 48 | }) 49 | .unwrap_or(stderrlog::Timestamp::Off); 50 | 51 | stderrlog::new() 52 | .module(module_path!()) 53 | .quiet(quiet) 54 | .verbosity(verbose) 55 | .timestamp(ts) 56 | .init() 57 | .unwrap(); 58 | trace!("trace message"); 59 | debug!("debug message"); 60 | info!("info message"); 61 | warn!("warn message"); 62 | error!("error message"); 63 | } 64 | -------------------------------------------------------------------------------- /examples/docopt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use docopt::Docopt; 10 | use log::*; 11 | use serde::Deserialize; 12 | use std::env; 13 | 14 | const USAGE: &'static str = " 15 | Usage: 16 | stderrtest [-q] [-v...] 17 | "; 18 | 19 | #[derive(Deserialize)] 20 | struct Args { 21 | flag_v: usize, 22 | flag_q: bool, 23 | } 24 | 25 | fn main() { 26 | let args: Args = Docopt::new(USAGE) 27 | .and_then(|d| d.argv(env::args().into_iter()).deserialize()) 28 | .unwrap_or_else(|e| e.exit()); 29 | 30 | stderrlog::new() 31 | .verbosity(args.flag_v) 32 | .quiet(args.flag_q) 33 | .init() 34 | .unwrap(); 35 | 36 | error!("error msg"); 37 | warn!("warning msg"); 38 | info!("info msg"); 39 | debug!("debug msg"); 40 | trace!("trace msg"); 41 | } 42 | -------------------------------------------------------------------------------- /examples/large-example/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /examples/large-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "large-example" 3 | version = "0.1.0" 4 | authors = ["Doug Goldstein "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4.11" 9 | stderrlog = { path = "../../" } 10 | structopt = "0.3" 11 | -------------------------------------------------------------------------------- /examples/large-example/src/bar.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use log::*; 10 | 11 | pub fn barfn() { 12 | println!("Entered {}", module_path!()); 13 | trace!("{} - trace message", module_path!()); 14 | debug!("{} - debug message", module_path!()); 15 | info!("{} - info message", module_path!()); 16 | warn!("{} - warn message", module_path!()); 17 | error!("{} - warn message", module_path!()); 18 | } 19 | -------------------------------------------------------------------------------- /examples/large-example/src/bin/another.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use large_example::bar; 10 | use log::*; 11 | use structopt::StructOpt; 12 | 13 | /// A basic example 14 | #[derive(StructOpt, Debug)] 15 | #[structopt()] 16 | struct Opt { 17 | /// Silence all output 18 | #[structopt(short = "q", long = "quiet")] 19 | quiet: bool, 20 | /// Verbose mode (-v, -vv, -vvv, etc.) 21 | #[structopt(short = "v", long = "verbose", parse(from_occurrences))] 22 | verbose: usize, 23 | /// Allow module to log 24 | #[structopt(short = "m", long = "module")] 25 | modules: Vec, 26 | /// Timestamp (sec, ms, ns, none) 27 | #[structopt(short = "t", long = "timestamp")] 28 | ts: Option, 29 | } 30 | 31 | fn main() { 32 | let opt = Opt::from_args(); 33 | 34 | stderrlog::new() 35 | .module(module_path!()) 36 | .quiet(opt.quiet) 37 | .verbosity(opt.verbose) 38 | .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off)) 39 | .modules(opt.modules) 40 | .init() 41 | .unwrap(); 42 | 43 | large_example::libfn(); 44 | bar::barfn(); 45 | 46 | println!("back to main()"); 47 | trace!("{} - trace message", module_path!()); 48 | debug!("{} - debug message", module_path!()); 49 | info!("{} - info message", module_path!()); 50 | warn!("{} - warn message", module_path!()); 51 | error!("{} - error message", module_path!()); 52 | 53 | println!("Some modules to try:"); 54 | println!(" -m large_example"); 55 | println!(" -m large_example::foo"); 56 | println!(" -m large_example::bar"); 57 | } 58 | -------------------------------------------------------------------------------- /examples/large-example/src/bin/yet/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use large_example::bar; 10 | use log::*; 11 | use structopt::StructOpt; 12 | 13 | /// A basic example 14 | #[derive(StructOpt, Debug)] 15 | #[structopt()] 16 | struct Opt { 17 | /// Silence all output 18 | #[structopt(short = "q", long = "quiet")] 19 | quiet: bool, 20 | /// Verbose mode (-v, -vv, -vvv, etc.) 21 | #[structopt(short = "v", long = "verbose", parse(from_occurrences))] 22 | verbose: usize, 23 | /// Allow module to log 24 | #[structopt(short = "m", long = "module")] 25 | modules: Vec, 26 | /// Timestamp (sec, ms, ns, none) 27 | #[structopt(short = "t", long = "timestamp")] 28 | ts: Option, 29 | } 30 | 31 | fn main() { 32 | let opt = Opt::from_args(); 33 | 34 | stderrlog::new() 35 | .module(module_path!()) 36 | .quiet(opt.quiet) 37 | .verbosity(opt.verbose) 38 | .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off)) 39 | .modules(opt.modules) 40 | .init() 41 | .unwrap(); 42 | 43 | large_example::libfn(); 44 | bar::barfn(); 45 | 46 | println!("back to main()"); 47 | trace!("{} - trace message", module_path!()); 48 | debug!("{} - debug message", module_path!()); 49 | info!("{} - info message", module_path!()); 50 | warn!("{} - warn message", module_path!()); 51 | error!("{} - error message", module_path!()); 52 | 53 | println!("Some modules to try:"); 54 | println!(" -m large_example"); 55 | println!(" -m large_example::foo"); 56 | println!(" -m large_example::bar"); 57 | } 58 | -------------------------------------------------------------------------------- /examples/large-example/src/foo.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use log::*; 10 | 11 | pub fn foofn() { 12 | println!("Entered {}", module_path!()); 13 | trace!("{} - trace message", module_path!()); 14 | debug!("{} - debug message", module_path!()); 15 | info!("{} - info message", module_path!()); 16 | warn!("{} - warn message", module_path!()); 17 | error!("{} - warn message", module_path!()); 18 | } 19 | -------------------------------------------------------------------------------- /examples/large-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | pub mod bar; 10 | mod foo; 11 | 12 | use log::*; 13 | 14 | pub fn libfn() { 15 | println!("Entered {}", module_path!()); 16 | trace!("{} - trace message", module_path!()); 17 | debug!("{} - debug message", module_path!()); 18 | info!("{} - info message", module_path!()); 19 | warn!("{} - warn message", module_path!()); 20 | error!("{} - warn message", module_path!()); 21 | 22 | foo::foofn(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/large-example/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use large_example::bar; 10 | use log::*; 11 | use structopt::StructOpt; 12 | 13 | /// A basic example 14 | #[derive(StructOpt, Debug)] 15 | #[structopt()] 16 | struct Opt { 17 | /// Silence all output 18 | #[structopt(short = "q", long = "quiet")] 19 | quiet: bool, 20 | /// Verbose mode (-v, -vv, -vvv, etc.) 21 | #[structopt(short = "v", long = "verbose", parse(from_occurrences))] 22 | verbose: usize, 23 | /// Allow module to log 24 | #[structopt(short = "m", long = "module")] 25 | modules: Vec, 26 | /// Timestamp (sec, ms, us, ns, none) 27 | #[structopt(short = "t", long = "timestamp")] 28 | ts: Option, 29 | } 30 | 31 | fn main() { 32 | let opt = Opt::from_args(); 33 | 34 | stderrlog::new() 35 | .quiet(opt.quiet) 36 | .verbosity(opt.verbose) 37 | .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off)) 38 | .modules(opt.modules) 39 | .init() 40 | .unwrap(); 41 | 42 | large_example::libfn(); 43 | bar::barfn(); 44 | 45 | println!("back to main()"); 46 | trace!("{} - trace message", module_path!()); 47 | debug!("{} - debug message", module_path!()); 48 | info!("{} - info message", module_path!()); 49 | warn!("{} - warn message", module_path!()); 50 | error!("{} - error message", module_path!()); 51 | 52 | println!("Some modules to try:"); 53 | println!(" -m large_example"); 54 | println!(" -m large_example::foo"); 55 | println!(" -m large_example::bar"); 56 | } 57 | -------------------------------------------------------------------------------- /examples/structopt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use log::*; 10 | use structopt::StructOpt; 11 | 12 | /// A basic example 13 | #[derive(StructOpt, Debug)] 14 | #[structopt()] 15 | struct Opt { 16 | /// Silence all output 17 | #[structopt(short = "q", long = "quiet")] 18 | quiet: bool, 19 | /// Verbose mode (-v, -vv, -vvv, etc.) 20 | #[structopt(short = "v", long = "verbose", parse(from_occurrences))] 21 | verbose: usize, 22 | /// Timestamp (sec, ms, us, ns, none) 23 | #[structopt(short = "t", long = "timestamp")] 24 | ts: Option, 25 | } 26 | 27 | fn main() { 28 | let opt = Opt::from_args(); 29 | 30 | stderrlog::new() 31 | .module(module_path!()) 32 | .quiet(opt.quiet) 33 | .verbosity(opt.verbose) 34 | .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off)) 35 | .init() 36 | .unwrap(); 37 | 38 | trace!("{} - trace message", module_path!()); 39 | debug!("{} - debug message", module_path!()); 40 | info!("{} - info message", module_path!()); 41 | warn!("{} - warn message", module_path!()); 42 | error!("{} - error message", module_path!()); 43 | } 44 | -------------------------------------------------------------------------------- /examples/timestamp.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_version, App, Arg}; 2 | use log::*; 3 | 4 | fn main() { 5 | let m = App::new("stderrlog example") 6 | .version(crate_version!()) 7 | .arg( 8 | Arg::with_name("verbosity") 9 | .short('v') 10 | .multiple_occurrences(true) 11 | .help("Increase message verbosity"), 12 | ) 13 | .arg( 14 | Arg::with_name("quiet") 15 | .short('q') 16 | .help("Silence all output"), 17 | ) 18 | .arg( 19 | Arg::with_name("timestamp") 20 | .short('t') 21 | .help("prepend log lines with a timestamp") 22 | .takes_value(true) 23 | .possible_values(&["none", "sec", "ms", "us", "ns"]), 24 | ) 25 | .get_matches(); 26 | 27 | let verbose = m.occurrences_of("verbosity") as usize; 28 | let quiet = m.is_present("quiet"); 29 | let ts = match m.value_of("timestamp") { 30 | Some("ns") => stderrlog::Timestamp::Nanosecond, 31 | Some("ms") => stderrlog::Timestamp::Millisecond, 32 | Some("us") => stderrlog::Timestamp::Microsecond, 33 | Some("sec") => stderrlog::Timestamp::Second, 34 | Some("none") | None => stderrlog::Timestamp::Off, 35 | Some(_) => clap::Error::raw( 36 | clap::ErrorKind::InvalidValue, 37 | "invalid value for 'timestamp'", 38 | ) 39 | .exit(), 40 | }; 41 | 42 | stderrlog::new() 43 | .module(module_path!()) 44 | .quiet(quiet) 45 | .verbosity(verbose) 46 | .timestamp(ts) 47 | .init() 48 | .unwrap(); 49 | trace!("trace message"); 50 | debug!("debug message"); 51 | info!("info message"); 52 | warn!("warn message"); 53 | error!("error message"); 54 | } 55 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_strings = false 2 | reorder_imports = true 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 Doug Goldstein 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![allow(clippy::needless_doctest_main)] 10 | #![doc(html_root_url = "https://docs.rs/stderrlog/0.6.0")] 11 | 12 | //! A simple logger to provide semantics similar to what is expected 13 | //! of most UNIX utilities by logging to stderr and the higher the 14 | //! verbosity the higher the log level. It supports the 15 | //! ability to provide timestamps at different granularities. As 16 | //! well as colorizing the different log levels. 17 | //! 18 | //! ## Simple Use Case 19 | //! 20 | //! ```rust 21 | //! use log::*; 22 | //! 23 | //! fn main() { 24 | //! stderrlog::new().module(module_path!()).init().unwrap(); 25 | //! 26 | //! error!("some failure"); 27 | //! 28 | //! // ... 29 | //! } 30 | //! ``` 31 | //! 32 | //! # StructOpt Example 33 | //! 34 | //! ``` 35 | //! use log::*; 36 | //! use structopt::StructOpt; 37 | //! 38 | //! /// A StructOpt example 39 | //! #[derive(StructOpt, Debug)] 40 | //! #[structopt()] 41 | //! struct Opt { 42 | //! /// Silence all output 43 | //! #[structopt(short = "q", long = "quiet")] 44 | //! quiet: bool, 45 | //! /// Verbose mode (-v, -vv, -vvv, etc) 46 | //! #[structopt(short = "v", long = "verbose", parse(from_occurrences))] 47 | //! verbose: usize, 48 | //! /// Timestamp (sec, ms, ns, none) 49 | //! #[structopt(short = "t", long = "timestamp")] 50 | //! ts: Option, 51 | //! } 52 | //! 53 | //! fn main() { 54 | //! let opt = Opt::from_args(); 55 | //! 56 | //! stderrlog::new() 57 | //! .module(module_path!()) 58 | //! .quiet(opt.quiet) 59 | //! .verbosity(opt.verbose) 60 | //! .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off)) 61 | //! .init() 62 | //! .unwrap(); 63 | //! trace!("trace message"); 64 | //! debug!("debug message"); 65 | //! info!("info message"); 66 | //! warn!("warn message"); 67 | //! error!("error message"); 68 | //! } 69 | //! ``` 70 | //! 71 | //! ## docopt Example 72 | //! 73 | //! ```rust 74 | //! use log::*; 75 | //! use docopt::Docopt; 76 | //! use serde::Deserialize; 77 | //! 78 | //! const USAGE: &'static str = " 79 | //! Usage: program [-q] [-v...] 80 | //! "; 81 | //! 82 | //! #[derive(Debug, Deserialize)] 83 | //! struct Args { 84 | //! flag_q: bool, 85 | //! flag_v: usize, 86 | //! } 87 | //! 88 | //! fn main() { 89 | //! let args: Args = Docopt::new(USAGE) 90 | //! .and_then(|d| d.deserialize()) 91 | //! .unwrap_or_else(|e| e.exit()); 92 | //! 93 | //! stderrlog::new() 94 | //! .module(module_path!()) 95 | //! .quiet(args.flag_q) 96 | //! .timestamp(stderrlog::Timestamp::Second) 97 | //! .verbosity(args.flag_v) 98 | //! .init() 99 | //! .unwrap(); 100 | //! trace!("trace message"); 101 | //! debug!("debug message"); 102 | //! info!("info message"); 103 | //! warn!("warn message"); 104 | //! error!("error message"); 105 | //! 106 | //! // ... 107 | //! } 108 | //! ``` 109 | //! 110 | //! # clap Example 111 | //! 112 | //! ``` 113 | //! use clap::{Arg, App, crate_version}; 114 | //! use log::*; 115 | //! use std::str::FromStr; 116 | //! 117 | //! fn main() { 118 | //! let m = App::new("stderrlog example") 119 | //! .version(crate_version!()) 120 | //! .arg(Arg::with_name("verbosity") 121 | //! .short('v') 122 | //! .takes_value(true) 123 | //! .multiple(true) 124 | //! .help("Increase message verbosity")) 125 | //! .arg(Arg::with_name("quiet") 126 | //! .short('q') 127 | //! .help("Silence all output")) 128 | //! .arg(Arg::with_name("timestamp") 129 | //! .short('t') 130 | //! .help("prepend log lines with a timestamp") 131 | //! .takes_value(true) 132 | //! .possible_values(&["none", "sec", "ms", "ns"])) 133 | //! .get_matches(); 134 | //! 135 | //! let verbose = m.occurrences_of("verbosity") as usize; 136 | //! let quiet = m.is_present("quiet"); 137 | //! let ts = m.value_of("timestamp").map(|v| { 138 | //! stderrlog::Timestamp::from_str(v).unwrap_or_else(|_| { 139 | //! clap::Error::raw(clap::ErrorKind::InvalidValue, "invalid value for 'timestamp'").exit() 140 | //! }) 141 | //! }).unwrap_or(stderrlog::Timestamp::Off); 142 | //! 143 | //! stderrlog::new() 144 | //! .module(module_path!()) 145 | //! .quiet(quiet) 146 | //! .verbosity(verbose) 147 | //! .timestamp(ts) 148 | //! .init() 149 | //! .unwrap(); 150 | //! trace!("trace message"); 151 | //! debug!("debug message"); 152 | //! info!("info message"); 153 | //! warn!("warn message"); 154 | //! error!("error message"); 155 | //! } 156 | //! ``` 157 | //! 158 | //! ### `log` Compatibility 159 | //! 160 | //! The 0.5.x versions of `stderrlog` aim to provide compatibility with 161 | //! applications using `log` >= 0.4.11. 162 | //! 163 | //! ### Rust Compatibility 164 | //! 165 | //! `stderrlog` is serious about backwards compat. `stderrlog` 166 | //! pins the minimum required version of Rust in the CI build. 167 | //! Bumping the minimum version of Rust is a minor breaking 168 | //! change and requires a minor version to be bumped. 169 | //! 170 | //! The minimum supported Rust version for this release is 1.48.0. 171 | //! 172 | //! ### Module Level Logging 173 | //! 174 | //! `stderrlog` has the ability to limit the components which can log. 175 | //! Many crates use [log](https://docs.rs/log/*/log/) but you may not 176 | //! want their output in your application. For example 177 | //! [hyper](https://docs.rs/hyper/*/hyper/) makes heavy use of log but 178 | //! when your application receives `-vvvvv` to enable the `trace!()` 179 | //! messages you don't want the output of `hyper`'s `trace!()` level. 180 | //! 181 | //! To support this `stderrlog` includes a `module()` method allowing 182 | //! you to specify the modules that are allowed to log. The examples 183 | //! above use the `module_path!()` macro to enable logging only for 184 | //! the binary itself but none of its dependencies. To enable logging 185 | //! from extra crates just add another call to `module()` with the 186 | //! name of the crate. To enable logging for only a module within 187 | //! that crate specify `crate::module` to `module()`. crates and 188 | //! modules will be named the same way would would include them in 189 | //! source code with `use` (e.g. `some-crate` would be `some_crate`). 190 | //! 191 | //! For a good example of how the module level logging works see the 192 | //! [large-example 193 | //! crate](https://github.com/cardoe/stderrlog-rs/tree/master/examples/large-example) 194 | //! under examples, you'll want to run the 195 | //! following binaries to see all the examples: 196 | //! 197 | //! - `cargo run --bin large-example --` 198 | //! - `cargo run --bin another --` 199 | //! - `cargo run --bin yet --` 200 | //! 201 | //! ### Features 202 | //! 203 | //! `stderrlog` has the following default crate features, which can be disabled 204 | //! to reduce the number of dependencies: 205 | //! 206 | //! - `timestamps`: Provides support for log timestamp prefixes (uses the `chrono` crate). 207 | 208 | #[cfg(feature = "timestamps")] 209 | use chrono::Local; 210 | use is_terminal::IsTerminal; 211 | use log::{Level, LevelFilter, Log, Metadata, Record}; 212 | use std::cell::RefCell; 213 | use std::fmt; 214 | use std::io::{self, Write}; 215 | #[cfg(feature = "timestamps")] 216 | use std::str::FromStr; 217 | use termcolor::{Color, ColorSpec, StandardStream, WriteColor}; 218 | 219 | pub use termcolor::ColorChoice; 220 | use thread_local::ThreadLocal; 221 | 222 | /// State of the timestamping in the logger. 223 | #[cfg(feature = "timestamps")] 224 | #[derive(Clone, Copy, Debug)] 225 | pub enum Timestamp { 226 | /// Disable timestamping of log messages 227 | Off, 228 | /// Timestamp with second granularity 229 | Second, 230 | /// Timestamp with millisecond granularity 231 | Millisecond, 232 | /// Timestamp with microsecond granularity 233 | Microsecond, 234 | /// Timestamp with nanosecond granularity 235 | Nanosecond, 236 | } 237 | 238 | /// Provides a quick conversion of the following: 239 | /// 240 | /// - "sec" -> `Timestamp::Second` 241 | /// - "ms" -> `Timestamp::Millisecond` 242 | /// - "us" -> `Timestamp::Microsecond` 243 | /// - "ns" -> `Timestamp::Nanosecond` 244 | /// - "none" | "off" -> `Timestamp::Off` 245 | /// 246 | /// This is provided as a helper for argument parsers 247 | #[cfg(feature = "timestamps")] 248 | impl FromStr for Timestamp { 249 | type Err = String; 250 | 251 | fn from_str(s: &str) -> Result { 252 | match s { 253 | "ns" => Ok(Timestamp::Nanosecond), 254 | "ms" => Ok(Timestamp::Millisecond), 255 | "us" => Ok(Timestamp::Microsecond), 256 | "sec" => Ok(Timestamp::Second), 257 | "none" | "off" => Ok(Timestamp::Off), 258 | _ => Err("invalid value".into()), 259 | } 260 | } 261 | } 262 | 263 | /// Data specific to this logger 264 | pub struct StdErrLog { 265 | verbosity: LevelFilter, 266 | quiet: bool, 267 | show_level: bool, 268 | #[cfg(feature = "timestamps")] 269 | timestamp: Timestamp, 270 | modules: Vec, 271 | writer: ThreadLocal>, 272 | color_choice: ColorChoice, 273 | show_module_names: bool, 274 | } 275 | 276 | impl fmt::Debug for StdErrLog { 277 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 278 | let mut builder = f.debug_struct("StdErrLog"); 279 | builder 280 | .field("verbosity", &self.verbosity) 281 | .field("quiet", &self.quiet) 282 | .field("show_level", &self.show_level); 283 | #[cfg(feature = "timestamps")] 284 | builder.field("timestamp", &self.timestamp); 285 | builder 286 | .field("modules", &self.modules) 287 | .field("writer", &"stderr") 288 | .field("color_choice", &self.color_choice) 289 | .field("show_module_names", &self.show_module_names) 290 | .finish() 291 | } 292 | } 293 | 294 | impl Clone for StdErrLog { 295 | fn clone(&self) -> StdErrLog { 296 | StdErrLog { 297 | modules: self.modules.clone(), 298 | writer: ThreadLocal::new(), 299 | ..*self 300 | } 301 | } 302 | } 303 | 304 | impl Log for StdErrLog { 305 | fn enabled(&self, metadata: &Metadata) -> bool { 306 | metadata.level() <= self.log_level_filter() && self.includes_module(metadata.target()) 307 | } 308 | 309 | fn log(&self, record: &Record) { 310 | // if logging isn't enabled for this level do a quick out 311 | if !self.enabled(record.metadata()) { 312 | return; 313 | } 314 | 315 | let writer = self 316 | .writer 317 | .get_or(|| RefCell::new(StandardStream::stderr(self.color_choice))); 318 | let writer = writer.borrow_mut(); 319 | let mut writer = io::LineWriter::new(writer.lock()); 320 | let color = match record.metadata().level() { 321 | Level::Error => Color::Red, 322 | Level::Warn => Color::Yellow, 323 | Level::Info => Color::Blue, 324 | Level::Debug => Color::Cyan, 325 | Level::Trace => Color::Magenta, 326 | }; 327 | { 328 | // A failure here indicates the stream closed. Do not panic. 329 | writer 330 | .get_mut() 331 | .set_color(ColorSpec::new().set_fg(Some(color))) 332 | .ok(); 333 | } 334 | 335 | if self.show_module_names { 336 | let _ = write!(writer, "{}: ", record.metadata().target()); 337 | } 338 | #[cfg(feature = "timestamps")] 339 | match self.timestamp { 340 | Timestamp::Second => { 341 | let fmt = "%Y-%m-%dT%H:%M:%S%:z"; 342 | let _ = write!(writer, "{} - ", Local::now().format(fmt)); 343 | } 344 | Timestamp::Millisecond => { 345 | let fmt = "%Y-%m-%dT%H:%M:%S%.3f%:z"; 346 | let _ = write!(writer, "{} - ", Local::now().format(fmt)); 347 | } 348 | Timestamp::Microsecond => { 349 | let fmt = "%Y-%m-%dT%H:%M:%S%.6f%:z"; 350 | let _ = write!(writer, "{} - ", Local::now().format(fmt)); 351 | } 352 | Timestamp::Nanosecond => { 353 | let fmt = "%Y-%m-%dT%H:%M:%S%.9f%:z"; 354 | let _ = write!(writer, "{} - ", Local::now().format(fmt)); 355 | } 356 | Timestamp::Off => {} 357 | } 358 | if self.show_level { 359 | let _ = write!(writer, "{} ", record.level()); 360 | } 361 | writer.flush().ok(); 362 | { 363 | // A failure here indicates the stream closed. Do not panic. 364 | writer.get_mut().reset().ok(); 365 | } 366 | let _ = writeln!(writer, "{}", record.args()); 367 | } 368 | 369 | fn flush(&self) { 370 | let writer = self 371 | .writer 372 | .get_or(|| RefCell::new(StandardStream::stderr(self.color_choice))); 373 | let mut writer = writer.borrow_mut(); 374 | writer.flush().ok(); 375 | } 376 | } 377 | 378 | pub enum LogLevelNum { 379 | Off, 380 | Error, 381 | Warn, 382 | Info, 383 | Debug, 384 | Trace, 385 | } 386 | 387 | impl From for LogLevelNum { 388 | fn from(verbosity: usize) -> Self { 389 | match verbosity { 390 | 0 => LogLevelNum::Error, 391 | 1 => LogLevelNum::Warn, 392 | 2 => LogLevelNum::Info, 393 | 3 => LogLevelNum::Debug, 394 | _ => LogLevelNum::Trace, 395 | } 396 | } 397 | } 398 | 399 | impl From for LogLevelNum { 400 | fn from(l: Level) -> Self { 401 | match l { 402 | Level::Error => LogLevelNum::Error, 403 | Level::Warn => LogLevelNum::Warn, 404 | Level::Info => LogLevelNum::Info, 405 | Level::Debug => LogLevelNum::Debug, 406 | Level::Trace => LogLevelNum::Trace, 407 | } 408 | } 409 | } 410 | 411 | impl From for LogLevelNum { 412 | fn from(l: LevelFilter) -> Self { 413 | match l { 414 | LevelFilter::Off => LogLevelNum::Off, 415 | LevelFilter::Error => LogLevelNum::Error, 416 | LevelFilter::Warn => LogLevelNum::Warn, 417 | LevelFilter::Info => LogLevelNum::Info, 418 | LevelFilter::Debug => LogLevelNum::Debug, 419 | LevelFilter::Trace => LogLevelNum::Trace, 420 | } 421 | } 422 | } 423 | 424 | impl StdErrLog { 425 | /// creates a new stderr logger 426 | pub fn new() -> StdErrLog { 427 | StdErrLog { 428 | verbosity: LevelFilter::Error, 429 | quiet: false, 430 | show_level: true, 431 | #[cfg(feature = "timestamps")] 432 | timestamp: Timestamp::Off, 433 | modules: Vec::new(), 434 | writer: ThreadLocal::new(), 435 | color_choice: ColorChoice::Auto, 436 | show_module_names: false, 437 | } 438 | } 439 | 440 | /// Sets the verbosity level of messages that will be displayed 441 | /// 442 | /// Values can be supplied as: 443 | /// - usize 444 | /// - log::Level 445 | /// - log::LevelFilter 446 | /// - LogLevelNum 447 | /// 448 | /// Values map as follows: 449 | /// 0 -> Error 450 | /// 1 -> Warn 451 | /// 2 -> Info 452 | /// 3 -> Debug 453 | /// 4 or higher -> Trace 454 | pub fn verbosity>(&mut self, verbosity: V) -> &mut StdErrLog { 455 | self.verbosity = match verbosity.into() { 456 | LogLevelNum::Off => LevelFilter::Off, 457 | LogLevelNum::Error => LevelFilter::Error, 458 | LogLevelNum::Warn => LevelFilter::Warn, 459 | LogLevelNum::Info => LevelFilter::Info, 460 | LogLevelNum::Debug => LevelFilter::Debug, 461 | LogLevelNum::Trace => LevelFilter::Trace, 462 | }; 463 | self 464 | } 465 | 466 | /// silence all output, no matter the value of verbosity 467 | pub fn quiet(&mut self, quiet: bool) -> &mut StdErrLog { 468 | self.quiet = quiet; 469 | self 470 | } 471 | 472 | /// Enables or disables the use of levels in log messages (default is true) 473 | pub fn show_level(&mut self, levels: bool) -> &mut StdErrLog { 474 | self.show_level = levels; 475 | self 476 | } 477 | 478 | /// Enables or disables the use of timestamps in log messages 479 | #[cfg(feature = "timestamps")] 480 | pub fn timestamp(&mut self, timestamp: Timestamp) -> &mut StdErrLog { 481 | self.timestamp = timestamp; 482 | self 483 | } 484 | 485 | /// Enables or disables the use of color in log messages 486 | pub fn color(&mut self, choice: ColorChoice) -> &mut StdErrLog { 487 | self.color_choice = choice; 488 | self 489 | } 490 | 491 | /// specify a module to allow to log to stderr 492 | pub fn module>(&mut self, module: T) -> &mut StdErrLog { 493 | self._module(module.into()) 494 | } 495 | 496 | /// Enables or disables the use of module names in log messages 497 | pub fn show_module_names(&mut self, show_module_names: bool) -> &mut StdErrLog { 498 | self.show_module_names = show_module_names; 499 | self 500 | } 501 | 502 | fn _module(&mut self, module: String) -> &mut StdErrLog { 503 | // If Ok, the module was already found 504 | if let Err(i) = self.modules.binary_search(&module) { 505 | // If a super-module of the current module already exists, don't insert this module 506 | if i == 0 || !is_submodule(&self.modules[i - 1], &module) { 507 | // Remove any submodules of the module we're inserting 508 | let submodule_count = self.modules[i..] 509 | .iter() 510 | .take_while(|possible_submodule| is_submodule(&module, possible_submodule)) 511 | .count(); 512 | self.modules.drain(i..i + submodule_count); 513 | self.modules.insert(i, module); 514 | } 515 | } 516 | self 517 | } 518 | 519 | /// specify modules to allow to log to stderr 520 | pub fn modules, I: IntoIterator>( 521 | &mut self, 522 | modules: I, 523 | ) -> &mut StdErrLog { 524 | for module in modules { 525 | self.module(module); 526 | } 527 | self 528 | } 529 | 530 | fn log_level_filter(&self) -> LevelFilter { 531 | if self.quiet { 532 | LevelFilter::Off 533 | } else { 534 | self.verbosity 535 | } 536 | } 537 | 538 | fn includes_module(&self, module_path: &str) -> bool { 539 | // If modules is empty, include all module paths 540 | if self.modules.is_empty() { 541 | return true; 542 | } 543 | // if a prefix of module_path is in `self.modules`, it must 544 | // be located at the first location before 545 | // where module_path would be. 546 | match self 547 | .modules 548 | .binary_search_by(|module| module.as_str().cmp(module_path)) 549 | { 550 | Ok(_) => { 551 | // Found exact module: return true 552 | true 553 | } 554 | Err(0) => { 555 | // if there's no item which would be located before module_path, no prefix is there 556 | false 557 | } 558 | Err(i) => is_submodule(&self.modules[i - 1], module_path), 559 | } 560 | } 561 | 562 | /// sets the the logger as active 563 | pub fn init(&mut self) -> Result<(), log::SetLoggerError> { 564 | /* if the user is using auto color choices then 565 | * detect if stderr is a tty, if it is continue 566 | * otherwise turn off colors by default 567 | */ 568 | self.color_choice = match self.color_choice { 569 | ColorChoice::Auto => { 570 | if io::stderr().is_terminal() { 571 | ColorChoice::Auto 572 | } else { 573 | ColorChoice::Never 574 | } 575 | } 576 | other => other, 577 | }; 578 | log::set_max_level(self.log_level_filter()); 579 | log::set_boxed_logger(Box::new(self.clone())) 580 | } 581 | } 582 | 583 | impl Default for StdErrLog { 584 | fn default() -> Self { 585 | StdErrLog::new() 586 | } 587 | } 588 | 589 | /// creates a new stderr logger 590 | pub fn new() -> StdErrLog { 591 | StdErrLog::new() 592 | } 593 | 594 | fn is_submodule(parent: &str, possible_child: &str) -> bool { 595 | // Treat as bytes, because we'll be doing slicing, and we only care about ':' chars 596 | let parent = parent.as_bytes(); 597 | let possible_child = possible_child.as_bytes(); 598 | 599 | // a longer module path cannot be a parent of a shorter module path 600 | if parent.len() > possible_child.len() { 601 | return false; 602 | } 603 | 604 | // If the path up to the parent isn't the same as the child, 605 | if parent != &possible_child[..parent.len()] { 606 | return false; 607 | } 608 | 609 | // Either the path is exactly the same, or the sub module should have a "::" after 610 | // the length of the parent path. This prevents things like 'a::bad' being considered 611 | // a submodule of 'a::b' 612 | parent.len() == possible_child.len() 613 | || possible_child.get(parent.len()..parent.len() + 2) == Some(b"::") 614 | } 615 | 616 | #[cfg(test)] 617 | mod tests { 618 | use super::{is_submodule, StdErrLog}; 619 | 620 | #[test] 621 | fn submodule() { 622 | assert!(is_submodule("a", "a::b::c::d")); 623 | assert!(is_submodule("a::b::c", "a::b::c::d")); 624 | assert!(is_submodule("a::b::c", "a::b::c")); 625 | assert!(!is_submodule("a::b::c", "a::bad::c")); 626 | assert!(!is_submodule("a::b::c", "a::b::cab")); 627 | assert!(!is_submodule("a::b::c", "a::b::cab::d")); 628 | assert!(!is_submodule("a::b::c", "a::b")); 629 | assert!(!is_submodule("a::b::c", "a::bad")); 630 | } 631 | 632 | #[test] 633 | fn test_default_level() { 634 | super::new().module(module_path!()).init().unwrap(); 635 | 636 | assert_eq!(log::Level::Error, log::max_level()) 637 | } 638 | 639 | #[test] 640 | fn modules_display_all() { 641 | let logger = StdErrLog::new(); 642 | assert!(logger.includes_module("good")); 643 | } 644 | 645 | #[test] 646 | fn modules_display_exact_match() { 647 | let mut logger = StdErrLog::new(); 648 | logger.module("good"); 649 | assert!(logger.includes_module("good")); 650 | assert!(!logger.includes_module("bad")); 651 | } 652 | 653 | #[test] 654 | fn modules_display_module() { 655 | let mut logger = StdErrLog::new(); 656 | logger.module("good"); 657 | assert!(logger.includes_module("good::foo")); 658 | assert!(logger.includes_module("good::bar")); 659 | assert!(logger.includes_module("good")); 660 | assert!(!logger.includes_module("bad")); 661 | } 662 | 663 | #[test] 664 | fn modules_display_submodule() { 665 | let mut logger = StdErrLog::new(); 666 | logger.module("good::foo"); 667 | assert!(logger.includes_module("good::foo")); 668 | assert!(!logger.includes_module("good::bar")); 669 | assert!(!logger.includes_module("good")); 670 | assert!(!logger.includes_module("bad")); 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /tests/debug_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn debug_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .timestamp(stderrlog::Timestamp::Second) 8 | .verbosity(3) 9 | .init() 10 | .unwrap(); 11 | 12 | error!("error msg"); 13 | warn!("warning msg"); 14 | info!("info msg"); 15 | debug!("debug msg"); 16 | trace!("trace msg"); 17 | 18 | assert_eq!(log::Level::Debug, log::max_level()) 19 | } 20 | -------------------------------------------------------------------------------- /tests/debug_level_log.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn debug_level_log() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .timestamp(stderrlog::Timestamp::Nanosecond) 8 | .verbosity(log::Level::Debug) 9 | .init() 10 | .unwrap(); 11 | 12 | error!("error msg"); 13 | warn!("warning msg"); 14 | info!("info msg"); 15 | debug!("debug msg"); 16 | trace!("trace msg"); 17 | 18 | assert_eq!(log::Level::Debug, log::max_level()) 19 | } 20 | -------------------------------------------------------------------------------- /tests/error_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn error_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .timestamp(stderrlog::Timestamp::Millisecond) 8 | .verbosity(0) 9 | .init() 10 | .unwrap(); 11 | 12 | error!("error msg"); 13 | warn!("warning msg"); 14 | info!("info msg"); 15 | debug!("debug msg"); 16 | trace!("trace msg"); 17 | 18 | assert_eq!(log::Level::Error, log::max_level()) 19 | } 20 | -------------------------------------------------------------------------------- /tests/info_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn info_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .timestamp(stderrlog::Timestamp::Microsecond) 8 | .verbosity(2) 9 | .init() 10 | .unwrap(); 11 | 12 | error!("error msg"); 13 | warn!("warning msg"); 14 | info!("info msg"); 15 | debug!("debug msg"); 16 | trace!("trace msg"); 17 | 18 | assert_eq!(log::Level::Info, log::max_level()) 19 | } 20 | -------------------------------------------------------------------------------- /tests/module_inclusion.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | mod included_not { 4 | use crate::utils; 5 | use log::log_enabled; 6 | use stderrlog::StdErrLog; 7 | #[test] 8 | fn including_module_with_substring_name() { 9 | utils::init(); 10 | let mut logger = StdErrLog::new(); 11 | logger.module("module_inclusion::included"); 12 | logger.verbosity(10); 13 | utils::set_logger(logger); 14 | assert!(!log_enabled!(log::Level::Error)); 15 | } 16 | } 17 | 18 | mod included { 19 | mod b { 20 | use crate::utils; 21 | use log::log_enabled; 22 | use stderrlog::StdErrLog; 23 | #[test] 24 | fn super_and_submodule_included() { 25 | utils::init(); 26 | let mut logger = StdErrLog::new(); 27 | logger.module("module_inclusion::included"); 28 | logger.module("module_inclusion::included::a"); 29 | logger.verbosity(10); 30 | utils::set_logger(logger); 31 | assert!(log_enabled!(log::Level::Error)); 32 | } 33 | #[test] 34 | fn sub_and_supermodule_included() { 35 | utils::init(); 36 | let mut logger = StdErrLog::new(); 37 | logger.module("module_inclusion::included::a"); 38 | logger.module("module_inclusion::included"); 39 | logger.verbosity(10); 40 | utils::set_logger(logger); 41 | assert!(log_enabled!(log::Level::Error)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/no_levels_output.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn no_levels_output() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .verbosity(4) 8 | .show_level(false) 9 | .init() 10 | .unwrap(); 11 | 12 | error!("error msg"); 13 | warn!("warning msg"); 14 | info!("info msg"); 15 | debug!("debug msg"); 16 | trace!("trace msg"); 17 | 18 | assert_eq!(log::Level::Trace, log::max_level()) 19 | } 20 | -------------------------------------------------------------------------------- /tests/pty_closed.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | mod unix { 3 | use std::fs::File; 4 | use std::io::Error; 5 | use std::os::unix::io::{AsRawFd, FromRawFd}; 6 | 7 | use log::info; 8 | 9 | fn check_ret(ret: libc::c_int, err_msg: &str) { 10 | if ret < 0 { 11 | panic!("{}: {}", err_msg, Error::last_os_error()); 12 | } 13 | } 14 | 15 | fn wrap_fd(fd: libc::c_int, err_msg: &str) -> File { 16 | check_ret(fd, err_msg); 17 | // Use File as a owned fd. 18 | unsafe { File::from_raw_fd(fd) } 19 | } 20 | 21 | #[cfg(all(unix, target_os = "linux"))] 22 | #[test] 23 | fn log_after_pty_close() { 24 | let pt = wrap_fd(unsafe { libc::getpt() }, "getpt"); 25 | check_ret(unsafe { libc::grantpt(pt.as_raw_fd()) }, "grantpt"); 26 | check_ret(unsafe { libc::unlockpt(pt.as_raw_fd()) }, "unlockpt"); 27 | 28 | let name = unsafe { libc::ptsname(pt.as_raw_fd()) }; 29 | if name.is_null() { 30 | panic!("ptsname: {}", Error::last_os_error()); 31 | } 32 | 33 | let client = wrap_fd(unsafe { libc::open(name, libc::O_RDWR) }, "open client pty"); 34 | 35 | // Back up stderr 36 | let dup_stderr = wrap_fd(unsafe { libc::dup(2) }, "dup stderr"); 37 | let dup_stderr_panic = wrap_fd(unsafe { libc::dup(2) }, "dup stderr"); 38 | 39 | // Set up the panic handler to restore stderr. 40 | let default_panic = std::panic::take_hook(); 41 | std::panic::set_hook(Box::new(move |info| { 42 | check_ret( 43 | unsafe { libc::dup2(dup_stderr_panic.as_raw_fd(), 2) }, 44 | "dup2 restore stderr", 45 | ); 46 | default_panic(info); 47 | })); 48 | 49 | // Replace stderr with pty 50 | check_ret( 51 | unsafe { libc::dup2(client.as_raw_fd(), 2) }, 52 | "dup2 restore stderr", 53 | ); 54 | 55 | stderrlog::new().verbosity(50).init().unwrap(); 56 | info!("This should work."); 57 | 58 | println!("Closing PTY"); 59 | drop(pt); 60 | 61 | println!("Sending log after PTY closed"); 62 | info!("This should trigger an EIO."); 63 | 64 | // Restore stderr 65 | check_ret( 66 | unsafe { libc::dup2(dup_stderr.as_raw_fd(), 2) }, 67 | "dup2 restore stderr", 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/quiet_trace_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn quiet_trace_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .timestamp(stderrlog::Timestamp::Second) 8 | .verbosity(4) 9 | .quiet(true) 10 | .init() 11 | .unwrap(); 12 | 13 | error!("error msg"); 14 | warn!("warning msg"); 15 | info!("info msg"); 16 | debug!("debug msg"); 17 | trace!("trace msg"); 18 | 19 | assert_eq!(log::LevelFilter::Off, log::max_level()) 20 | } 21 | -------------------------------------------------------------------------------- /tests/trace_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn trace_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .verbosity(4) 8 | .init() 9 | .unwrap(); 10 | 11 | error!("error msg"); 12 | warn!("warning msg"); 13 | info!("info msg"); 14 | debug!("debug msg"); 15 | trace!("trace msg"); 16 | 17 | assert_eq!(log::Level::Trace, log::max_level()) 18 | } 19 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use log::{self, Log}; 2 | use std::cell::RefCell; 3 | use std::sync; 4 | use stderrlog::StdErrLog; 5 | 6 | thread_local! { 7 | pub static LOGGER_INSTANCE: RefCell> = RefCell::new(None); 8 | } 9 | 10 | static INIT_LOGGER: sync::Once = sync::Once::new(); 11 | 12 | struct DelegatingLogger; 13 | 14 | impl Log for DelegatingLogger { 15 | fn enabled(&self, metadata: &log::Metadata) -> bool { 16 | LOGGER_INSTANCE.with(|instance| { 17 | let instance = instance.borrow(); 18 | if let Some(ref instance) = *instance { 19 | instance.enabled(metadata) 20 | } else { 21 | false 22 | } 23 | }) 24 | } 25 | 26 | fn log(&self, record: &log::Record) { 27 | LOGGER_INSTANCE.with(|instance| { 28 | let instance = instance.borrow(); 29 | if let Some(ref instance) = *instance { 30 | instance.log(record); 31 | } 32 | }); 33 | } 34 | 35 | fn flush(&self) { 36 | LOGGER_INSTANCE.with(|instance| { 37 | let instance = instance.borrow(); 38 | if let Some(ref instance) = *instance { 39 | instance.flush(); 40 | } 41 | }); 42 | } 43 | } 44 | 45 | pub fn init() { 46 | INIT_LOGGER.call_once(|| { 47 | log::set_max_level(log::LevelFilter::max()); 48 | log::set_boxed_logger(Box::new(DelegatingLogger)).unwrap(); 49 | }); 50 | } 51 | 52 | pub fn set_logger(logger: StdErrLog) { 53 | LOGGER_INSTANCE.with(|instance| { 54 | let mut instance = instance.borrow_mut(); 55 | *instance = Some(logger); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /tests/warn_level.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | #[test] 4 | fn warn_level() { 5 | stderrlog::new() 6 | .module(module_path!()) 7 | .verbosity(1) 8 | .init() 9 | .unwrap(); 10 | 11 | error!("error msg"); 12 | warn!("warning msg"); 13 | info!("info msg"); 14 | debug!("debug msg"); 15 | trace!("trace msg"); 16 | 17 | assert_eq!(log::Level::Warn, log::max_level()) 18 | } 19 | --------------------------------------------------------------------------------