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)
}
}