tracing-indicatif-0.3.9/.cargo_vcs_info.json0000644000000001360000000000100144240ustar { "git": { "sha1": "6ebfeaae14866a76084be6e758d9350c5d787e34" }, "path_in_vcs": "" }tracing-indicatif-0.3.9/.github/workflows/rust.yml000064400000000000000000000004741046102023000203360ustar 00000000000000name: Rust on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose tracing-indicatif-0.3.9/.gitignore000064400000000000000000000000241046102023000152000ustar 00000000000000/target /Cargo.lock tracing-indicatif-0.3.9/CHANGELOG.md000064400000000000000000000032621046102023000150300ustar 00000000000000# Change Log ## 0.3.9 - 2025-01-18 * fix panic when entering a grandparent span after a child span was entered (#14) * fix panic when using `with_span_field_formatter` (#15) ## 0.3.8 - 2024-12-01 * improve docs ## 0.3.7 - 2024-12-01 * bump dependencies * allow for customizing tick/redraw intervals * fix bug around footer not appearing after disappearing once * re-export indicatif `ProgressStyle` for ease of use * provide helper macros for printing to stdout/stderr without interfering with progress bars * disable use of `set_move_cursor` due to regression in indicatif 0.17.9 (https://github.com/console-rs/indicatif/issues/669), this may introduce new flickering unfortunately ## 0.3.6 - 2023-12-11 * update dev dependencies (#8) ## 0.3.5 - 2023-08-21 * add method to suspend progress bars managed by IndicatifLayer, e.g. to show dialogue confirmations (closes #4) ## 0.3.4 - 2023-04-28 * add methods to fetch the `IndicatifWriter` globally if there is a default tracing subscriber and if the `IndicatifLayer` has been added ## 0.3.3 - 2023-04-27 * fix a very suble race condition that could trigger a panic (#3, thanks again @Kyuuhachi!) ## 0.3.2 - 2023-04-26 * fixed a race condition that could trigger a deadlock on span close (#2, thanks @Kyuuhachi!) ## 0.3.1 - 2023-04-25 * `inc` is now allowed to be called before `pb_start` * added a rudimentary filter layer that allows specifying whether to show a pb or not on a per-span level ## 0.3.0 - 2023-02-18 * `get_stderr_writer` replaced `get_fmt_writer` * added `get_stdout_writer` so one can print to stdout without interfering with progress bars * added `IndicatifSpanExt` to be able to set per-span progress styles, support progress bars, etc tracing-indicatif-0.3.9/Cargo.lock0000644000000614530000000000100124100ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "console" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width 0.1.14", "windows-sys 0.52.0", ] [[package]] name = "dialoguer" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "shell-words", "tempfile", "thiserror", "zeroize", ] [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indicatif" version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", "number_prefix", "portable-atomic", "unicode-width 0.2.0", "vt100", "web-time", ] [[package]] name = "itoa" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "js-sys" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "pin-project-lite" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "tokio" version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-indicatif" version = "0.3.9" dependencies = [ "console", "dialoguer", "futures", "indicatif", "rand", "tokio", "tracing", "tracing-core", "tracing-subscriber", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "nu-ansi-term", "sharded-slab", "smallvec", "thread_local", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vt100" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" dependencies = [ "itoa", "log", "unicode-width 0.1.14", "vte", ] [[package]] name = "vte" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ "arrayvec", "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte_generate_state_changes" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" tracing-indicatif-0.3.9/Cargo.toml0000644000000042410000000000100124230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "tracing-indicatif" version = "0.3.9" build = false exclude = ["*.gif"] autobins = false autoexamples = false autotests = false autobenches = false description = "Tracing layer that automatically creates and manages progress bars for active spans." documentation = "https://docs.rs/tracing-indicatif" readme = "README.md" keywords = [ "cli", "progress", "progressbar", "progress-bar", "tracing", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/emersonford/tracing-indicatif" [lib] name = "tracing_indicatif" path = "src/lib.rs" [[example]] name = "basic" path = "examples/basic.rs" [[example]] name = "build_console" path = "examples/build_console.rs" [[example]] name = "child_spans" path = "examples/child_spans.rs" [[example]] name = "dialoguer_suspend" path = "examples/dialoguer_suspend.rs" [[example]] name = "filter" path = "examples/filter.rs" [[example]] name = "multithread" path = "examples/multithread.rs" [[example]] name = "per_span_style" path = "examples/per_span_style.rs" [[example]] name = "progress_bar" path = "examples/progress_bar.rs" [[example]] name = "stdout_stderr_printing" path = "examples/stdout_stderr_printing.rs" [dependencies.indicatif] version = "0.17.9" features = ["in_memory"] [dependencies.tracing] version = "0.1.40" [dependencies.tracing-core] version = "0.1.32" [dependencies.tracing-subscriber] version = "0.3.18" [dev-dependencies.console] version = "0.15.8" [dev-dependencies.dialoguer] version = "0.11.0" [dev-dependencies.futures] version = "0.3.31" [dev-dependencies.rand] version = "0.8.5" features = ["std_rng"] [dev-dependencies.tokio] version = "1.41.1" features = ["full"] tracing-indicatif-0.3.9/Cargo.toml.orig000064400000000000000000000021621046102023000161040ustar 00000000000000[package] name = "tracing-indicatif" version = "0.3.9" edition = "2021" description = "Tracing layer that automatically creates and manages progress bars for active spans." license = "MIT" repository = "https://github.com/emersonford/tracing-indicatif" categories = ["command-line-interface"] keywords = ["cli", "progress", "progressbar", "progress-bar", "tracing"] documentation = "https://docs.rs/tracing-indicatif" exclude = ["*.gif"] [dependencies] indicatif = { version = "0.17.9", features = ["in_memory"] } tracing = "0.1.40" tracing-core = "0.1.32" tracing-subscriber = { version = "0.3.18" } [dev-dependencies] futures = "0.3.31" tokio = { version = "1.41.1", features = ["full"] } rand = { version = "0.8.5", features = ["std_rng"] } console = "0.15.8" dialoguer = "0.11.0" [lib] name = "tracing_indicatif" [[example]] name = "basic" [[example]] name = "child_spans" [[example]] name = "build_console" [[example]] name = "per_span_style" [[example]] name = "progress_bar" [[example]] name = "filter" [[example]] name = "multithread" [[example]] name = "stdout_stderr_printing" [[example]] name = "dialoguer_suspend" tracing-indicatif-0.3.9/LICENSE000064400000000000000000000020551046102023000142230ustar 00000000000000MIT License Copyright (c) 2023 Emerson Ford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tracing-indicatif-0.3.9/README.md000064400000000000000000000047021046102023000144760ustar 00000000000000# tracing-indicatif [![Documentation](https://docs.rs/tracing-indicatif/badge.svg)](https://docs.rs/tracing-indicatif/) [![Crates.io](https://img.shields.io/crates/v/tracing-indicatif.svg)](https://crates.io/crates/tracing-indicatif) A [tracing](https://docs.rs/tracing/latest/tracing/) layer that automatically creates and manages [indicatif](https://docs.rs/indicatif/latest/indicatif/index.html) progress bars for active spans. Progress bars are a great way to make your CLIs feel more responsive. However, adding and managing progress bars in your libraries can be invasive, unergonomic, and difficult to keep track of. This library aims to make it easy to show progress bars for your CLI by tying progress bars to [tracing spans](https://docs.rs/tracing/latest/tracing/#spans). For CLIs/libraries already using tracing spans, this allow for a dead simple (3 line) code change to enable a smooth progress bar experience for your program. This eliminates having to have code in your libraries to manually manage progress bar instances. This ends up working quite well as progress bars are fundamentally tracking the lifetime of some "span" (whether that "span" is defined explicitly or implicitly), so might as well make that relationship explicit. ## Demo See the [`examples`](https://github.com/emersonford/tracing-indicatif/tree/main/examples) folder for demo code. ### [Default Configuration](https://github.com/emersonford/tracing-indicatif/blob/main/examples/basic.rs) ![demo using basic example](basic.gif) ### [Default Configuration with Child Spans](https://github.com/emersonford/tracing-indicatif/blob/main/examples/child_spans.rs) ![demo using child_spans example](child_spans.gif) ### [Progress Bar](https://github.com/emersonford/tracing-indicatif/blob/main/examples/progress_bar.rs) ![demo using progress_bar example](progress_bar.gif) ### [Build Console Like](https://github.com/emersonford/tracing-indicatif/blob/main/examples/build_console.rs) A recreation of `buck2`'s [superconsole](https://github.com/facebookincubator/superconsole). ![demo using build_console example](build_console.gif) ## Features * Customize progress bars using the same [`ProgressStyle`](https://docs.rs/indicatif/latest/indicatif/style/struct.ProgressStyle.html#method.template) API as indicatif. * Supports displaying parent-child span relationship between progress bars. * Limit the number of progress bars visible on the terminal. * Prevents progress bars from clobbering tracing logs. tracing-indicatif-0.3.9/examples/basic.rs000064400000000000000000000022221046102023000164570ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use rand::thread_rng; use rand::Rng; use tracing::info; use tracing::instrument; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument] async fn do_work(val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(250)..Duration::from_millis(500)); tokio::time::sleep(sleep_time).await; info!("doing work for val: {}", val); let sleep_time = thread_rng().gen_range(Duration::from_millis(500)..Duration::from_millis(1000)); tokio::time::sleep(sleep_time).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let res: u64 = stream::iter((0..20).map(|val| do_work(val))) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); println!("final result: {}", res); } tracing-indicatif-0.3.9/examples/build_console.rs000064400000000000000000000066631046102023000202340ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use indicatif::ProgressState; use indicatif::ProgressStyle; use rand::thread_rng; use rand::Rng; use tracing::info; use tracing::info_span; use tracing::instrument; use tracing_indicatif::span_ext::IndicatifSpanExt; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; fn elapsed_subsec(state: &ProgressState, writer: &mut dyn std::fmt::Write) { let seconds = state.elapsed().as_secs(); let sub_seconds = (state.elapsed().as_millis() % 1000) / 100; let _ = writer.write_str(&format!("{}.{}s", seconds, sub_seconds)); } #[instrument] async fn build_sub_unit(sub_unit: u64) { let sleep_time = thread_rng().gen_range(Duration::from_millis(5000)..Duration::from_millis(10000)); tokio::time::sleep(sleep_time).await; if thread_rng().gen_bool(0.2) { info!("sub_unit did something!"); } } #[instrument] async fn build(unit: u64) { let sleep_time = thread_rng().gen_range(Duration::from_millis(2500)..Duration::from_millis(5000)); tokio::time::sleep(sleep_time).await; let rand_num: f64 = thread_rng().gen(); if rand_num < 0.1 { tokio::join!(build_sub_unit(0), build_sub_unit(1), build_sub_unit(2)); } else if rand_num < 0.3 { tokio::join!(build_sub_unit(0), build_sub_unit(1)); } else { build_sub_unit(0).await; } } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new().with_progress_style( ProgressStyle::with_template( "{color_start}{span_child_prefix}{span_fields} -- {span_name} {wide_msg} {elapsed_subsec}{color_end}", ) .unwrap() .with_key( "elapsed_subsec", elapsed_subsec, ) .with_key( "color_start", |state: &ProgressState, writer: &mut dyn std::fmt::Write| { let elapsed = state.elapsed(); if elapsed > Duration::from_secs(8) { // Red let _ = write!(writer, "\x1b[{}m", 1 + 30); } else if elapsed > Duration::from_secs(4) { // Yellow let _ = write!(writer, "\x1b[{}m", 3 + 30); } }, ) .with_key( "color_end", |state: &ProgressState, writer: &mut dyn std::fmt::Write| { if state.elapsed() > Duration::from_secs(4) { let _ =write!(writer, "\x1b[0m"); } }, ), ).with_span_child_prefix_symbol("↳ ").with_span_child_prefix_indent(" "); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let header_span = info_span!("header"); header_span.pb_set_style( &ProgressStyle::with_template( "Working on tasks for command: `build`. {wide_msg} {elapsed_subsec}\n{wide_bar}", ) .unwrap() .with_key("elapsed_subsec", elapsed_subsec) .progress_chars("---"), ); header_span.pb_start(); // Bit of a hack to show a full "-----" line underneath the header. header_span.pb_set_length(1); header_span.pb_set_position(1); stream::iter((0..20).map(|val| build(val))) .buffer_unordered(7) .collect::>() .await; } tracing-indicatif-0.3.9/examples/child_spans.rs000064400000000000000000000030351046102023000176700ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use rand::thread_rng; use rand::Rng; use tracing::instrument; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument] async fn do_sub_work(val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(1500)..Duration::from_millis(3000)); tokio::time::sleep(sleep_time).await; val + 1 } #[instrument] async fn do_work(mut val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(250)..Duration::from_millis(500)); tokio::time::sleep(sleep_time).await; if thread_rng().gen_bool(0.4) { let (val1, val2, val3) = tokio::join!(do_sub_work(val), do_sub_work(val), do_sub_work(val)); val = val1 + val2 + val3; } else { val = do_sub_work(val).await; } let sleep_time = thread_rng().gen_range(Duration::from_millis(500)..Duration::from_millis(1000)); tokio::time::sleep(sleep_time).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let res: u64 = stream::iter((0..20).map(|val| do_work(val))) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); println!("final result: {}", res); } tracing-indicatif-0.3.9/examples/dialoguer_suspend.rs000064400000000000000000000022071046102023000211150ustar 00000000000000use std::io::Write; use std::time::Duration; use dialoguer::Confirm; use tracing::info_span; use tracing_indicatif::span_ext::IndicatifSpanExt; use tracing_indicatif::writer::get_indicatif_stderr_writer; use tracing_indicatif::{suspend_tracing_indicatif, IndicatifLayer}; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let _span = info_span!("foo"); _span.pb_start(); std::thread::sleep(Duration::from_secs(1)); suspend_tracing_indicatif(|| { if Confirm::new() .with_prompt("Do you like Rust?") .interact() .unwrap_or(false) { println!("Yay!"); } else { println!("oh... okay :("); } }); let _ = writeln!( get_indicatif_stderr_writer().unwrap(), "sleeping for some time..." ); std::thread::sleep(Duration::from_secs(1)); } tracing-indicatif-0.3.9/examples/filter.rs000064400000000000000000000035351046102023000166730ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use rand::thread_rng; use rand::Rng; use tracing::instrument; use tracing_indicatif::filter::hide_indicatif_span_fields; use tracing_indicatif::filter::IndicatifFilter; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::fmt::format::DefaultFields; use tracing_subscriber::layer::Layer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument(fields(indicatif.pb_show))] async fn do_sub_work(val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(1500)..Duration::from_millis(3000)); tokio::time::sleep(sleep_time).await; val + 1 } #[instrument] async fn do_work(mut val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(250)..Duration::from_millis(500)); tokio::time::sleep(sleep_time).await; if thread_rng().gen_bool(0.2) { let (val1, val2) = tokio::join!(do_sub_work(val), do_sub_work(val),); val = val1 + val2; } else { val = do_sub_work(val).await; } let sleep_time = thread_rng().gen_range(Duration::from_millis(500)..Duration::from_millis(1000)); tokio::time::sleep(sleep_time).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new() .with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new())); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer.with_filter(IndicatifFilter::new(false))) .init(); let res: u64 = stream::iter((0..20).map(|val| do_work(val))) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); println!("final result: {}", res); } tracing-indicatif-0.3.9/examples/multithread.rs000064400000000000000000000017661046102023000177340ustar 00000000000000use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let handles = (0..32) .map(|_| { std::thread::spawn(move || { let _span = tracing::info_span!("ch").entered(); let subhandles = (0..16) .map(|_| { std::thread::spawn(move || { let _span = tracing::info_span!("subch").entered(); }) }) .collect::>(); subhandles.into_iter().for_each(|i| i.join().unwrap()); }) }) .collect::>(); handles.into_iter().for_each(|i| i.join().unwrap()); } tracing-indicatif-0.3.9/examples/per_span_style.rs000064400000000000000000000027021046102023000204300ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use indicatif::ProgressStyle; use rand::thread_rng; use rand::Rng; use tracing::instrument; use tracing::Span; use tracing_indicatif::span_ext::IndicatifSpanExt; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument] async fn do_sub_work(val: u64) -> u64 { // Maybe we don't want a spinner here Span::current().pb_set_style( &ProgressStyle::with_template("{span_child_prefix}{span_name}{{{span_fields}}}").unwrap(), ); let sleep_time = thread_rng().gen_range(Duration::from_secs(3)..Duration::from_secs(5)); tokio::time::sleep(sleep_time).await; val + 1 } #[instrument] async fn do_work(mut val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_secs(1)..Duration::from_secs(3)); tokio::time::sleep(sleep_time).await; val = do_sub_work(val).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let res: u64 = stream::iter((0..20).map(|val| do_work(val))) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); println!("final result: {}", res); } tracing-indicatif-0.3.9/examples/progress_bar.rs000064400000000000000000000031631046102023000200730ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use indicatif::ProgressStyle; use rand::thread_rng; use rand::Rng; use tracing::info_span; use tracing::instrument; use tracing::Span; use tracing_indicatif::span_ext::IndicatifSpanExt; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument] async fn do_sub_work(val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_secs(3)..Duration::from_secs(5)); tokio::time::sleep(sleep_time).await; val + 1 } #[instrument] async fn do_work(mut val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_secs(1)..Duration::from_secs(3)); tokio::time::sleep(sleep_time).await; val = do_sub_work(val).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let header_span = info_span!("header"); header_span.pb_set_style(&ProgressStyle::default_bar()); header_span.pb_set_length(20); let header_span_enter = header_span.enter(); let res: u64 = stream::iter((0..20).map(|val| async move { let res = do_work(val).await; Span::current().pb_inc(1); res })) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); std::mem::drop(header_span_enter); std::mem::drop(header_span); println!("final result: {}", res); } tracing-indicatif-0.3.9/examples/stdout_stderr_printing.rs000064400000000000000000000024351046102023000222230ustar 00000000000000use std::time::Duration; use futures::stream::{self, StreamExt}; use rand::thread_rng; use rand::Rng; use tracing::instrument; use tracing_indicatif::indicatif_eprintln; use tracing_indicatif::indicatif_println; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[instrument] async fn do_work(val: u64) -> u64 { let sleep_time = thread_rng().gen_range(Duration::from_millis(250)..Duration::from_millis(500)); tokio::time::sleep(sleep_time).await; indicatif_eprintln!("writing val {} to stderr", val); indicatif_println!("writing val {} to stdout", val); let sleep_time = thread_rng().gen_range(Duration::from_millis(500)..Duration::from_millis(1000)); tokio::time::sleep(sleep_time).await; val + 1 } #[tokio::main] async fn main() { let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer) .init(); let res: u64 = stream::iter((0..20).map(|val| do_work(val))) .buffer_unordered(5) .collect::>() .await .into_iter() .sum(); println!("final result: {}", res); } tracing-indicatif-0.3.9/src/filter.rs000064400000000000000000000064521046102023000156450ustar 00000000000000//! Provides a rudimentary filter layer that can be used to selectively enable progress bars on a //! per-span level. //! //! # Example Use //! //! ``` //! use tracing_subscriber::layer::SubscriberExt; //! use tracing_subscriber::util::SubscriberInitExt; //! use tracing_indicatif::IndicatifLayer; //! use tracing_indicatif::filter::IndicatifFilter; //! use tracing_indicatif::filter::hide_indicatif_span_fields; //! use tracing_subscriber::fmt::format::DefaultFields; //! use tracing_subscriber::layer::Layer; //! //! let indicatif_layer = IndicatifLayer::new() //! .with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new())); //! //! tracing_subscriber::registry() //! .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) //! .with(indicatif_layer.with_filter(IndicatifFilter::new(false))) //! .init(); //! ``` use std::fmt; use std::marker::PhantomData; use tracing_core::{Field, Subscriber}; use tracing_subscriber::layer::Filter; use tracing_subscriber::{ field::{MakeVisitor, VisitFmt, VisitOutput}, fmt::format::Writer, }; use crate::util::FilteredFormatFields; /// A filter that filters based on the presence of a field with the name of either /// "indicatif.pb_show" or "indicatif.pb_hide" on the span. /// /// The value for this field is irrelevant and not factored in to the filtering (this is due to /// tracing not making field values available in the `on_new_span` method). To avoid confusion, it /// is recommended to set the value of this field to [`tracing::field::Empty`]. /// /// If both "indicatif.pb_show" and "indicatif.pb_hide" are present, the behavior is to show a /// progress bar. pub struct IndicatifFilter { show_progress_bars_by_default: bool, subscriber: PhantomData, } impl IndicatifFilter { /// Constructs the filter. /// /// If "indicatif.pb_show" or "indicatif.pb_hide" are not present as a field on the span, /// then the value of `show_progress_bars_by_default` is used; i.e. if /// `show_progress_bars_by_default` is `false`, then progress bars are not shown for spans by /// default. pub fn new(show_progress_bars_by_default: bool) -> Self { Self { show_progress_bars_by_default, subscriber: PhantomData, } } } impl Filter for IndicatifFilter { fn enabled( &self, meta: &tracing::Metadata<'_>, _: &tracing_subscriber::layer::Context<'_, S>, ) -> bool { if !meta.is_span() { return false; } if meta.fields().field("indicatif.pb_show").is_some() { return true; } if meta.fields().field("indicatif.pb_hide").is_some() { return false; } self.show_progress_bars_by_default } } /// Returns a [`tracing_subscriber::fmt::FormatFields`] that ignores the "indicatif.pb_show" and "indicatif.pb_hide" fields. pub fn hide_indicatif_span_fields<'writer, Format>( format: Format, ) -> FilteredFormatFields bool + Clone> where Format: MakeVisitor>, Format::Visitor: VisitFmt + VisitOutput, { FilteredFormatFields::new(format, |field: &Field| { field.name() != "indicatif.pb_show" && field.name() != "indicatif.pb_hide" }) } tracing-indicatif-0.3.9/src/lib.rs000064400000000000000000000706301046102023000151250ustar 00000000000000//! A [tracing](https://docs.rs/tracing/latest/tracing/) layer that automatically creates and manages [indicatif](https://docs.rs/indicatif/latest/indicatif/index.html) progress bars for active spans. //! //! Progress bars are a great way to make your CLIs feel more responsive. However, //! adding and managing progress bars in your libraries can be invasive, unergonomic, //! and difficult to keep track of. //! //! This library aims to make it easy to show progress bars for your CLI by tying //! progress bars to [tracing spans](https://docs.rs/tracing/latest/tracing/#spans). //! For CLIs/libraries already using tracing spans, this allow for a dead simple (3 //! line) code change to enable a smooth progress bar experience for your program. //! This eliminates having to have code in your libraries to manually manage //! progress bar instances. //! //! This ends up working quite well as progress bars are fundamentally tracking the //! lifetime of some "span" (whether that "span" is defined explicitly or implicitly), //! so might as well make that relationship explicit. //! //! An easy quick start for this crate is: //! ``` //! use tracing_subscriber::layer::SubscriberExt; //! use tracing_subscriber::util::SubscriberInitExt; //! use tracing_indicatif::IndicatifLayer; //! //! let indicatif_layer = IndicatifLayer::new(); //! //! tracing_subscriber::registry() //! .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) //! .with(indicatif_layer) //! .init(); //! ``` //! See [`IndicatifLayer`] for additional documentation. //! //! See the [`examples`](https://github.com/emersonford/tracing-indicatif/tree/main/examples) folder for examples of how to customize the layer / progress bar //! appearance. //! //! Note: it is highly recommended you pass `indicatif_layer.get_stderr_writer()` or //! `indicatif_layer.get_stdout_writer()` to your `fmt::layer()` (depending on where you want to //! emit tracing logs) to prevent progress bars from clobbering any console logs. use std::any::TypeId; use std::marker::PhantomData; use std::sync::Mutex; /// Re-export of [`indicatif`]'s style module for ease of use. pub use indicatif::style; use indicatif::style::ProgressStyle; use indicatif::style::ProgressTracker; use indicatif::MultiProgress; use indicatif::ProgressBar; use tracing_core::span; use tracing_core::Subscriber; use tracing_subscriber::fmt::format::DefaultFields; use tracing_subscriber::fmt::FormatFields; use tracing_subscriber::fmt::FormattedFields; use tracing_subscriber::layer; use tracing_subscriber::registry::LookupSpan; pub mod filter; mod pb_manager; pub mod span_ext; pub mod util; pub mod writer; use pb_manager::ProgressBarManager; pub use pb_manager::TickSettings; #[doc(inline)] pub use writer::IndicatifWriter; #[derive(Clone)] struct IndicatifProgressKey { message: String, } impl ProgressTracker for IndicatifProgressKey { fn clone_box(&self) -> Box { Box::new(self.clone()) } fn tick(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {} fn reset(&mut self, _: &indicatif::ProgressState, _: std::time::Instant) {} fn write(&self, _: &indicatif::ProgressState, w: &mut dyn std::fmt::Write) { let _ = w.write_str(&self.message); } } // Suppose we have a [Span] (maybe gotten via [Span::current]) and want access to our // [IndicatifLayer] instance from it. The way to do this would be something like // ``` // span.with_subscriber(|(id, subscriber)| { // let maybe_layer = subscriber.downcast_ref::>(); // ... // }); // ``` // but this has the problem that, because `IndicatifLayer` has generic params, we need to pass // a concrete type `S` and `F` to that `downcast_ref` call. And the callsite doesn't know what // those concrete types are. // // Therefore, we use this `WithContext` struct (along with the defined `downcast_raw` method) to do // a form of indirection to something that does already know (or "remembers") what those concrete // types `S` and `F` are, so the callsite doesn't need to care about it. // // This doesn't actually return a reference to our [IndicatifLayer] instance as we only care about // the associated span data, so we just pass that to the corresponding `fn`. // // See: // * https://github.com/tokio-rs/tracing/blob/a0126b2e2d465e8e6d514acdf128fcef5b863d27/tracing-error/src/subscriber.rs#L32 // * https://github.com/tokio-rs/tracing/blob/a0126b2e2d465e8e6d514acdf128fcef5b863d27/tracing-opentelemetry/src/subscriber.rs#L74 #[allow(clippy::type_complexity)] pub(crate) struct WithContext( fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut IndicatifSpanContext)), ); #[allow(clippy::type_complexity)] pub(crate) struct WithStderrWriter( fn(&tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter)), ); #[allow(clippy::type_complexity)] pub(crate) struct WithStdoutWriter( fn(&tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter)), ); #[allow(clippy::type_complexity)] pub(crate) struct WithMultiProgress(fn(&tracing::Dispatch, f: &mut dyn FnMut(MultiProgress))); impl WithContext { pub(crate) fn with_context( &self, dispatch: &tracing::Dispatch, id: &span::Id, mut f: impl FnMut(&mut IndicatifSpanContext), ) { (self.0)(dispatch, id, &mut f) } } impl WithStderrWriter { pub(crate) fn with_context( &self, dispatch: &tracing::Dispatch, mut f: impl FnMut(IndicatifWriter), ) { (self.0)(dispatch, &mut f) } } impl WithStdoutWriter { pub(crate) fn with_context( &self, dispatch: &tracing::Dispatch, mut f: impl FnMut(IndicatifWriter), ) { (self.0)(dispatch, &mut f) } } impl WithMultiProgress { pub(crate) fn with_context( &self, dispatch: &tracing::Dispatch, mut f: impl FnMut(MultiProgress), ) { (self.0)(dispatch, &mut f) } } #[derive(Default)] struct ProgressBarInitSettings { style: Option, len: Option, pos: Option, message: Option, } struct IndicatifSpanContext { // If this progress bar is `Some(pb)` and `pb.is_hidden`, it means the progress bar is queued. // We start the progress bar in hidden mode so things like `elapsed` are accurate. // // If this progress bar is `None`, it means the span has not yet been entered. progress_bar: Option, // If `Some`, the progress bar will use this style when the span is entered for the first time. pb_init_settings: ProgressBarInitSettings, // Notes: // * A parent span cannot close before its child spans, so if a parent span has a progress bar, // that parent progress bar's lifetime will be greater than this span's progress bar. // * The ProgressBar is just a wrapper around `Arc`, so cloning and tracking it here is fine. parent_progress_bar: Option, // This is only `Some` if we have some parent with a progress bar. parent_span: Option, // Fields to be passed to the progress bar as keys. span_fields_formatted: Option, span_name: String, span_child_prefix: String, // Used to quickly compute a child span's prefix without having to traverse up the entire span // scope. level: u16, } impl IndicatifSpanContext { fn add_keys_to_style(&self, style: ProgressStyle) -> ProgressStyle { style .with_key( "span_name", IndicatifProgressKey { message: self.span_name.clone(), }, ) .with_key( "span_fields", IndicatifProgressKey { message: self.span_fields_formatted.to_owned().unwrap_or_default(), }, ) .with_key( "span_child_prefix", IndicatifProgressKey { message: self.span_child_prefix.clone(), }, ) } fn make_progress_bar(&mut self, default_style: &ProgressStyle) { if self.progress_bar.is_none() { let pb = ProgressBar::hidden().with_style( self.pb_init_settings .style .take() .unwrap_or_else(|| self.add_keys_to_style(default_style.clone())), ); if let Some(len) = self.pb_init_settings.len.take() { pb.set_length(len); } if let Some(msg) = self.pb_init_settings.message.take() { pb.set_message(msg); } if let Some(pos) = self.pb_init_settings.pos.take() { pb.set_position(pos); } self.progress_bar = Some(pb); } } fn set_progress_bar_style(&mut self, style: ProgressStyle) { if let Some(ref pb) = self.progress_bar { pb.set_style(self.add_keys_to_style(style)); } else { self.pb_init_settings.style = Some(self.add_keys_to_style(style)); } } fn set_progress_bar_length(&mut self, len: u64) { if let Some(ref pb) = self.progress_bar { pb.set_length(len); } else { self.pb_init_settings.len = Some(len); } } fn set_progress_bar_position(&mut self, pos: u64) { if let Some(ref pb) = self.progress_bar { pb.set_position(pos); } else { self.pb_init_settings.pos = Some(pos); } } fn set_progress_bar_message(&mut self, msg: String) { if let Some(ref pb) = self.progress_bar { pb.set_message(msg); } else { self.pb_init_settings.message = Some(msg); } } fn inc_progress_bar_position(&mut self, pos: u64) { if let Some(ref pb) = self.progress_bar { pb.inc(pos); } else if let Some(ref mut pb_pos) = self.pb_init_settings.pos { *pb_pos += pos; } else { // indicatif defaults position to 0, so copy that behavior. self.pb_init_settings.pos = Some(pos); } } fn inc_progress_bar_length(&mut self, len: u64) { if let Some(ref pb) = self.progress_bar { pb.inc_length(len); } else if let Some(ref mut pb_len) = self.pb_init_settings.len { *pb_len += len; } } fn progress_bar_tick(&mut self) { if let Some(ref pb) = self.progress_bar { pb.tick() } } } /// The layer that handles creating and managing indicatif progress bars for active spans. This /// layer must be registered with your tracing subscriber to have any effect. /// /// This layer performs no filtering on which spans to show progress bars for. It is expected one /// attaches [filters to this /// layer](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers) /// to control which spans actually have progress bars generated for them. See /// [`filter::IndicatifFilter`] for a rudimentary filter. /// /// Progress bars will be started the very first time a span is [entered](tracing::Span::enter) /// or when one of its child spans is entered for the first time, and will finish when the span /// is [closed](tracing_subscriber::Layer::on_close) (including all child spans having closed). /// /// Progress bars are emitted to stderr. /// /// Under the hood, this just uses indicatif's [`MultiProgress`] struct to /// manage individual [`ProgressBar`] instances per span. pub struct IndicatifLayer { pb_manager: Mutex, // Allows us to fetch the `MultiProgress` without taking a lock. // Do not mutate `mp` directly, always go through `pb_manager`. mp: MultiProgress, span_field_formatter: F, progress_style: ProgressStyle, span_child_prefix_indent: &'static str, span_child_prefix_symbol: &'static str, get_context: WithContext, get_stderr_writer_context: WithStderrWriter, get_stdout_writer_context: WithStdoutWriter, get_multi_progress_context: WithMultiProgress, inner: PhantomData, } impl IndicatifLayer where S: Subscriber + for<'a> LookupSpan<'a>, { /// Spawns a progress bar for every tracing span that is received by this layer. /// /// The default settings for this layer are 7 progress bars maximum and progress bars in the /// style of: /// ```text /// ⠄ do_work{val=0} /// ⠄ do_work{val=1} /// ⠄ do_work{val=2} /// ↳ ⠴ do_sub_work{val=2} /// ↳ ⠴ do_sub_work{val=2} /// ⠄ do_work{val=3} /// ⠄ do_work{val=4} /// ...and 5 more not shown above. /// ``` pub fn new() -> Self { Self::default() } } impl Default for IndicatifLayer where S: Subscriber + for<'a> LookupSpan<'a>, { fn default() -> Self { let pb_manager = ProgressBarManager::new( 7, Some( ProgressStyle::with_template( "...and {pending_progress_bars} more not shown above.", ) .unwrap(), ), TickSettings::default(), ); let mp = pb_manager.mp.clone(); Self { pb_manager: Mutex::new(pb_manager), mp, span_field_formatter: DefaultFields::new(), progress_style: ProgressStyle::with_template( "{span_child_prefix}{spinner} {span_name}{{{span_fields}}}", ) .unwrap(), span_child_prefix_indent: " ", span_child_prefix_symbol: "↳ ", get_context: WithContext(Self::get_context), get_stderr_writer_context: WithStderrWriter(Self::get_stderr_writer_context), get_stdout_writer_context: WithStdoutWriter(Self::get_stdout_writer_context), get_multi_progress_context: WithMultiProgress(Self::get_multi_progress_context), inner: PhantomData, } } } // pub methods impl IndicatifLayer where S: Subscriber + for<'a> LookupSpan<'a>, { #[deprecated(since = "0.2.3", note = "use get_stderr_writer() instead")] pub fn get_fmt_writer(&self) -> IndicatifWriter { self.get_stderr_writer() } /// Returns the a writer for [`std::io::Stderr`] that ensures its output will not be clobbered by /// active progress bars. /// /// Instead of `eprintln!(...)` prefer `writeln!(indicatif_layer.get_stderr_writer(), ...)` /// instead to ensure your output is not clobbered by active progress bars. /// /// If one wishes tracing logs to be output to stderr, this should be passed into /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer). pub fn get_stderr_writer(&self) -> IndicatifWriter { // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here. IndicatifWriter::new(self.mp.clone()) } /// Returns the a writer for [`std::io::Stdout`] that ensures its output will not be clobbered by /// active progress bars. /// /// Instead of `println!(...)` prefer `writeln!(indicatif_layer.get_stdout_writer(), ...)` /// instead to ensure your output is not clobbered by active progress bars. /// /// If one wishes tracing logs to be output to stdout, this should be passed into /// [`fmt::Layer::with_writer`](tracing_subscriber::fmt::Layer::with_writer). pub fn get_stdout_writer(&self) -> IndicatifWriter { // `MultiProgress` is merely a wrapper over an `Arc`, so we can clone here. IndicatifWriter::new(self.mp.clone()) } /// Set the formatter for span fields, the result of which will be available as the /// progress bar template key `span_fields`. /// /// The default is the [`DefaultFields`] formatter. pub fn with_span_field_formatter(self, formatter: F2) -> IndicatifLayer where F2: for<'writer> FormatFields<'writer> + 'static, { IndicatifLayer { pb_manager: self.pb_manager, mp: self.mp, span_field_formatter: formatter, progress_style: self.progress_style, span_child_prefix_indent: self.span_child_prefix_indent, span_child_prefix_symbol: self.span_child_prefix_symbol, get_context: WithContext(IndicatifLayer::::get_context), get_stderr_writer_context: WithStderrWriter( IndicatifLayer::::get_stderr_writer_context, ), get_stdout_writer_context: WithStdoutWriter( IndicatifLayer::::get_stdout_writer_context, ), get_multi_progress_context: WithMultiProgress( IndicatifLayer::::get_multi_progress_context, ), inner: self.inner, } } /// Override the style used for displayed progress bars. /// /// Two additional keys are available for the progress bar template: /// * `span_fields` - the formatted string of this span's fields /// * `span_name` - the name of the span /// * `span_child_prefix` - a prefix that increase in size according to the number of parents /// the span has. /// /// The default template is `{span_child_prefix}{spinner} {span_name}{{{span_fields}}}`. pub fn with_progress_style(mut self, style: ProgressStyle) -> Self { self.progress_style = style; self } /// Set the indent used to mark the "level" of a given child span's progress bar. /// /// For example, if the given span is two levels deep (iow has two parent spans with progress /// bars), and this is " ", the `{span_child_prefix}` key for this span's progress bar will be /// prefixed with " ". pub fn with_span_child_prefix_indent(mut self, indent: &'static str) -> Self { self.span_child_prefix_indent = indent; self } /// Set the symbol used to denote this is a progress bar from a child span. /// /// This is ultimately concatenated with the child prefix indent to make the /// `span_child_prefix` progress bar key. pub fn with_span_child_prefix_symbol(mut self, symbol: &'static str) -> Self { self.span_child_prefix_symbol = symbol; self } /// Set the maximum number of progress bars that will be displayed, and the possible footer /// "progress bar" that displays when there are more progress bars than can be displayed. /// /// `footer_style` dictates the appearance of the footer, and the footer will only appear if /// there are more progress bars than can be displayed. If it is `None`, no footer will be /// displayed. `footer_style` has the following keys available to it: /// * `pending_progress_bars` - the number of progress bars waiting to be shown pub fn with_max_progress_bars( mut self, max_progress_bars: u64, footer_style: Option, ) -> Self { self.pb_manager .get_mut() .unwrap() .set_max_progress_bars(max_progress_bars, footer_style); self } /// Configures how often progress bars are recalcuated and redrawn to the terminal. pub fn with_tick_settings(mut self, tick_settings: TickSettings) -> Self { self.pb_manager .get_mut() .unwrap() .set_tick_settings(tick_settings); self } } impl IndicatifLayer where S: Subscriber + for<'a> LookupSpan<'a>, F: for<'writer> FormatFields<'writer> + 'static, { fn get_context( dispatch: &tracing::Dispatch, id: &span::Id, f: &mut dyn FnMut(&mut IndicatifSpanContext), ) { // The only way `get_context` can be called is if we have an `IndicatifLayer` added to the // expected subscriber, hence why we can `.expect` here. let subscriber = dispatch .downcast_ref::() .expect("subscriber should downcast to expected type; this is a bug!"); let span = subscriber .span(id) .expect("Span not found in context, this is a bug"); let mut ext = span.extensions_mut(); if let Some(indicatif_ctx) = ext.get_mut::() { f(indicatif_ctx); } } fn get_stderr_writer_context( dispatch: &tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter), ) { let layer = dispatch .downcast_ref::>() .expect("subscriber should downcast to expected type; this is a bug!"); f(layer.get_stderr_writer()) } fn get_stdout_writer_context( dispatch: &tracing::Dispatch, f: &mut dyn FnMut(IndicatifWriter), ) { let layer = dispatch .downcast_ref::>() .expect("subscriber should downcast to expected type; this is a bug!"); f(layer.get_stdout_writer()) } fn get_multi_progress_context(dispatch: &tracing::Dispatch, f: &mut dyn FnMut(MultiProgress)) { let layer = dispatch .downcast_ref::>() .expect("subscriber should downcast to expected type; this is a bug!"); f(layer.mp.clone()) } fn handle_on_enter( &self, pb_manager: &mut ProgressBarManager, id: &span::Id, ctx: &layer::Context<'_, S>, ) -> Option { let span = ctx .span(id) .expect("Span not found in context, this is a bug"); let mut ext = span.extensions_mut(); if let Some(indicatif_ctx) = ext.get_mut::() { // Start the progress bar when we enter the span for the first time. if indicatif_ctx.progress_bar.is_none() { indicatif_ctx.make_progress_bar(&self.progress_style); if let Some(ref parent_span_with_pb) = indicatif_ctx.parent_span { // Recursively start parent PBs if parent spans have not been entered yet. let parent_pb = self.handle_on_enter(pb_manager, parent_span_with_pb, ctx); indicatif_ctx.parent_progress_bar = parent_pb; } pb_manager.show_progress_bar(indicatif_ctx, id); } return indicatif_ctx.progress_bar.to_owned(); } None } } impl layer::Layer for IndicatifLayer where S: Subscriber + for<'a> LookupSpan<'a>, F: for<'writer> FormatFields<'writer> + 'static, { fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) { let span = ctx .span(id) .expect("Span not found in context, this is a bug"); let mut ext = span.extensions_mut(); let mut fields = FormattedFields::::new(String::new()); let _ = self .span_field_formatter .format_fields(fields.as_writer(), attrs); // Get the next parent span with a progress bar. let parent_span = ctx.span_scope(id).and_then(|scope| { scope.skip(1).find(|span| { let ext = span.extensions(); ext.get::().is_some() }) }); let parent_span_id = parent_span.as_ref().map(|span| span.id()); let parent_span_ext = parent_span.as_ref().map(|span| span.extensions()); let parent_indicatif_ctx = parent_span_ext .as_ref() .map(|ext| ext.get::().unwrap()); let (span_child_prefix, level) = match parent_indicatif_ctx { Some(v) => { let level = v.level + 1; ( format!( "{}{}", self.span_child_prefix_indent.repeat(level.into()), self.span_child_prefix_symbol ), level, ) } None => (String::new(), 0), }; ext.insert(IndicatifSpanContext { progress_bar: None, pb_init_settings: ProgressBarInitSettings::default(), parent_progress_bar: None, parent_span: parent_span_id, span_fields_formatted: Some(fields.fields), span_name: span.name().to_string(), span_child_prefix, level, }); } fn on_enter(&self, id: &span::Id, ctx: layer::Context<'_, S>) { let mut pb_manager_lock = self.pb_manager.lock().unwrap(); self.handle_on_enter(&mut pb_manager_lock, id, &ctx); } fn on_close(&self, id: span::Id, ctx: layer::Context<'_, S>) { let mut pb_manager_lock = self.pb_manager.lock().unwrap(); let span = ctx .span(&id) .expect("Span not found in context, this is a bug"); let mut ext = span.extensions_mut(); // Clear the progress bar only when the span has closed completely. if let Some(indicatif_ctx) = ext.get_mut::() { pb_manager_lock.finish_progress_bar(indicatif_ctx, &ctx); } } // See comments on [WithContext] for why we have this. // // SAFETY: this is safe because the `WithContext` function pointer is valid // for the lifetime of `&self`. unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { match id { id if id == TypeId::of::() => Some(self as *const _ as *const ()), id if id == TypeId::of::() => { Some(&self.get_context as *const _ as *const ()) } id if id == TypeId::of::() => { Some(&self.get_stderr_writer_context as *const _ as *const ()) } id if id == TypeId::of::() => { Some(&self.get_stdout_writer_context as *const _ as *const ()) } id if id == TypeId::of::() => { Some(&self.get_multi_progress_context as *const _ as *const ()) } _ => None, } } } /// Hide all progress bars managed by [`IndicatifLayer`] (if it exists), executes `f`, then redraws /// the progress bars. Identical to [`indicatif::MultiProgress::suspend`]. /// /// Executes `f` even if there is no default tracing subscriber or if a `IndicatifLayer` has not /// been registered to that subscriber. /// /// NOTE: this does not suspend stdout/stderr prints from other threads, including things like /// `tracing::info!`. This only suspends the drawing of progress bars. /// /// WARNING: this holds an internal lock within `MultiProgress`. Calling methods like /// `writeln!(get_indicatif_stderr_writer(), "foobar")` or calling this method inside of `f` will /// result in a deadlock. pub fn suspend_tracing_indicatif R, R>(f: F) -> R { let mut mp: Option = None; tracing::dispatcher::get_default(|dispatch| { if let Some(ctx) = dispatch.downcast_ref::() { ctx.with_context(dispatch, |fetched_mp| { mp = Some(fetched_mp); }) } }); if let Some(mp) = mp { mp.suspend(f) } else { f() } } /// Helper macro that allows you to print to stdout without interfering with the progress bars /// created by tracing-indicatif. /// /// Args are directly forwarded to `writeln!`. Do not call this macro inside of /// `suspend_tracing_indicatif` or you will trigger a deadlock. #[macro_export] macro_rules! indicatif_println { ($($arg:tt)*) => { { use std::io::Write; if let Some(mut writer) = $crate::writer::get_indicatif_stdout_writer() { writeln!(writer, $($arg)*).unwrap(); } else { #[allow(clippy::explicit_write)] writeln!(std::io::stdout(), $($arg)*).unwrap(); } } } } /// Helper macro that allows you to print to stderr without interfering with the progress bars /// created by tracing-indicatif. /// /// Args are directly forwarded to `writeln!`. Do not call this macro inside of /// `suspend_tracing_indicatif` or you will trigger a deadlock. #[macro_export] macro_rules! indicatif_eprintln { ($($arg:tt)*) => { { use std::io::Write; if let Some(mut writer) = $crate::writer::get_indicatif_stderr_writer() { writeln!(writer, $($arg)*).unwrap(); } else { #[allow(clippy::explicit_write)] writeln!(std::io::stderr(), $($arg)*).unwrap(); } } } } #[cfg(test)] mod tests; tracing-indicatif-0.3.9/src/pb_manager.rs000064400000000000000000000253671046102023000164610ustar 00000000000000use std::collections::VecDeque; use std::sync::atomic::AtomicUsize; use std::sync::Arc; use std::time::Duration; use indicatif::style::ProgressStyle; use indicatif::MultiProgress; use indicatif::ProgressBar; use indicatif::ProgressDrawTarget; use indicatif::ProgressState; use tracing_core::span; use tracing_core::Subscriber; use tracing_subscriber::layer; use tracing_subscriber::registry::LookupSpan; use crate::IndicatifSpanContext; #[derive(Clone)] struct RequireDefault; /// Controls how often progress bars are recalculated and redrawn to the terminal. /// /// This struct must be constructed as /// ``` /// # use tracing_indicatif::TickSettings; /// TickSettings { /// term_draw_hz: 20, /// default_tick_interval: None, /// footer_tick_interval: None, /// ..Default::default() /// } /// # ; /// ``` /// as to ensure forward compatibility. #[derive(Clone)] pub struct TickSettings { /// The rate at which to draw to the terminal. /// /// A value of 20 here means indicatif will redraw the terminal state 20 times a second (i.e. /// once every 50ms). pub term_draw_hz: u8, /// The default interval to pass to `enable_steady_tick` for a new progress bar. This controls /// how often the progress bar state is recalculated. Defaults to /// `Some(Duration::from_millis(100))`. /// /// Note, this does not control how often the progress bar is actually redrawn, that is /// controlled by [`Self::term_draw_hz`]. /// /// Using `None` here will disable steady ticks for your progress bars. pub default_tick_interval: Option, /// The interval to pass to `enable_steady_tick` for the footer progress bar. This controls /// how often the footer progress bar state is recalculated. Defaults to `None`. /// /// Note, this does not control how often the footer progress bar is actually redrawn, that is /// controlled by [`Self::term_draw_hz`]. /// /// Using `None` here will disable steady ticks for the footer progress bar. Unless you have a /// spinner in your footer, you should set this to `None` as we manually redraw the footer /// whenever something changes. pub footer_tick_interval: Option, // Exists solely to require `..Default::default()` at the end of constructing this struct. #[doc(hidden)] #[allow(private_interfaces)] pub require_default: RequireDefault, } impl Default for TickSettings { fn default() -> Self { Self { term_draw_hz: 20, default_tick_interval: Some(Duration::from_millis(100)), footer_tick_interval: None, require_default: RequireDefault, } } } pub(crate) struct ProgressBarManager { pub(crate) mp: MultiProgress, active_progress_bars: u64, max_progress_bars: u64, // This is used in the footer progress bar and tracks the actual number of pending progress // bars. pending_progress_bars: Arc, // The `.len()` of this may differ from `pending_progress_bars`. If a span closes before its // progress bar is ever un-hidden, we decrement `pending_progress_bars` but won't clean the // span entry up from this `VecDeque` for performance reasons. Instead, whenever we do un-hide // a progress bar, we'll "garbage collect" closed spans from this then. pending_spans: VecDeque, // If this is `None`, a footer will never be shown. footer_pb: Option, tick_settings: TickSettings, } impl ProgressBarManager { pub(crate) fn new( max_progress_bars: u64, footer_progress_style: Option, tick_settings: TickSettings, ) -> Self { let mut s = Self { mp: { let mp = MultiProgress::new(); mp.set_draw_target(ProgressDrawTarget::stderr_with_hz( tick_settings.term_draw_hz, )); mp }, active_progress_bars: 0, max_progress_bars: 0, pending_progress_bars: Arc::new(AtomicUsize::new(0)), pending_spans: VecDeque::new(), footer_pb: None, tick_settings, }; s.set_max_progress_bars(max_progress_bars, footer_progress_style); s } pub(crate) fn set_max_progress_bars( &mut self, max_progress_bars: u64, footer_style: Option, ) { self.max_progress_bars = max_progress_bars; let pending_progress_bars = self.pending_progress_bars.clone(); self.footer_pb = footer_style.map(move |style| { ProgressBar::hidden().with_style(style.with_key( "pending_progress_bars", move |_: &ProgressState, writer: &mut dyn std::fmt::Write| { let _ = write!( writer, "{}", pending_progress_bars.load(std::sync::atomic::Ordering::Acquire) ); }, )) }); } pub(crate) fn set_tick_settings(&mut self, tick_settings: TickSettings) { self.mp.set_draw_target(ProgressDrawTarget::stderr_with_hz( tick_settings.term_draw_hz, )); self.tick_settings = tick_settings; } fn decrement_pending_pb(&mut self) { let prev_val = self .pending_progress_bars .fetch_sub(1, std::sync::atomic::Ordering::AcqRel); if let Some(footer_pb) = self.footer_pb.as_ref() { // If this span was the last one pending, clear the footer (if it was active). if prev_val == 1 { debug_assert!( !footer_pb.is_hidden(), "footer progress bar was hidden despite there being pending progress bars" ); if self.tick_settings.footer_tick_interval.is_some() { footer_pb.disable_steady_tick(); } // Appears to have broken with // https://github.com/console-rs/indicatif/pull/648 // self.mp.set_move_cursor(false); footer_pb.finish_and_clear(); self.mp.remove(footer_pb); } else { footer_pb.tick(); } } } fn add_pending_pb(&mut self, span_id: &span::Id) { let prev_val = self .pending_progress_bars .fetch_add(1, std::sync::atomic::Ordering::AcqRel); self.pending_spans.push_back(span_id.clone()); // Show the footer progress bar. if let Some(footer_pb) = self.footer_pb.as_ref() { if prev_val == 0 { debug_assert!( footer_pb.is_hidden(), "footer progress bar was not hidden despite there being no pending progress bars" ); footer_pb.reset(); if let Some(tick_interval) = self.tick_settings.footer_tick_interval { footer_pb.enable_steady_tick(tick_interval); } self.mp.add(footer_pb.clone()); // Appears to have broken with // https://github.com/console-rs/indicatif/pull/648 // self.mp.set_move_cursor(true); } footer_pb.tick(); } } pub(crate) fn show_progress_bar( &mut self, pb_span_ctx: &mut IndicatifSpanContext, span_id: &span::Id, ) { if self.active_progress_bars < self.max_progress_bars { let pb = match pb_span_ctx.parent_progress_bar { // TODO(emersonford): fix span ordering in progress bar, because we use // `insert_after`, we end up showing the child progress bars in reverse order. Some(ref parent_pb) => self .mp .insert_after(parent_pb, pb_span_ctx.progress_bar.take().unwrap()), None => { if self .footer_pb .as_ref() .map(|footer_pb| !footer_pb.is_hidden()) .unwrap_or(false) { self.mp .insert_from_back(1, pb_span_ctx.progress_bar.take().unwrap()) } else { self.mp.add(pb_span_ctx.progress_bar.take().unwrap()) } } }; self.active_progress_bars += 1; if let Some(tick_interval) = self.tick_settings.default_tick_interval { pb.enable_steady_tick(tick_interval); } pb.tick(); pb_span_ctx.progress_bar = Some(pb); } else { self.add_pending_pb(span_id); } } pub(crate) fn finish_progress_bar( &mut self, pb_span_ctx: &mut IndicatifSpanContext, ctx: &layer::Context<'_, S>, ) where S: Subscriber + for<'a> LookupSpan<'a>, { let Some(pb) = pb_span_ctx.progress_bar.take() else { // Span was never entered. return; }; // The span closed before we had a chance to show its progress bar. if pb.is_hidden() { self.decrement_pending_pb(); return; } // This span had an active/shown progress bar. pb.finish_and_clear(); self.mp.remove(&pb); self.active_progress_bars -= 1; loop { let Some(span_id) = self.pending_spans.pop_front() else { break; }; match ctx.span(&span_id) { Some(next_eligible_span) => { let mut ext = next_eligible_span.extensions_mut(); let indicatif_span_ctx = ext .get_mut::() .expect("No IndicatifSpanContext found; this is a bug"); // It possible `on_close` has been called on a span but it has not yet been // removed from `ctx.span` (e.g., tracing may still be iterating through each // layer's `on_close` method and cannot remove the span from the registry until // it has finished `on_close` for each layer). So we may successfully fetch the // span, despite having closed out its progress bar. if indicatif_span_ctx.progress_bar.is_none() { continue; } self.decrement_pending_pb(); self.show_progress_bar(indicatif_span_ctx, &span_id); break; } None => { // Span was closed earlier, we "garbage collect" it from the queue here. continue; } } } } } tracing-indicatif-0.3.9/src/span_ext.rs000064400000000000000000000111641046102023000161750ustar 00000000000000//! Helpers to modify a progress bar associated with a given span. use indicatif::ProgressStyle; use tracing::Span; use crate::IndicatifSpanContext; use crate::WithContext; // TODO(emersonford): expose stderr/stdout writers in span ext fn apply_to_indicatif_span(span: &Span, f: impl FnMut(&mut IndicatifSpanContext)) { span.with_subscriber(|(id, subscriber)| { if let Some(get_context) = subscriber.downcast_ref::() { get_context.with_context(subscriber, id, f); } }); } /// Utilities to modify the progress bar associated to tracing [`Span`]'s (if one exists). /// /// For example, you can call these on the current Span: /// ``` /// use tracing_indicatif::span_ext::IndicatifSpanExt; /// use indicatif::ProgressStyle; /// /// tracing::Span::current().pb_set_style(&ProgressStyle::default_spinner()); /// ``` /// /// NOTE: These methods will silently have no effect if a /// [`IndicatifLayer`](crate::IndicatifLayer) was not registered with the tracing subscriber, /// or if this span was filtered for the registered `IndicatifLayer`. Because of this behavior, you /// can "safely" call these methods inside of non-CLI contexts as these methods will gracefully /// do nothing if you have not enabled progress bars for your tracing spans. pub trait IndicatifSpanExt { /// Sets the [`ProgressStyle`] of the progress bar associated with this span. /// /// If this span has not yet been entered, this will be the progress style the progress bar for /// this span uses when the span is entered for the first time. If this span has been entered, /// this will update the existing progress bar's style. fn pb_set_style(&self, style: &ProgressStyle); /// Briefly enters the span, which starts the progress bar for the span. /// /// Has no effect if the span has already been entered before. fn pb_start(&self); /// Sets the length of the progress bar for this span. See /// [`set_length`](indicatif::ProgressBar::set_length). fn pb_set_length(&self, len: u64); /// Sets the position of the progress bar for this span. See /// [`set_position`](indicatif::ProgressBar::set_position). /// /// WARNING: you should call [`Self::pb_set_length`] at least once before calling this method, or you /// may see a buggy progress bar. fn pb_set_position(&self, pos: u64); /// Increments the position of the progress bar for this span. See /// [`inc`](indicatif::ProgressBar::inc). /// /// WARNING: you should call [`Self::pb_set_length`] at least once before calling this method, or you /// may see a buggy progress bar. fn pb_inc(&self, delta: u64); /// Increments the length of the progress bar for this span. See /// [`inc_length`](indicatif::ProgressBar::inc_length). /// /// Has no effect if [`Self::pb_set_length`] has not been called at least once. fn pb_inc_length(&self, delta: u64); /// Sets the message of the progress bar for this span. See /// [`set_message`](indicatif::ProgressBar::set_message). fn pb_set_message(&self, msg: &str); /// Trigger a recalculation of the progress bar state. See /// [`tick`](indicatif::ProgressBar::tick). /// /// Has no effect if the progress bar for this span is not active. fn pb_tick(&self); } impl IndicatifSpanExt for Span { fn pb_set_style(&self, style: &ProgressStyle) { apply_to_indicatif_span(self, |indicatif_ctx| { // Cloning the `ProgressStyle` is necessary to make this `FnMut` :( indicatif_ctx.set_progress_bar_style(style.clone()); }); } fn pb_start(&self) { let _ = self.enter(); } fn pb_set_length(&self, len: u64) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.set_progress_bar_length(len); }); } fn pb_set_position(&self, pos: u64) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.set_progress_bar_position(pos); }); } fn pb_inc(&self, pos: u64) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.inc_progress_bar_position(pos); }); } fn pb_inc_length(&self, delta: u64) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.inc_progress_bar_length(delta); }); } fn pb_set_message(&self, msg: &str) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.set_progress_bar_message(msg.to_string()); }); } fn pb_tick(&self) { apply_to_indicatif_span(self, |indicatif_ctx| { indicatif_ctx.progress_bar_tick(); }); } } tracing-indicatif-0.3.9/src/tests.rs000064400000000000000000000540121046102023000155150ustar 00000000000000use std::io; use std::thread; use std::time::Duration; use indicatif::InMemoryTerm; use indicatif::MultiProgress; use indicatif::ProgressDrawTarget; use indicatif::ProgressStyle; use indicatif::TermLike; use tracing::info; use tracing::info_span; use tracing_core::Subscriber; use tracing_subscriber::fmt::format::DefaultFields; use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::layer::SubscriberExt; use crate::filter::hide_indicatif_span_fields; use crate::span_ext::IndicatifSpanExt; use crate::suspend_tracing_indicatif; use crate::IndicatifLayer; use crate::TickSettings; #[derive(Clone)] struct InMemoryTermWriter { progress_bars: Option, term: InMemoryTerm, } impl io::Write for InMemoryTermWriter { fn write(&mut self, buf: &[u8]) -> io::Result { if let Some(ref pb) = self.progress_bars { pb.suspend(|| self.term.write_str(std::str::from_utf8(buf).unwrap()))?; } else { self.term.write_str(std::str::from_utf8(buf).unwrap())?; } Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { if let Some(ref pb) = self.progress_bars { pb.suspend(|| self.term.flush()) } else { self.term.flush() } } } impl<'a> MakeWriter<'a> for InMemoryTermWriter { type Writer = InMemoryTermWriter; fn make_writer(&'a self) -> Self::Writer { self.clone() } } struct HelpersConfig { show_footer: bool, enable_steady_tick: bool, } impl Default for HelpersConfig { fn default() -> Self { Self { show_footer: true, enable_steady_tick: false, } } } fn make_helpers(config: HelpersConfig) -> (impl Subscriber, InMemoryTerm) { let indicatif_layer = IndicatifLayer::new() .with_max_progress_bars( 5, config.show_footer.then(|| { ProgressStyle::with_template("...and {pending_progress_bars} more not shown above.") .unwrap() }), ) .with_span_field_formatter(DefaultFields::new()) .with_progress_style( ProgressStyle::with_template("{span_child_prefix}{span_name}{{{span_fields}}}") .unwrap(), ) .with_span_child_prefix_indent("--") .with_span_child_prefix_symbol("> ") .with_tick_settings(TickSettings { term_draw_hz: 20, default_tick_interval: if config.enable_steady_tick { Some(Duration::from_millis(50)) } else { None }, footer_tick_interval: None, ..Default::default() }); let term = InMemoryTerm::new(10, 100); let mp = indicatif_layer.pb_manager.lock().unwrap().mp.clone(); mp.set_draw_target(ProgressDrawTarget::term_like(Box::new(term.clone()))); let writer = InMemoryTermWriter { progress_bars: Some(mp), term: term.clone(), }; ( tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() .with_ansi(false) .without_time() .with_writer(writer), ) .with(indicatif_layer), term, ) } #[test] fn test_one_basic_pb() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span = info_span!("foo").entered(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} "# .trim() ); }); } #[test] fn test_one_child_pb() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span = info_span!("foo").entered(); let _child_span = info_span!("child").entered(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} --> child{} "# .trim() ); }); } #[test] fn test_span_fields() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span = info_span!("foo", val = 3).entered(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{val=3} "# .trim() ); }); } #[test] fn test_multi_child_pb() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span1 = info_span!("foo", blah = 1); let _span1_enter = _span1.enter(); let _child_span1 = info_span!("foo.child"); let _child_span1_enter = _child_span1.enter(); let _child_child_span1 = info_span!("foo.child.child", blah = 3, hello = "world"); let _child_child_span1_enter = _child_child_span1.enter(); std::mem::drop(_span1_enter); std::mem::drop(_child_span1_enter); std::mem::drop(_child_child_span1_enter); let _span2 = info_span!("bar"); let _span2_enter = _span2.enter(); let _child_span2 = info_span!("bar.child"); let _child_span2_enter = _child_span2.enter(); std::mem::drop(_span2_enter); std::mem::drop(_child_span2_enter); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{blah=1} --> foo.child{} ----> foo.child.child{blah=3 hello="world"} bar{} --> bar.child{} "# .trim() ); }); } #[test] fn test_max_pbs() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span1 = info_span!("1"); _span1.pb_start(); let _span2 = info_span!("2"); _span2.pb_start(); let _span3 = info_span!("3"); _span3.pb_start(); let _span4 = info_span!("4"); _span4.pb_start(); let _span5 = info_span!("5"); _span5.pb_start(); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} "# .trim() ); let _span6 = info_span!("6"); _span6.pb_start(); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} ...and 1 more not shown above. "# .trim() ); let _span7 = info_span!("7"); _span7.pb_start(); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} ...and 2 more not shown above. "# .trim() ); std::mem::drop(_span6); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} ...and 1 more not shown above. "# .trim() ); std::mem::drop(_span1); assert_eq!( term.contents(), r#" 2{} 3{} 4{} 5{} 7{} "# .trim() ); std::mem::drop(_span2); assert_eq!( term.contents(), r#" 3{} 4{} 5{} 7{} "# .trim() ); let _span8 = info_span!("8"); _span8.pb_start(); assert_eq!( term.contents(), r#" 3{} 4{} 5{} 7{} 8{} "# .trim() ); let _span9 = info_span!("9"); _span9.pb_start(); assert_eq!( term.contents(), r#" 3{} 4{} 5{} 7{} 8{} ...and 1 more not shown above. "# .trim() ); let _span10 = info_span!("10"); _span10.pb_start(); assert_eq!( term.contents(), r#" 3{} 4{} 5{} 7{} 8{} ...and 2 more not shown above. "# .trim() ); drop(_span3); assert_eq!( term.contents(), r#" 4{} 5{} 7{} 8{} 9{} ...and 1 more not shown above. "# .trim() ); drop(_span4); assert_eq!( term.contents(), r#" 5{} 7{} 8{} 9{} 10{} "# .trim() ); }); } #[test] fn test_max_pbs_no_footer() { let (subscriber, term) = make_helpers(HelpersConfig { show_footer: false, ..Default::default() }); tracing::subscriber::with_default(subscriber, || { let _span1 = info_span!("1"); _span1.pb_start(); let _span2 = info_span!("2"); _span2.pb_start(); let _span3 = info_span!("3"); _span3.pb_start(); let _span4 = info_span!("4"); _span4.pb_start(); let _span5 = info_span!("5"); _span5.pb_start(); let _span6 = info_span!("6"); _span6.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} "# .trim() ); let _span7 = info_span!("7"); _span7.pb_start(); // Need 150ms of sleep here to trigger a refresh of the footer. thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} "# .trim() ); std::mem::drop(_span6); // Need 150ms of sleep here to trigger a refresh of the footer. thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" 1{} 2{} 3{} 4{} 5{} "# .trim() ); std::mem::drop(_span1); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" 2{} 3{} 4{} 5{} 7{} "# .trim() ); }); } #[test] fn test_parent_no_enter_doesnt_panic() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span = info_span!("foo"); let _child_span = info_span!(parent: &span, "child").entered(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} --> child{} "# .trim() ); }); } #[test] fn test_log_statements_coexist() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span1 = info_span!("foo"); _span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} "# .trim() ); info!("hello world!"); assert_eq!( term.contents() .lines() .map(|line| line.trim()) .collect::>() .join("\n"), r#" INFO tracing_indicatif::tests: hello world! foo{} "# .trim() ); }); } #[test] fn test_change_style_before_show() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_set_style(&ProgressStyle::with_template("hello_world").unwrap()); span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" hello_world "# .trim() ); }); } #[test] fn test_change_style_after_show() { let (subscriber, term) = make_helpers(HelpersConfig { enable_steady_tick: true, ..Default::default() }); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} "# .trim() ); span1.pb_set_style(&ProgressStyle::with_template("hello_world").unwrap()); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" hello_world "# .trim() ); }); } #[test] fn test_change_style_after_show_tick() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_start(); assert_eq!( term.contents(), r#" foo{} "# .trim() ); span1.pb_set_style(&ProgressStyle::with_template("hello_world").unwrap()); span1.pb_tick(); assert_eq!( term.contents(), r#" hello_world "# .trim() ); }); } #[test] fn test_bar_style_progress_bar() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_set_style(&ProgressStyle::default_bar()); span1.pb_set_length(10); span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 "# .trim() ); span1.pb_inc(1); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" █████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10 "# .trim() ); span1.pb_inc(9); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" ██████████████████████████████████████████████████████████████████████████████████████████████ 10/10 "# .trim() ); }); } #[test] fn test_bar_style_progress_bar_inc_before_start() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_set_style(&ProgressStyle::default_bar()); span1.pb_set_length(10); span1.pb_inc_length(1); span1.pb_inc(2); span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" █████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/11 "# .trim() ); span1.pb_inc(1); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" █████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 3/11 "# .trim() ); span1.pb_inc_length(1); span1.pb_inc(9); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" ██████████████████████████████████████████████████████████████████████████████████████████████ 12/12 "# .trim() ); }); } #[test] fn test_bar_style_progress_bar_inc_without_set_length() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_set_style(&ProgressStyle::default_bar()); span1.pb_inc_length(5); span1.pb_inc(2); span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/2 "# .trim() ); span1.pb_inc_length(5); span1.pb_inc(1); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 3/3 "# .trim() ); span1.pb_inc(8); thread::sleep(Duration::from_millis(150)); assert_eq!( term.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 11/11 "# .trim() ); }); } #[test] fn test_span_ext_no_effect_when_layer_not_added() { let term = InMemoryTerm::new(10, 100); let writer = InMemoryTermWriter { progress_bars: None, term: term.clone(), }; let subscriber = tracing_subscriber::registry().with( tracing_subscriber::fmt::layer() .with_ansi(false) .without_time() .with_writer(writer), ); tracing::subscriber::with_default(subscriber, || { let span1 = info_span!("foo"); span1.pb_set_style(&ProgressStyle::default_bar()); span1.pb_set_length(10); span1.pb_start(); thread::sleep(Duration::from_millis(10)); info!("hello world!"); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents().trim(), r#" INFO tracing_indicatif::tests: hello world! "# .trim() ); }); } #[test] fn test_suspend_with_layer() { let (subscriber, term) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let _span1 = info_span!("foo"); _span1.pb_start(); thread::sleep(Duration::from_millis(10)); assert_eq!( term.contents(), r#" foo{} "# .trim() ); let _ = suspend_tracing_indicatif(|| term.write_line("hello world")); assert_eq!( term.contents() .lines() .map(|line| line.trim()) .collect::>() .join("\n"), r#" hello world foo{} "# .trim() ); let _ = suspend_tracing_indicatif(|| term.write_line("this is a test")); assert_eq!( term.contents() .lines() .map(|line| line.trim()) .collect::>() .join("\n"), r#" hello world this is a test foo{} "# .trim() ); }); } #[test] fn test_suspend_without_layer() { let term = InMemoryTerm::new(10, 100); assert_eq!( term.contents(), r#" "# .trim() ); let _ = suspend_tracing_indicatif(|| term.write_line("hello world")); assert_eq!( term.contents() .lines() .map(|line| line.trim()) .collect::>() .join("\n"), r#" hello world "# .trim() ); } #[test] fn test_with_span_field_formatter() { let indicatif_layer = IndicatifLayer::new() .with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new())); let subscriber = tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_writer(indicatif_layer.get_stderr_writer())) .with(indicatif_layer); tracing::subscriber::with_default(subscriber, || { let _span = info_span!("foo"); _span.pb_start(); suspend_tracing_indicatif(|| {}); }); } #[test] fn test_parent_span_enter_ordering() { let (subscriber, _) = make_helpers(HelpersConfig::default()); tracing::subscriber::with_default(subscriber, || { let grandparent_span = info_span!("grandparent"); let parent_span = info_span!(parent: &grandparent_span, "parent"); let child_span = info_span!(parent: &parent_span, "child"); let span1 = info_span!("span1"); span1.pb_start(); let span2 = info_span!("span2"); span2.pb_start(); let span3 = info_span!("span3"); span3.pb_start(); let span4 = info_span!("span4"); span4.pb_start(); let span5 = info_span!("span5"); span5.pb_start(); child_span.pb_start(); grandparent_span.pb_start(); drop(span1); }); } // These don't actually run anything, but exist to type check macros. #[allow(dead_code)] fn type_check_indicatif_println() { indicatif_println!("{}", "hello"); } #[allow(dead_code)] fn type_check_indicatif_eprintln() { indicatif_eprintln!("{}", "hello"); } tracing-indicatif-0.3.9/src/util.rs000064400000000000000000000101761046102023000153330ustar 00000000000000//! Provides some general tracing utilities that are useful for this crate, for example a utility //! to filter "indicatif.pb_show" and "indicatif.pb_hide" from printing spans. use std::fmt; use tracing::field::Visit; use tracing_core::Field; use tracing_subscriber::field::RecordFields; use tracing_subscriber::{ field::{MakeVisitor, VisitFmt, VisitOutput}, fmt::{format::Writer, FormatFields}, }; /// Wraps around an existing struct that impls [`FormatFields`], but allows for filtering specific /// fields from spans or events. pub struct FilteredFormatFields { format: Format, filter: Filter, } impl<'writer, Format, Filter> FilteredFormatFields where Format: MakeVisitor>, Format::Visitor: VisitFmt + VisitOutput, Filter: Clone + Fn(&Field) -> bool, { /// Wraps around an existing struct that impls [`FormatFields`], but filters out any fields which /// returns _false_ when passed into `filter`. pub fn new(format: Format, filter: Filter) -> Self { Self { format, filter } } } impl<'writer, Format, Filter> FormatFields<'writer> for FilteredFormatFields where Format: MakeVisitor>, Format::Visitor: VisitFmt + VisitOutput, Filter: Clone + Fn(&Field) -> bool, { fn format_fields( &self, writer: Writer<'writer>, fields: R, ) -> std::fmt::Result { let mut v = FilteredFormatFieldsVisitor::new(self.format.make_visitor(writer), self.filter.clone()); fields.record(&mut v); v.finish()?; Ok(()) } } struct FilteredFormatFieldsVisitor { visitor: Visitor, filter: Filter, } impl FilteredFormatFieldsVisitor { fn new(visitor: Visitor, filter: Filter) -> Self { Self { visitor, filter } } } impl Visit for FilteredFormatFieldsVisitor where Visitor: Visit, Filter: Fn(&Field) -> bool, { fn record_debug(&mut self, field: &tracing_core::Field, value: &dyn std::fmt::Debug) { if (self.filter)(field) { self.visitor.record_debug(field, value); } } fn record_f64(&mut self, field: &tracing_core::Field, value: f64) { if (self.filter)(field) { self.visitor.record_f64(field, value); } } fn record_i64(&mut self, field: &tracing_core::Field, value: i64) { if (self.filter)(field) { self.visitor.record_i64(field, value); } } fn record_u64(&mut self, field: &tracing_core::Field, value: u64) { if (self.filter)(field) { self.visitor.record_u64(field, value); } } fn record_str(&mut self, field: &tracing_core::Field, value: &str) { if (self.filter)(field) { self.visitor.record_str(field, value); } } fn record_i128(&mut self, field: &tracing_core::Field, value: i128) { if (self.filter)(field) { self.visitor.record_i128(field, value); } } fn record_u128(&mut self, field: &tracing_core::Field, value: u128) { if (self.filter)(field) { self.visitor.record_u128(field, value); } } fn record_bool(&mut self, field: &tracing_core::Field, value: bool) { if (self.filter)(field) { self.visitor.record_bool(field, value); } } fn record_error( &mut self, field: &tracing_core::Field, value: &(dyn std::error::Error + 'static), ) { if (self.filter)(field) { self.visitor.record_error(field, value); } } } impl VisitOutput for FilteredFormatFieldsVisitor where Visitor: VisitOutput, Filter: Fn(&Field) -> bool, { fn finish(self) -> fmt::Result { self.visitor.finish() } } impl VisitFmt for FilteredFormatFieldsVisitor where Visitor: VisitFmt, Filter: Fn(&Field) -> bool, { fn writer(&mut self) -> &mut dyn fmt::Write { self.visitor.writer() } } tracing-indicatif-0.3.9/src/writer.rs000064400000000000000000000114501046102023000156660ustar 00000000000000//! Helpers to prevent progress bars from clobbering your console output. use std::io; use std::marker::PhantomData; use indicatif::MultiProgress; use tracing_subscriber::fmt::MakeWriter; pub trait WriterTarget: private::Sealed {} /// Marker for where the [`IndicatifWriter`] should write to. pub struct Stdout {} /// Marker for where the [`IndicatifWriter`] should write to. pub struct Stderr {} impl WriterTarget for Stdout {} impl WriterTarget for Stderr {} // TODO(emersonford): find a cleaner way to integrate this layer with fmt::Layer. /// A wrapper around [`std::io::stdout()`] or [`std::io::stderr()`] to ensure that output to either is /// not clobbered by active progress bars. This should be passed into tracing fmt's layer so /// tracing log entries are not clobbered. pub struct IndicatifWriter { progress_bars: MultiProgress, inner: PhantomData, } impl IndicatifWriter where T: WriterTarget, { pub(crate) fn new(mp: MultiProgress) -> Self { Self { progress_bars: mp, inner: PhantomData, } } } impl Clone for IndicatifWriter { fn clone(&self) -> Self { Self { progress_bars: self.progress_bars.clone(), inner: self.inner, } } } impl io::Write for IndicatifWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.progress_bars.suspend(|| io::stdout().write(buf)) } fn flush(&mut self) -> io::Result<()> { self.progress_bars.suspend(|| io::stdout().flush()) } fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { self.progress_bars .suspend(|| io::stdout().write_vectored(bufs)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.progress_bars.suspend(|| io::stdout().write_all(buf)) } fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { self.progress_bars.suspend(|| io::stdout().write_fmt(fmt)) } } impl io::Write for IndicatifWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.progress_bars.suspend(|| io::stderr().write(buf)) } fn flush(&mut self) -> io::Result<()> { self.progress_bars.suspend(|| io::stderr().flush()) } fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { self.progress_bars .suspend(|| io::stderr().write_vectored(bufs)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.progress_bars.suspend(|| io::stderr().write_all(buf)) } fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { self.progress_bars.suspend(|| io::stderr().write_fmt(fmt)) } } impl<'a> MakeWriter<'a> for IndicatifWriter { type Writer = IndicatifWriter; fn make_writer(&'a self) -> Self::Writer { self.clone() } } impl<'a> MakeWriter<'a> for IndicatifWriter { type Writer = IndicatifWriter; fn make_writer(&'a self) -> Self::Writer { self.clone() } } mod private { pub trait Sealed {} impl Sealed for super::Stdout {} impl Sealed for super::Stderr {} } /// Returns the stderr writer (equivalent to /// [`get_stderr_writer`](crate::IndicatifLayer::get_stderr_writer)) of the registered /// [`IndicatifLayer`](crate::IndicatifLayer) for the current default tracing subscriber. /// /// Returns `None` if there is either no default tracing subscriber or if there is not a /// `IndicatifLayer` registered with that subscriber. pub fn get_indicatif_stderr_writer() -> Option> { tracing::dispatcher::get_default(|dispatch| { dispatch .downcast_ref::() .and_then(|ctx| { let mut ret: Option> = None; ctx.with_context(dispatch, |writer| { ret = Some(writer); }); ret }) }) } /// Returns the stdout writer (equivalent to /// [`get_stdout_writer`](crate::IndicatifLayer::get_stdout_writer)) of the registered /// [`IndicatifLayer`](crate::IndicatifLayer) for the current default tracing subscriber. /// /// Returns `None` if there is either no default tracing subscriber or if there is not a /// `IndicatifLayer` registered with that subscriber. pub fn get_indicatif_stdout_writer() -> Option> { tracing::dispatcher::get_default(|dispatch| { dispatch .downcast_ref::() .and_then(|ctx| { let mut ret: Option> = None; ctx.with_context(dispatch, |writer| { ret = Some(writer); }); ret }) }) }