diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..0e45b63 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,8 @@ +[advisories] +ignore = [ + "RUSTSEC-2020-0071", # `chrono` - Potential segfault in the time crate +] + +[output] +quiet = false +deny = ["warnings"] diff --git a/Cargo.lock b/Cargo.lock index 8cf928c..3109bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "atty" version = "0.2.14" @@ -67,6 +76,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + [[package]] name = "byteorder" version = "1.4.3" @@ -79,12 +94,33 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clipboard-win" version = "3.1.1" @@ -95,6 +131,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colored" version = "2.0.0" @@ -138,6 +184,12 @@ dependencies = [ "x11-clipboard", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "crc32fast" version = "1.3.2" @@ -190,6 +242,50 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.15", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -250,6 +346,40 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.25" @@ -266,6 +396,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gethostname" version = "0.2.3" @@ -293,7 +432,7 @@ checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -353,6 +492,42 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ignore" version = "0.4.20" @@ -370,12 +545,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy-bytes-cast" version = "5.0.1" @@ -404,6 +611,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -492,6 +724,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" @@ -553,6 +804,29 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + [[package]] name = "parseit" version = "0.1.2" @@ -680,6 +954,20 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustix" +version = "0.37.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.13" @@ -707,6 +995,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + [[package]] name = "serde" version = "1.0.160" @@ -812,7 +1106,9 @@ dependencies = [ name = "systeroid" version = "0.3.2" dependencies = [ + "env_logger", "getopts", + "log", "parseit", "systeroid-core", ] @@ -824,6 +1120,7 @@ dependencies = [ "colored", "dirs-next", "lazy_static", + "log", "parseit", "rayon", "rust-ini", @@ -840,13 +1137,24 @@ dependencies = [ "colorsys", "copypasta-ext", "getopts", + "log", "ratatui", "systeroid-core", "termion", "thiserror", + "tui-logger", "unicode-width", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "termion" version = "2.0.1" @@ -889,6 +1197,31 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tui-logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9e708d73f97e548b2d6e75f9931aff915a5b8bbad8fc7c7a63273994af6b5b" +dependencies = [ + "chrono", + "fxhash", + "lazy_static", + "log", + "parking_lot", + "ratatui", +] + [[package]] name = "unicode-ident" version = "1.0.8" @@ -923,12 +1256,72 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + [[package]] name = "wayland-client" version = "0.29.5" @@ -1053,6 +1446,147 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "x11-clipboard" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 56d829c..2654e07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = ["systeroid-core", "systeroid-tui", "systeroid"] [workspace.dependencies] parseit = { version = "0.1.2", features = ["gzip"] } +log = { version = "0.4.17", features = ["std"] } [profile.dev] opt-level = 0 diff --git a/Dockerfile b/Dockerfile index fcad436..d53e0da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.66.1-alpine3.17 as builder +FROM rust:1.69.0-alpine3.17 as builder WORKDIR /app RUN apk update RUN apk add --no-cache musl-dev bash git diff --git a/README.md b/README.md index a58613c..0b5e226 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Although **systeroid** does not need the parameter section to be specified expli - [Loading values from the system directories](#loading-values-from-the-system-directories) - [Searching parameters](#searching-parameters) - [Showing information about parameters](#showing-information-about-parameters) + - [Verbose logging](#verbose-logging) - [TUI](#tui) - [Usage](#usage-2) - [Key Bindings](#key-bindings) @@ -93,6 +94,7 @@ Although **systeroid** does not need the parameter section to be specified expli - [Changing the colors](#changing-the-colors) - [Viewing the parameter documentation](#viewing-the-parameter-documentation) - [Setting the refresh rate](#setting-the-refresh-rate) + - [Logging](#logging) - [Configuration](#configuration) - [Resources](#resources) - [References](#references) @@ -398,6 +400,20 @@ It is also possible to retrieve information about multiple parameters: systeroid -E --pattern '.*ipv4.*' --no-pager ``` +#### Verbose logging + +`--verbose` flag can be used to enable verbose logging: + +```sh +systeroid --verbose +``` + +Also, `RUST_LOG` environment variable can be set accordingly to filter based on different log levels. + +```sh +RUST_LOG=trace systeroid +``` + ## TUI ### Usage @@ -440,6 +456,7 @@ systeroid-tui [options] | enter | select / set parameter value | | s | save parameter value | | c | copy to clipboard | +| ctrl-l, f2 | show logs | | r, f5 | refresh | | esc | cancel / exit | | q, ctrl-c/ctrl-d | exit | @@ -524,15 +541,16 @@ Press : to open the command prompt for running a command. Available c | Command | Description | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `:help` | Show help | -| `:search` | Enable search | -| `:select` | Select the current parameter in the list | -| `:set ` | Set parameter value | -| `:save ` | Save parameter value to file | -| `:scroll [area] [direction] ` | Scroll the list or text
- areas: `list`, `docs`, `section`
- directions: `up`, `down`, `top`, `bottom`, `right`, `left` | -| `:copy` | Copy to clipboard | -| `:refresh` | Refresh values | -| `:quit`, `:q` | Quit | +| `:help` | show help | +| `:search` | enable search | +| `:select` | select the current parameter in the list | +| `:set ` | set parameter value | +| `:save ` | save parameter value to file | +| `:scroll [area] [direction] ` | scroll the list or text
- areas: `list`, `docs`, `section`
- directions: `up`, `down`, `top`, `bottom`, `right`, `left` | +| `:copy` | copy to clipboard | +| `:logs` | show logs | +| `:refresh` | refresh values | +| `:quit`, `:q` | quit | #### Copying to clipboard @@ -570,6 +588,55 @@ It is possible to specify a value in milliseconds via `--tick-rate` argument for systeroid-tui --tick-rate 500 ``` +#### Logging + +To view the log messages, press ctrl-l. It will bring up a pane in the TUI for analyzing the logs: + +![Logs](assets/systeroid-tui-logs.gif) + +This pane consists of two parts. Left is the target selector and on the right side the logging messages view scrolling up. + +The target selector controls: + +- Capturing of log messages by the logger. +- Selection of levels for display in the logging message view. + +The two columns have the following meaning: + +- Code `EWIDT`: `E` stands for Error, `W` for Warn, and similarly Info, Debug and Trace. + - Inverted characters (EWIDT) are enabled log levels in the view. + - Normal characters show enabled capturing of a log level per target. + - If any of EWIDT are not shown, then the respective log level is not captured. + +This logger pane has the following key bindings and they are only activated while the logs are being shown: + +| Key | Action | +| ------------------- | -------------------------------------------------------------- | +| h | toggles target selector widget hidden/visible | +| f | toggle focus on the selected target only | +| up | select previous target in target selector widget | +| down | select next target in target selector widget | +| left | reduce SHOWN (!) log messages by one level | +| right | increase SHOWN (!) log messages by one level | +| - | reduce CAPTURED (!) log messages by one level | +| + | increase CAPTURED (!) log messages by one level | +| pageup | enter Page Mode and scroll approx. half page up in log history | +| pagedown | only in page mode: scroll 10 events down in log history | +| escape | exit page mode and go back to scrolling mode | +| space | toggles hiding of targets, which have logfilter set to off | + +For saving the logs to a file, you can use the `--log-file` argument: + +```sh +systeroid-tui --log-file systeroid.log +``` + +`RUST_LOG` environment variable can be used to set the log level accordingly. + +```sh +RUST_LOG=debug systeroid-tui +``` + ## Configuration **systeroid** can be configured with a configuration file that uses the [INI format](https://en.wikipedia.org/wiki/INI_file). It can be specified via `--config` or `SYSTEROID_CONFIG` environment variable. It can also be placed in one of the following global locations: diff --git a/assets/systeroid-tui-logs.gif b/assets/systeroid-tui-logs.gif new file mode 100644 index 0000000..53e038b Binary files /dev/null and b/assets/systeroid-tui-logs.gif differ diff --git a/config/systeroid.conf b/config/systeroid.conf index bd37ea5..40f9f57 100644 --- a/config/systeroid.conf +++ b/config/systeroid.conf @@ -17,8 +17,6 @@ display_deprecated = false kernel_docs = "/usr/share/doc/linux" [cli] -; enable verbose logging -verbose = false ; ignore unknown variable errors ignore_errors = true ; do not print variable after the value is set @@ -59,6 +57,8 @@ tick_rate = 250 no_docs = true ; path for saving the changed kernel parameters save_path = "/etc/sysctl.conf" +; file to save the logs +;log_file = "systeroid.log" [tui.colors] ; available colors are defined in https://docs.rs/tui/latest/tui/style/enum.Color.html diff --git a/man8/systeroid-tui.8 b/man8/systeroid-tui.8 index eb7d241..41e8d86 100644 --- a/man8/systeroid-tui.8 +++ b/man8/systeroid-tui.8 @@ -23,6 +23,9 @@ Use this option to set a custom path for the kernel documentation. \fB\-\-save\-path\fR Use this option to set the path for saving the changed parameters. .TP +\fB\-l\fR, \fB\-\-log\-file\fR +Use this option to set the file to save the logs. +.TP \fB\-s\fR, \fB\-\-section\fR
Use this option to set the section to filter. .HP @@ -61,6 +64,8 @@ systeroid-tui \-D /usr/share/doc/linux .br systeroid-tui \-\-save-path 99-local.conf .br +systeroid-tui \-\-log-file systeroid.log +.br systeroid-tui -n .SH KEY BINDINGS diff --git a/systeroid-core/Cargo.toml b/systeroid-core/Cargo.toml index b36f342..18bdb7d 100644 --- a/systeroid-core/Cargo.toml +++ b/systeroid-core/Cargo.toml @@ -12,6 +12,8 @@ edition = "2021" rust-version = "1.64.0" [dependencies] +parseit.workspace = true +log.workspace = true sysctl = "0.5.4" thiserror = "1.0.40" lazy_static = "1.4.0" @@ -20,5 +22,4 @@ colored = "2.0.0" serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" dirs-next = "2.0.0" -parseit.workspace = true rust-ini = "0.18.0" diff --git a/systeroid-core/src/config.rs b/systeroid-core/src/config.rs index f7c11f9..447f565 100644 --- a/systeroid-core/src/config.rs +++ b/systeroid-core/src/config.rs @@ -54,8 +54,6 @@ pub struct Config { /// CLI configuration. #[derive(Clone, Debug)] pub struct CliConfig { - /// Whether if the verbose logging is enabled. - pub verbose: bool, /// Whether if the errors should be ignored. pub ignore_errors: bool, /// Whether if the quiet mode is enabled. @@ -88,6 +86,8 @@ pub struct TuiConfig { pub no_docs: bool, /// Path for saving the changed kernel parameters. pub save_path: Option, + /// File to save the logs. + pub log_file: Option, /// Color configuration. pub color: TuiColorConfig, } @@ -116,6 +116,7 @@ impl Config { } } if let Some(path) = config_path { + log::trace!(target: "config", "Parsing configuration from {:?}", path); let ini = Ini::load_from_file(path)?; if let Some(general_section) = ini.section(Some("general")) { if let Some(display_deprecated) = general_section.get("display_deprecated") { @@ -126,7 +127,6 @@ impl Config { } } if let Some(section) = ini.section(Some("cli")) { - parse_ini_flag!(self, cli, section, verbose); parse_ini_flag!(self, cli, section, ignore_errors); parse_ini_flag!(self, cli, section, quiet); parse_ini_flag!(self, cli, section, no_pager); @@ -162,6 +162,9 @@ impl Config { if let Some(save_path) = section.get("save_path") { self.tui.save_path = Some(PathBuf::from(save_path)); } + if let Some(log_file) = section.get("log_file") { + self.tui.log_file = Some(log_file.to_string()); + } parse_ini_flag!(self, tui, section, no_docs); } if let Some(section) = ini.section(Some("tui.colors")) { @@ -183,7 +186,6 @@ impl Default for Config { display_deprecated: false, kernel_docs: None, cli: CliConfig { - verbose: false, ignore_errors: false, quiet: false, no_pager: false, @@ -207,6 +209,7 @@ impl Default for Config { tick_rate: 250, no_docs: false, save_path: None, + log_file: None, color: TuiColorConfig { fg_color: String::from("white"), bg_color: String::from("black"), diff --git a/systeroid-core/src/sysctl/controller.rs b/systeroid-core/src/sysctl/controller.rs index 2705972..00b02ab 100644 --- a/systeroid-core/src/sysctl/controller.rs +++ b/systeroid-core/src/sysctl/controller.rs @@ -51,9 +51,7 @@ impl Sysctl { } } Err(e) => { - if config.cli.verbose { - eprintln!("{} ({})", e, ctl.name()?); - } + log::trace!(target: "sysctl", "{} ({})", e, ctl.name()?); } } } @@ -68,6 +66,7 @@ impl Sysctl { /// Returns the parameters that matches the given query. pub fn get_parameters(&self, query: &str) -> Vec<&Parameter> { + log::trace!(target: "sysctl", "Querying parameters: {:?}", query); let query = query.replace('/', "."); let parameters = self .parameters @@ -79,7 +78,8 @@ impl Sysctl { }) .collect::>(); if parameters.is_empty() && !self.config.cli.ignore_errors { - eprintln!( + log::error!( + target: "sysctl", "{}: cannot stat {}{}: No such file or directory", env!("CARGO_PKG_NAME").split('-').collect::>()[0], PROC_PATH, @@ -91,6 +91,7 @@ impl Sysctl { /// Updates the descriptions of the kernel parameters using the given cached data. pub fn update_docs_from_cache(&mut self, cache: &Cache) -> Result<()> { + log::trace!(target: "cache", "{:?}", cache); let mut kernel_docs_path = if let Some(path) = &self.config.kernel_docs { vec![path.to_path_buf()] } else { @@ -109,6 +110,7 @@ impl Sysctl { } if let Some(path) = kernel_docs_path.iter().find(|path| path.exists()) { if cache.exists(PARAMETERS_CACHE_LABEL) { + log::trace!(target: "cache", "Cache hit for {:?}", path); let cache_data = cache.read(PARAMETERS_CACHE_LABEL)?; if cache_data.timestamp == CacheData::<()>::get_timestamp(path)? { self.update_params(cache_data.data); @@ -117,13 +119,14 @@ impl Sysctl { } self.update_docs(path)?; if env::var(DISABLE_CACHE_ENV).is_err() { + log::trace!(target: "cache", "Writing cache to {:?}", cache); cache.write( CacheData::new(&self.parameters, path)?, PARAMETERS_CACHE_LABEL, )?; } } else { - eprintln!("warning: `Linux kernel documentation cannot be found. Please specify a path via '-D' argument`"); + log::error!(target: "sysctl", "warning: `Linux kernel documentation cannot be found. Please specify a path via '-D' argument`"); } Ok(()) } @@ -146,6 +149,7 @@ impl Sysctl { /// Updates the descriptions of the kernel parameters. fn update_docs(&mut self, kernel_docs: &Path) -> Result<()> { + log::trace!(target: "sysctl", "Parsing the kernel documentation from {:?}", kernel_docs); let documents = parse_kernel_docs(kernel_docs)?; self.parameters .par_iter_mut() @@ -187,6 +191,13 @@ impl Sysctl { let save_path = save_path .clone() .unwrap_or_else(|| PathBuf::from(DEFAULT_PRELOAD)); + log::trace!( + target: "param", + "Writing the new value ({:?}) of {:?} to {:?}", + new_value, + param_name, + save_path + ); let data = format!("{param_name} = {new_value}"); if save_path.exists() { let contents = reader::read_to_string(&save_path)?; diff --git a/systeroid-core/src/sysctl/parameter.rs b/systeroid-core/src/sysctl/parameter.rs index b8ac014..7e72cf5 100644 --- a/systeroid-core/src/sysctl/parameter.rs +++ b/systeroid-core/src/sysctl/parameter.rs @@ -188,6 +188,7 @@ impl Parameter { config: &Config, output: &mut Output, ) -> Result<()> { + log::trace!(target: "param", "Setting the value of {:?} to {:?}", self.name, new_value); let ctl = Ctl::new(&self.name)?; let new_value = ctl.set_value_string(new_value)?; self.value = new_value; diff --git a/systeroid-tui/Cargo.toml b/systeroid-tui/Cargo.toml index 1c0f2aa..b8d24b7 100644 --- a/systeroid-tui/Cargo.toml +++ b/systeroid-tui/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/orhun/systeroid" keywords = ["linux", "kernel", "parameter", "sysctl", "tui"] categories = ["command-line-utilities"] edition = "2021" -rust-version = "1.64.0" +rust-version = "1.68.2" [features] # clipboard support is enabled as default @@ -24,6 +24,7 @@ thiserror = "1.0.40" getopts = "0.2.21" copypasta-ext = { version = "0.4.4", optional = true } colorsys = "0.6.7" +log.workspace = true [dependencies.systeroid-core] version = "0.3.2" # managed by release.sh @@ -35,6 +36,11 @@ version = "0.20.1" default-features = false features = ["termion"] +[dependencies.tui-logger] +version = "0.9.0" +default-features = false +features = ["ratatui-support"] + # metadata for cargo-binstall to get the right artifacts [package.metadata.binstall] pkg-url = "{ repo }/releases/download/v{ version }/systeroid-{ version }-{ target }.{ archive-format }" diff --git a/systeroid-tui/src/app.rs b/systeroid-tui/src/app.rs index 6c0b8fb..69f83b2 100644 --- a/systeroid-tui/src/app.rs +++ b/systeroid-tui/src/app.rs @@ -4,11 +4,13 @@ use crate::options::{CopyOption, Direction, ScrollArea}; use crate::widgets::SelectableList; #[cfg(feature = "clipboard")] use copypasta_ext::{display::DisplayServer, ClipboardProviderExt}; +use log::{Level, LevelFilter}; use std::str::FromStr; use std::time::Instant; use systeroid_core::sysctl::controller::Sysctl; use systeroid_core::sysctl::parameter::Parameter; use systeroid_core::sysctl::section::Section; +use tui_logger::TuiWidgetState; use unicode_width::UnicodeWidthStr; /// Representation of a key binding. @@ -87,6 +89,11 @@ pub const KEY_BINDINGS: &[&KeyBinding] = &[ action: "copy to clipboard", command: None, }, + &KeyBinding { + key: "ctrl-l, f2", + action: "show logs", + command: Some("logs"), + }, &KeyBinding { key: "r, f5", action: "refresh", @@ -113,6 +120,10 @@ pub struct App<'a> { pub running: bool, /// Whether if the help message is shown. pub show_help: bool, + /// Whether if the logs are shown. + pub show_logs: bool, + /// Logger state. + pub logger_state: TuiWidgetState, /// Input buffer. pub input: Option, /// Time tracker for measuring the time for clearing the input. @@ -144,6 +155,8 @@ impl<'a> App<'a> { let mut app = Self { running: true, show_help: false, + show_logs: false, + logger_state: TuiWidgetState::new().set_default_display_level(LevelFilter::Trace), input: None, input_time: None, input_cursor: 0, @@ -167,16 +180,16 @@ impl<'a> App<'a> { app.parameter_list.items = app.sysctl.parameters.clone(); #[cfg(feature = "clipboard")] { - app.clipboard = match DisplayServer::select().try_context() { - None => { - app.input = Some(String::from( + app.clipboard = + match DisplayServer::select().try_context() { + None => { + app.log(Level::Error, String::from( "Failed to initialize clipboard, no suitable clipboard provider found", )); - app.input_time = Some(Instant::now()); - None + None + } + clipboard => clipboard, } - clipboard => clipboard, - } } app } @@ -186,6 +199,13 @@ impl<'a> App<'a> { self.input.is_some() && self.input_time.is_none() } + /// Sets the log message for the application. + pub fn log(&mut self, level: Level, message: String) { + log::log!(target: "tui", level, "{message}"); + self.input = Some(message); + self.input_time = Some(Instant::now()); + } + /// Performs a search operation in the kernel parameter list. pub fn search(&mut self) { let section = self @@ -228,7 +248,8 @@ impl<'a> App<'a> { /// Copies the selected entry to the clipboard. #[cfg(feature = "clipboard")] fn copy_to_clipboard(&mut self, copy_option: CopyOption) -> Result<()> { - self.input = Some(if let Some(clipboard) = self.clipboard.as_mut() { + log::debug!(target: "tui", "Copying to clipboard: {:?}", copy_option); + if let Some(clipboard) = self.clipboard.as_mut() { if let Some(parameter) = self.parameter_list.selected() { match copy_option { CopyOption::Name => clipboard.set_contents(parameter.name.clone()), @@ -238,22 +259,23 @@ impl<'a> App<'a> { } } .map_err(|e| crate::error::Error::ClipboardError(e.to_string()))?; - String::from("Copied to clipboard!") + self.log(Level::Info, String::from("Copied to clipboard!")); } else { - String::from("No parameter is selected") + self.log(Level::Warn, String::from("No parameter is selected")); } } else { - String::from("Clipboard is not initialized") - }); - self.input_time = Some(Instant::now()); + self.log(Level::Error, String::from("Clipboard is not initialized")); + } Ok(()) } /// Shows a message about clipboard being not enabled. #[cfg(not(feature = "clipboard"))] fn copy_to_clipboard(&mut self, _: CopyOption) -> Result<()> { - self.input = Some(String::from("Clipboard support is not enabled")); - self.input_time = Some(Instant::now()); + self.log( + Level::Warn, + String::from("Clipboard support is not enabled"), + ); Ok(()) } @@ -268,6 +290,12 @@ impl<'a> App<'a> { self.show_help = true; hide_popup = false; } + Command::Logs => { + self.show_logs = !self.show_logs; + } + Command::LoggerEvent(event) => { + self.logger_state.transition(&event.0); + } Command::Select => { if let Some(copy_option) = self .options @@ -312,8 +340,7 @@ impl<'a> App<'a> { self.run_command(Command::Refresh)?; } Err(e) => { - self.input = Some(e.to_string()); - self.input_time = Some(Instant::now()); + self.log(Level::Error, e.to_string()); } } if save_to_file { @@ -323,17 +350,15 @@ impl<'a> App<'a> { &self.sysctl.config.tui.save_path, ) { Ok(path) => { - self.input = Some(format!("Saved to file: {path:?}")); + self.log(Level::Info, format!("Saved to file: {path:?}")); } Err(e) => { - self.input = Some(format!("Failed to save: {e}")); + self.log(Level::Error, format!("Failed to save: {e}")); } } - self.input_time = Some(Instant::now()); } } else { - self.input = Some(String::from("Unknown parameter")); - self.input_time = Some(Instant::now()); + self.log(Level::Warn, String::from("Unknown parameter")); } } Command::Scroll(ScrollArea::List, Direction::Up, amount) => { @@ -442,8 +467,7 @@ impl<'a> App<'a> { self.run_command(command)?; hide_popup = false; } else { - self.input = Some(String::from("Unknown command")); - self.input_time = Some(Instant::now()); + self.log(Level::Warn, String::from("Unknown command")); } } } @@ -516,8 +540,7 @@ impl<'a> App<'a> { hide_popup = false; self.show_help = false; } else { - self.input = Some(String::from("No parameter is selected")); - self.input_time = Some(Instant::now()); + self.log(Level::Warn, String::from("No parameter is selected")); } } Command::Refresh => { @@ -531,6 +554,7 @@ impl<'a> App<'a> { parameter.value = param.value.to_string(); } }); + self.log(Level::Info, String::from("Refreshed!")); } Command::Cancel => { if self.input.is_some() { diff --git a/systeroid-tui/src/args.rs b/systeroid-tui/src/args.rs index 1ada09c..6cb2412 100644 --- a/systeroid-tui/src/args.rs +++ b/systeroid-tui/src/args.rs @@ -26,6 +26,8 @@ pub struct Args { pub kernel_docs: Option, /// Path for the changed parameters. pub save_path: Option, + /// File to save the logs. + pub log_file: Option, /// Sysctl section to filter. pub section: Option
, /// Query to search on startup. @@ -62,6 +64,7 @@ impl Args { "set the path for saving the changed parameters", "", ); + opts.optopt("l", "log-file", "set the file to save the logs", ""); opts.optopt("s", "section", "set the section to filter", "
"); opts.optopt("q", "query", "set the query to search", ""); opts.optopt( @@ -123,6 +126,7 @@ impl Args { .or_else(|| env::var(KERNEL_DOCS_ENV).ok()) .map(PathBuf::from), save_path: matches.opt_str("save-path").map(PathBuf::from), + log_file: matches.opt_str("l"), section: matches.opt_str("s").map(Section::from), search_query: matches.opt_str("q"), fg_color: matches diff --git a/systeroid-tui/src/command.rs b/systeroid-tui/src/command.rs index 2518689..f274e2c 100644 --- a/systeroid-tui/src/command.rs +++ b/systeroid-tui/src/command.rs @@ -1,12 +1,44 @@ use crate::options::{Direction, ScrollArea}; use std::str::FromStr; use termion::event::Key; +use tui_logger::TuiWidgetEvent; + +/// Possible logger widget commands. +#[derive(Debug, PartialEq)] +pub struct LoggerCommand(pub TuiWidgetEvent); + +impl Eq for LoggerCommand {} + +impl LoggerCommand { + /// Parses a logger command from the given key. + pub fn parse(key: Key) -> Option { + match key { + Key::Char(' ') => Some(Self(TuiWidgetEvent::SpaceKey)), + Key::Esc => Some(Self(TuiWidgetEvent::EscapeKey)), + Key::PageUp => Some(Self(TuiWidgetEvent::PrevPageKey)), + Key::PageDown => Some(Self(TuiWidgetEvent::NextPageKey)), + Key::Up => Some(Self(TuiWidgetEvent::UpKey)), + Key::Down => Some(Self(TuiWidgetEvent::DownKey)), + Key::Left => Some(Self(TuiWidgetEvent::LeftKey)), + Key::Right => Some(Self(TuiWidgetEvent::RightKey)), + Key::Char('+') => Some(Self(TuiWidgetEvent::PlusKey)), + Key::Char('-') => Some(Self(TuiWidgetEvent::MinusKey)), + Key::Char('h') => Some(Self(TuiWidgetEvent::HideKey)), + Key::Char('f') => Some(Self(TuiWidgetEvent::FocusKey)), + _ => None, + } + } +} /// Possible application commands. #[derive(Debug, PartialEq, Eq)] pub enum Command { /// Show help. Help, + /// Show logs. + Logs, + /// Logger event. + LoggerEvent(LoggerCommand), /// Perform an action based on the selected entry. Select, /// Save the value of a parameter to a file. @@ -42,6 +74,7 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match s { "help" => Ok(Command::Help), + "logs" => Ok(Command::Logs), "search" => Ok(Command::Search), "select" => Ok(Command::Select), "copy" => Ok(Command::Copy), @@ -91,6 +124,7 @@ impl Command { } else { match key { Key::Char('?') | Key::F(1) => Command::Help, + Key::Ctrl('l') | Key::F(2) => Command::Logs, Key::Up | Key::Char('k') => Command::Scroll(ScrollArea::List, Direction::Up, 1), Key::Down | Key::Char('j') => Command::Scroll(ScrollArea::List, Direction::Down, 1), Key::PageUp => Command::Scroll(ScrollArea::List, Direction::Up, 4), @@ -135,6 +169,7 @@ mod tests { fn test_command() { for (command, value) in vec![ (Command::Help, "help"), + (Command::Logs, "logs"), (Command::Search, "search"), (Command::Select, "select"), (Command::Copy, "copy"), @@ -181,6 +216,7 @@ mod tests { assert_command_parser! { input_mode: false, Key::Char('?') => Command::Help, + Key::Ctrl('l') => Command::Logs, Key::Up => Command::Scroll(ScrollArea::List, Direction::Up, 1), Key::Down => Command::Scroll(ScrollArea::List, Direction::Down, 1), Key::PageUp => Command::Scroll(ScrollArea::List, Direction::Up, 4), diff --git a/systeroid-tui/src/error.rs b/systeroid-tui/src/error.rs index 9bd4f18..84ad58f 100644 --- a/systeroid-tui/src/error.rs +++ b/systeroid-tui/src/error.rs @@ -15,6 +15,12 @@ pub enum Error { /// Error that may occur while parsing a color. #[error(transparent)] ColorParseError(#[from] colorsys::ParseError), + /// Error that may occur if the logger is already set. + #[error(transparent)] + LoggerSetError(#[from] log::SetLoggerError), + /// Error that may occur when the string doesn’t match any of the log levels. + #[error(transparent)] + LoggerParseError(#[from] log::ParseLevelError), /// Error that may occur in the core library. #[error(transparent)] SysctlError(#[from] systeroid_core::error::Error), diff --git a/systeroid-tui/src/lib.rs b/systeroid-tui/src/lib.rs index dadad9d..8044c27 100644 --- a/systeroid-tui/src/lib.rs +++ b/systeroid-tui/src/lib.rs @@ -27,6 +27,10 @@ use crate::command::Command; use crate::error::Result; use crate::event::{Event, EventHandler}; use crate::style::Colors; +use command::LoggerCommand; +use log::LevelFilter; +use std::env; +use std::str::FromStr; use systeroid_core::cache::Cache; use systeroid_core::config::Config; use systeroid_core::sysctl::controller::Sysctl; @@ -42,11 +46,22 @@ pub fn run(args: Args, backend: B) -> Result<()> { }; config.tui.tick_rate = args.tick_rate; config.tui.save_path = args.save_path; + config.tui.log_file = args.log_file; config.tui.no_docs = args.no_docs; config.tui.color.fg_color = args.fg_color; config.tui.color.bg_color = args.bg_color; config.parse(args.config)?; let colors = Colors::new(&config.tui.color.bg_color, &config.tui.color.fg_color)?; + tui_logger::init_logger(if let Ok(log_level) = env::var("RUST_LOG") { + LevelFilter::from_str(&log_level)? + } else { + LevelFilter::Trace + })?; + tui_logger::set_default_level(LevelFilter::Trace); + if let Some(ref log_file) = config.tui.log_file { + tui_logger::set_log_file(log_file)?; + } + log::trace!(target: "config", "{:?}", config); let mut sysctl = Sysctl::init(config)?; if !sysctl.config.tui.no_docs { sysctl.update_docs_from_cache(&Cache::init()?)?; @@ -75,7 +90,12 @@ pub fn run(args: Args, backend: B) -> Result<()> { terminal.draw(|frame| ui::render(frame, &mut app, &colors))?; match event_handler.next()? { Event::KeyPress(key) => { - let command = Command::parse(key, app.is_input_mode()); + let mut command = Command::parse(key, app.is_input_mode()); + if app.show_logs { + command = LoggerCommand::parse(key) + .map(Command::LoggerEvent) + .unwrap_or(command); + } app.run_command(command)?; } #[cfg(not(test))] diff --git a/systeroid-tui/src/ui.rs b/systeroid-tui/src/ui.rs index f2fd0dc..c65f6eb 100644 --- a/systeroid-tui/src/ui.rs +++ b/systeroid-tui/src/ui.rs @@ -3,9 +3,11 @@ use crate::style::Colors; use crate::widgets::SelectableList; use tui::backend::Backend; use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use tui::style::{Color as TuiColor, Style}; use tui::text::{Span, Text}; use tui::widgets::{Block, BorderType, Borders, Cell, Clear, Paragraph, Row, Table, Wrap}; use tui::Frame; +use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget}; use unicode_width::UnicodeWidthStr; /// Renders the user interface. @@ -16,35 +18,48 @@ pub fn render(frame: &mut Frame<'_, B>, app: &mut App, colors: &Colo .and_then(|parameter| parameter.get_documentation()); let rect = frame.size(); let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints(if documentation.is_some() { - [Constraint::Percentage(50), Constraint::Percentage(50)] + .direction(Direction::Vertical) + .constraints(if app.show_logs { + [Constraint::Percentage(60), Constraint::Percentage(40)] } else { [Constraint::Percentage(100), Constraint::Min(0)] }) .split(rect); { let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints(if app.input.is_some() { - [Constraint::Min(rect.height - 3), Constraint::Min(3)] + .direction(Direction::Horizontal) + .constraints(if documentation.is_some() { + [Constraint::Percentage(50), Constraint::Percentage(50)] } else { [Constraint::Percentage(100), Constraint::Min(0)] }) .split(chunks[0]); - render_parameter_list(frame, chunks[0], app, colors); - if app.input.is_some() { - render_input_prompt(frame, chunks[1], rect.height - 2, app, colors); + { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints(if app.input.is_some() { + [Constraint::Min(chunks[0].height - 3), Constraint::Min(3)] + } else { + [Constraint::Percentage(100), Constraint::Min(0)] + }) + .split(chunks[0]); + render_parameter_list(frame, chunks[0], app, colors); + if app.input.is_some() { + render_input_prompt(frame, chunks[1], chunks[0].height + 1, app, colors); + } + } + if let Some(documentation) = documentation { + render_parameter_documentation( + frame, + chunks[1], + documentation, + &mut app.docs_scroll_amount, + colors, + ); } } - if let Some(documentation) = documentation { - render_parameter_documentation( - frame, - chunks[1], - documentation, - &mut app.docs_scroll_amount, - colors, - ); + if app.show_logs { + render_log_view(frame, chunks[1], app, colors); } if app.show_help { render_help_text(frame, rect, &mut app.key_bindings, colors); @@ -463,3 +478,24 @@ fn render_input_prompt( rect, ); } + +/// Renders the log view. +fn render_log_view(frame: &mut Frame<'_, B>, rect: Rect, app: &App, colors: &Colors) { + let logger_widget = TuiLoggerSmartWidget::default() + .style_trace(Style::default().fg(TuiColor::DarkGray)) + .style_debug(Style::default().fg(TuiColor::Blue)) + .style_warn(Style::default().fg(TuiColor::Yellow)) + .style_error(Style::default().fg(TuiColor::Red)) + .style_info(Style::default().fg(TuiColor::Green)) + .highlight_style(colors.get_fg_style()) + .border_style(colors.get_fg_style()) + .style(colors.get_bg_style()) + .output_separator(':') + .output_timestamp(Some("%H:%M:%S".to_string())) + .output_level(Some(TuiLoggerLevelOutput::Long)) + .output_target(true) + .output_file(true) + .output_line(true) + .state(&app.logger_state); + frame.render_widget(logger_widget, rect); +} diff --git a/systeroid-tui/tests/integration_test.rs b/systeroid-tui/tests/integration_test.rs index 7a5c49a..9a5da5d 100644 --- a/systeroid-tui/tests/integration_test.rs +++ b/systeroid-tui/tests/integration_test.rs @@ -307,10 +307,10 @@ fn test_render_tui() -> Result<()> { "│vm.stat_interval =││============= │", "│ ││The time interval │", "│ ││between which vm │", - "│ ││statistics are │", - "│ ││updated │", - "│ ││- │", - "│ 2/2 ││Parameter: │", + "│ 2/2 ││statistics are │", + "╰──────────────────╯│updated │", + "╭──────────────────╮│- │", + "│MSG: Refreshed! ││Parameter: │", "╰──────────────────╯╰──────────────────╯", ]), terminal.backend(), diff --git a/systeroid/Cargo.toml b/systeroid/Cargo.toml index c0ea1b2..50b70ab 100644 --- a/systeroid/Cargo.toml +++ b/systeroid/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/orhun/systeroid" keywords = ["linux", "kernel", "parameter", "sysctl"] categories = ["command-line-utilities"] edition = "2021" -default-run="systeroid" +default-run = "systeroid" rust-version = "1.64.0" [features] @@ -18,8 +18,10 @@ rust-version = "1.64.0" live-tests = [] [dependencies] -getopts = "0.2.21" parseit.workspace = true +log.workspace = true +env_logger = "0.10.0" +getopts = "0.2.21" [dependencies.systeroid-core] version = "0.3.2" # managed by release.sh diff --git a/systeroid/src/app.rs b/systeroid/src/app.rs index b71c9bc..d30b587 100644 --- a/systeroid/src/app.rs +++ b/systeroid/src/app.rs @@ -95,7 +95,7 @@ impl<'a, Output: Write> App<'a, Output> { } Err(e) => { if !pager.is_empty() { - eprintln!("pager error: `{e}`"); + log::error!("pager error: `{e}`"); } fallback_to_default = true; } @@ -130,7 +130,7 @@ impl<'a, Output: Write> App<'a, Output> { if parameters.len() == 1 { let param = parameters[0]; if DEPRECATED_PARAMS.contains(¶m.get_absolute_name().unwrap_or_default()) { - eprintln!( + log::error!( "{}: {} is deprecated, value not set", env!("CARGO_PKG_NAME"), parameter @@ -145,14 +145,14 @@ impl<'a, Output: Write> App<'a, Output> { param.update_value(&new_value, &config, self.output)?; } } else { - eprintln!( + log::error!( "{}: ambiguous parameter name: {}", env!("CARGO_PKG_NAME"), parameter ); } } else if write_mode { - eprintln!( + log::error!( "{}: {:?} must be in the format: name=value", env!("CARGO_PKG_NAME"), parameter @@ -171,13 +171,13 @@ impl<'a, Output: Write> App<'a, Output> { let lines = stdin.lock().lines(); for line in lines { if let Err(e) = self.process_parameter(line?, true, false) { - println!("{}: {}", env!("CARGO_PKG_NAME"), e); + log::info!("{}: {}", env!("CARGO_PKG_NAME"), e); } } return Ok(()); } if !path.exists() { - eprintln!( + log::error!( "{}: cannot open {:?}: No such file or directory", env!("CARGO_PKG_NAME"), path @@ -194,7 +194,7 @@ impl<'a, Output: Write> App<'a, Output> { if !parameter.starts_with('-') { process_result?; } else if let Err(e) = process_result { - eprintln!("{}: {}", env!("CARGO_PKG_NAME"), e); + log::error!("{}: {}", env!("CARGO_PKG_NAME"), e); } } Ok(()) @@ -208,7 +208,7 @@ impl<'a, Output: Write> App<'a, Output> { { if let Ok(glob_walker) = globwalk::glob(preload_path.to_string_lossy()) { for file in glob_walker.filter_map(|v| v.ok()) { - println!("* Applying {} ...", file.path().display()); + log::info!("* Applying {} ...", file.path().display()); self.preload_from_file(file.path().to_path_buf())?; } } diff --git a/systeroid/src/lib.rs b/systeroid/src/lib.rs index fd6f220..1caca13 100644 --- a/systeroid/src/lib.rs +++ b/systeroid/src/lib.rs @@ -23,13 +23,13 @@ pub fn run(args: Args, output: &mut Output) -> Result<()> { kernel_docs: args.kernel_docs, ..Default::default() }; - config.cli.verbose = args.verbose; config.cli.ignore_errors = args.ignore_errors; config.cli.quiet = args.quiet; config.cli.no_pager = args.no_pager; config.cli.display_type = args.display_type; config.cli.output_type = args.output_type; config.parse(args.config)?; + log::trace!("{:?}", config); let mut sysctl = Sysctl::init(config)?; if args.explain { sysctl.update_docs_from_cache(&Cache::init()?)?; diff --git a/systeroid/src/main.rs b/systeroid/src/main.rs index c6a44fc..56487f0 100644 --- a/systeroid/src/main.rs +++ b/systeroid/src/main.rs @@ -1,10 +1,19 @@ +use env_logger::Builder as LoggerBuilder; +use log::LevelFilter; use std::env; -use std::io; +use std::io::{self, Write}; use std::process::{self, Command}; use systeroid::args::Args; fn main() { if let Some(args) = Args::parse(env::args().collect()) { + let mut builder = LoggerBuilder::from_default_env(); + if args.verbose { + builder.filter(None, LevelFilter::Trace); + } + builder + .format(|buf, record| writeln!(buf, "{}", record.args())) + .init(); if args.show_tui { let bin = format!("{}-tui", env!("CARGO_PKG_NAME")); let mut command = Command::new(&bin); @@ -17,7 +26,7 @@ fn main() { match command.spawn().map(|mut child| child.wait()) { Ok(_) => process::exit(0), Err(e) => { - eprintln!("Cannot run `{bin}` ({e})"); + log::error!("Cannot run `{bin}` ({e})"); process::exit(1) } } @@ -26,7 +35,7 @@ fn main() { match systeroid::run(args, &mut stdout) { Ok(_) => process::exit(0), Err(e) => { - eprintln!("{e}"); + log::error!("{e}"); process::exit(1) } }