diff --git a/Cargo.lock b/Cargo.lock index 7085a01d..a6adb0e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -77,44 +77,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -128,7 +128,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -139,15 +139,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -163,27 +163,27 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.2.24" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chrono" @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", "clap_derive", @@ -211,9 +211,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "color-eyre" @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compact_str" @@ -294,13 +294,28 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crossterm" version = "0.28.1" @@ -361,6 +376,15 @@ dependencies = [ "syn", ] +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive-getters" version = "0.5.0" @@ -398,12 +422,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -418,9 +442,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -463,9 +487,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -527,9 +551,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indoc" @@ -539,9 +563,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "instability" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling", "indoc", @@ -589,9 +613,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" @@ -607,9 +631,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -632,9 +656,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" @@ -644,9 +668,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -699,6 +723,22 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -729,17 +769,23 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -747,15 +793,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -781,7 +827,10 @@ dependencies = [ "serde", "serde-xml-rs", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", + "tracing", + "tracing-appender", + "tracing-subscriber", "ureq", "which", ] @@ -816,9 +865,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -826,6 +875,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "predicates" version = "3.1.3" @@ -854,9 +909,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -893,9 +948,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -945,9 +1000,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" @@ -964,22 +1019,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "log", "once_cell", @@ -1010,9 +1065,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -1021,9 +1076,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1071,9 +1126,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -1119,9 +1174,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1134,9 +1189,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "static_assertions" @@ -1180,9 +1235,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1206,11 +1261,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -1226,9 +1281,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -1237,12 +1292,42 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1252,14 +1337,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -1275,15 +1384,42 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ + "nu-ansi-term", + "serde", + "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -1329,9 +1465,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.0.12" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" +checksum = "00432f493971db5d8e47a65aeb3b02f8226b9b11f1450ff86bb772776ebadd70" dependencies = [ "base64", "flate2", @@ -1342,14 +1478,14 @@ dependencies = [ "rustls-pki-types", "ureq-proto", "utf-8", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] name = "ureq-proto" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" +checksum = "c5b6cabebbecc4c45189ab06b52f956206cea7d8c8a20851c35a85cb169224cc" dependencies = [ "base64", "http", @@ -1377,9 +1513,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" @@ -1441,18 +1577,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.0", -] - -[[package]] -name = "webpki-roots" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -1464,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.0.7", + "rustix 1.0.8", "winsafe", ] @@ -1527,9 +1654,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -1555,7 +1682,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1564,7 +1691,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -1573,14 +1709,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1589,48 +1742,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winsafe" version = "0.0.19" @@ -1639,9 +1840,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xml-rs" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index d498a1f5..49ef11d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } serde-xml-rs = "0.8.1" serde_json = "1.0.140" +tracing = "0.1" +tracing-appender = "0.2.3" +tracing-subscriber = { version = "0.3.19", features = ["fmt", "std", "json"] } thiserror = "2.0.12" clap = { version = "4.5.40", features = ["derive"] } chrono = "0.4.41" diff --git a/src/app/cover_renderer.rs b/src/app/cover_renderer.rs index cdca61cf..dda19c7b 100644 --- a/src/app/cover_renderer.rs +++ b/src/app/cover_renderer.rs @@ -5,8 +5,7 @@ use std::{ io::Write, process::{Command, Stdio}, }; - -use crate::infrastructure::logging::Logger; +use tracing::{event, Level}; #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] pub enum CoverRenderer { @@ -68,7 +67,7 @@ fn bat_cover_renderer(patch: &str) -> color_eyre::Result { .stdout(Stdio::piped()) .spawn() .map_err(|e| { - Logger::error(format!("Failed to spawn bat for cover preview: {e}")); + event!(Level::ERROR, "Failed to spawn bat for cover preview: {}", e); e })?; diff --git a/src/app/mod.rs b/src/app/mod.rs index 23a64389..5afe930b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,11 +6,12 @@ pub mod screens; use ansi_to_tui::IntoText; use color_eyre::eyre::bail; use ratatui::text::Text; +use tracing::{event, Level}; use std::collections::{HashMap, HashSet}; use crate::{ - infrastructure::{garbage_collector, logging::Logger}, + infrastructure::monitoring::logging::garbage_collector::collect_garbage, log_on_error, lore::{ lore_api_client::BlockingLoreAPIClient, @@ -59,8 +60,7 @@ pub struct App { impl App { /// Creates a new instance of `App`. It dynamically loads configurations /// based on precedence (see [crate::app::Config::build]), app data - /// (available mailing lists, bookmarked patchsets, reviewed patchsets), and - /// initializes the Logger (see [crate::app::logging::Logger]) + /// (available mailing lists, bookmarked patchsets, reviewed patchsets) /// /// # Returns /// @@ -79,10 +79,8 @@ impl App { let lore_api_client = BlockingLoreAPIClient::default(); - // Initialize the logger before the app starts - Logger::init_log_file(&config)?; - Logger::info("patch-hub started"); - garbage_collector::collect_garbage(&config); + event!(Level::INFO, "patch-hub started"); + collect_garbage(&config); Ok(App { current_screen: CurrentScreen::MailingListSelection, @@ -216,7 +214,10 @@ impl App { { Ok(render) => render, Err(_) => { - Logger::error("Failed to render cover preview with external program"); + event!( + Level::ERROR, + "Failed to render cover preview with external program" + ); raw_cover.to_string() } }; @@ -225,7 +226,8 @@ impl App { match render_patch_preview(raw_patch, self.config.patch_renderer()) { Ok(render) => render, Err(_) => { - Logger::error( + event!( + Level::ERROR, "Failed to render patch preview with external program", ); raw_patch.to_string() @@ -404,30 +406,38 @@ impl App { let mut app_can_run = true; if which::which("b4").is_err() { - Logger::error("b4 is not installed, patchsets cannot be downloaded"); + event!( + Level::ERROR, + "b4 is not installed, patchsets cannot be downloaded" + ); app_can_run = false; } if which::which("git").is_err() { - Logger::warn("git is not installed, send-email won't work"); + event!(Level::WARN, "git is not installed, send-email won't work"); } match self.config.patch_renderer() { PatchRenderer::Bat => { if which::which("bat").is_err() { - Logger::warn("bat is not installed, patch rendering will fallback to default"); + event!( + Level::WARN, + "bat is not installed, patch rendering will fallback to default" + ); } } PatchRenderer::Delta => { if which::which("delta").is_err() { - Logger::warn( + event!( + Level::WARN, "delta is not installed, patch rendering will fallback to default", ); } } PatchRenderer::DiffSoFancy => { if which::which("diff-so-fancy").is_err() { - Logger::warn( + event!( + Level::WARN, "diff-so-fancy is not installed, patch rendering will fallback to default", ); } diff --git a/src/app/patch_renderer.rs b/src/app/patch_renderer.rs index ef625580..60278726 100644 --- a/src/app/patch_renderer.rs +++ b/src/app/patch_renderer.rs @@ -1,5 +1,6 @@ use color_eyre::eyre::eyre; use serde::{Deserialize, Serialize}; +use tracing::{event, Level}; use std::{ fmt::Display, @@ -7,8 +8,6 @@ use std::{ process::{Command, Stdio}, }; -use crate::infrastructure::logging::Logger; - #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] pub enum PatchRenderer { #[default] @@ -100,7 +99,7 @@ fn bat_patch_renderer(patch: &str) -> color_eyre::Result { .stdout(Stdio::piped()) .spawn() .map_err(|e| { - Logger::error(format!("Failed to spawn bat for patch preview: {e}")); + event!(Level::ERROR, "Failed to spawn bat for patch preview: {}", e); e })?; @@ -136,7 +135,11 @@ fn delta_patch_renderer(patch: &str) -> color_eyre::Result { .stdout(Stdio::piped()) .spawn() .map_err(|e| { - Logger::error(format!("Failed to spawn delta for patch preview: {e}")); + event!( + Level::ERROR, + "Failed to spawn delta for patch preview: {}", + e + ); e })?; @@ -166,9 +169,11 @@ fn diff_so_fancy_renderer(patch: &str) -> color_eyre::Result { .stdout(Stdio::piped()) .spawn() .map_err(|e| { - Logger::error(format!( - "Failed to spawn diff-so-fancy for patch preview: {e}" - )); + event!( + Level::ERROR, + "Failed to spawn diff-so-fancy for patch preview: {}", + e + ); e })?; diff --git a/src/handler/mod.rs b/src/handler/mod.rs index feaf6658..120132c3 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -5,7 +5,7 @@ mod latest; mod mail_list; use ratatui::{ - crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, + crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}, prelude::Backend, Terminal, }; @@ -116,7 +116,7 @@ where // need to refresh the UI independently of any event as doing so gravely // hinders the performance to below acceptable. // if event::poll(Duration::from_millis(16))? { - if let Event::Key(key) = event::read()? { + if let Event::Key(key) = ratatui::crossterm::event::read()? { if key.kind == KeyEventKind::Release { continue; } @@ -133,8 +133,8 @@ fn wait_key_press(ch: char, wait_time: Duration) -> color_eyre::Result { let start = Instant::now(); while Instant::now() - start < wait_time { - if event::poll(Duration::from_millis(16))? { - if let Event::Key(key) = event::read()? { + if ratatui::crossterm::event::poll(Duration::from_millis(16))? { + if let Event::Key(key) = ratatui::crossterm::event::read()? { if key.kind == KeyEventKind::Release { continue; } diff --git a/src/infrastructure/errors.rs b/src/infrastructure/errors.rs index a6335279..ad6c59a5 100644 --- a/src/infrastructure/errors.rs +++ b/src/infrastructure/errors.rs @@ -1,6 +1,6 @@ use std::panic; -use super::{logging::Logger, terminal::restore}; +use super::terminal::restore; /// This replaces the standard color_eyre panic and error hooks with hooks that /// restore the terminal before printing the panic or error. @@ -11,7 +11,6 @@ pub fn install_hooks() -> color_eyre::Result<()> { let panic_hook = panic_hook.into_panic_hook(); panic::set_hook(Box::new(move |panic_info| { restore().unwrap(); - Logger::flush(); panic_hook(panic_info); })); diff --git a/src/infrastructure/logging/mod.rs b/src/infrastructure/logging/mod.rs deleted file mode 100644 index e7ceb67e..00000000 --- a/src/infrastructure/logging/mod.rs +++ /dev/null @@ -1,301 +0,0 @@ -pub mod garbage_collector; - -use chrono::Local; - -use std::{ - fmt::Display, - fs::{self, File, OpenOptions}, - io::Write, -}; - -use crate::app::config::Config; - -const LATEST_LOG_FILENAME: &str = "latest.log"; - -static mut LOG_BUFFER: Logger = Logger { - log_file: None, - log_filepath: None, - latest_log_file: None, - latest_log_filepath: None, - logs_to_print: Vec::new(), - print_level: LogLevel::Warning, -}; - -/// Describes the log level of a message -/// -/// This is used to determine the severity of a log message so the logger handles it accordingly to the verbosity level. -/// -/// The levels severity are: `Info` < `Warning` < `Error` -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[allow(dead_code)] -pub enum LogLevel { - Info, - Warning, - Error, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct LogMessage { - level: LogLevel, - message: String, -} - -/// The Logger singleton that manages logging to [`stderr`] (log buffer) and a log file. -/// This is safe to use only in single-threaded scenarios. The messages are written to the log file immediatly, -/// but the messages to the `stderr` are written only after the TUI is closed, so they are kept in memory. -/// -/// The logger also has a log level that can be set to filter the messages that are written to the log file. -/// Only messages with a level equal or higher than the log level are written to the log file. -/// -/// The expected flow is: -/// - Initialize the log file with [`init_log_file`] -/// - Write to the log file with [`info`], [`warn`] or [`error`] -/// - Flush the log buffer to the stderr and close the log file with [`flush`] -/// -/// The log file is created in the logs_path defined in the [`Config`] struct -/// -/// [`Config`]: super::config::Config -/// [`init_log_file`]: Logger::init_log_file -/// [`info`]: Logger::info -/// [`warn`]: Logger::warn -/// [`error`]: Logger::error -/// [`flush`]: Logger::flush -/// [`stderr`]: std::io::stderr -#[derive(Debug)] -pub struct Logger { - log_file: Option, - log_filepath: Option, - latest_log_file: Option, - latest_log_filepath: Option, - logs_to_print: Vec, - print_level: LogLevel, // TODO: Add a log level configuration -} - -impl Logger { - /// Private method to get access to the Logger singleton - /// - /// This function makes use of unsafe code to access a static mut. Also, it's `inline` so won't have any overhead - /// - /// # Safety - /// - /// It's safe to use in single-threaded scenarios only - /// - /// # Examples - /// ```rust norun - /// // Get the logger singleton - /// Logger::init_log_file(&config); // Initialize the log file - /// Logger::info("This is an info log message"); // Write a message to the log file - /// ``` - #[inline] - fn get_logger() -> &'static mut Logger { - #[allow(static_mut_refs)] - unsafe { - &mut LOG_BUFFER - } - } - - /// Write the string `msg` to the logs to print buffer and the log file - /// - /// # Panics - /// - /// If the log file is not initialized - /// - /// # Examples - /// ```rust norun - /// // Make sure to initialize the log file before writing to it - /// Logger::init_log_file(&config); - /// // Get the logger singleton and write a message to the log file - /// Logger::get_logger().log(LogLevel::Info, "This is a log message"); - /// ``` - fn log(&mut self, level: LogLevel, message: M) { - let current_datetime = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); - let message = format!("[{current_datetime}] {message}"); - - let log = LogMessage { level, message }; - - let file = self.log_file - .as_mut() - .expect("Log file not initialized, make sure to call Logger::init_log_file() before writing to the log file"); - writeln!(file, "{log}").expect("Failed to write to log file"); - - let latest_log_file = self.latest_log_file - .as_mut() - .expect("Latest log file not initialized, make sure to call Logger::init_log_file() before writing to the log file"); - writeln!(latest_log_file, "{log}").expect("Failed to write to real time log file"); - - if self.print_level <= level { - // Only save logs to print w/ level equal or higher than the filter log level - self.logs_to_print.push(log); - } - } - - /// Write an info message to the log - /// - /// # Panics - /// - /// If the log file is not initialized - /// - /// # Safety - /// - /// It's safe to use in single-threaded scenarios only - /// - /// # Examples - /// - /// ```rust norun - /// - /// // Make sure to initialize the log file before writing to it - /// Logger::init_log_file(&config); - /// Logger::info("This is an info message"); // [INFO] [2024-09-11 14:59:00] This is an info message - /// ``` - #[inline] - #[allow(dead_code)] - pub fn info(msg: M) { - Logger::get_logger().log(LogLevel::Info, msg); - } - - /// Write a warn message to the log - /// - /// # Panics - /// - /// If the log file is not initialized - /// - /// # Safety - /// - /// It's safe to use in single-threaded scenarios only - /// - /// # Examples - /// - /// ```rust norun - /// - /// // Make sure to initialize the log file before writing to it - /// Logger::init_log_file(&config); - /// Logger::warn("This is a warning"); // [WARN] [2024-09-11 14:59:00] This is a warning - /// ``` - #[inline] - #[allow(dead_code)] - pub fn warn(msg: M) { - Logger::get_logger().log(LogLevel::Warning, msg); - } - - /// Write an error message to the log - /// - /// # Panics - /// - /// If the log file is not initialized - /// - /// # Safety - /// - /// It's safe to use in single-threaded scenarios only - /// - /// # Examples - /// - /// ```rust norun - /// - /// // Make sure to initialize the log file before writing to it - /// Logger::init_log_file(&config); - /// Logger::error("This is an error message"); // [ERROR] [2024-09-11 14:59:00] This is an error message - /// ``` - #[inline] - #[allow(dead_code)] - pub fn error(msg: M) { - Logger::get_logger().log(LogLevel::Error, msg); - } - - /// Flush the log buffer to stderr and closes the log file. - /// It's intended to be called only once when patch-hub is finishing. - /// - /// # Panics - /// - /// If called before the log file is initialized or if called twice - /// - /// # Examples - /// ```rust norun - /// // Make sure to initialize the log file before writing to it - /// Logger::init_log_file(&config); - /// - /// // Flush before finishing the application - /// Logger::flush(); - /// // Any further attempt to use the logger will panic, unless it's reinitialized - /// ``` - pub fn flush() { - let logger = Logger::get_logger(); - for entry in &logger.logs_to_print { - eprintln!("{entry}"); - } - - if let Some(f) = &logger.log_filepath { - eprintln!("Check the full log file: {f}"); - } - } - - /// Initialize the log file. - /// - /// This function must be called before any other operation with the logging system - /// - /// # Panics - /// - /// If it fails to create the log file - /// - /// # Examples - /// ```rust norun - /// // Once you get the config struct... - /// let config = Config::build(); - /// // ... initialize the log file - /// Logger::init_log_file(&config); - /// ``` - pub fn init_log_file(config: &Config) -> Result<(), std::io::Error> { - let logger = Logger::get_logger(); - - let logs_path = config.logs_path(); - fs::create_dir_all(logs_path)?; - - if logger.latest_log_file.is_none() { - let latest_log_filename = LATEST_LOG_FILENAME.to_string(); - let latest_log_filepath = format!("{logs_path}/{latest_log_filename}"); - - File::create(&latest_log_filepath)?; - - let log_file = OpenOptions::new() - .create(true) - .append(true) - .open(&latest_log_filepath)?; - - logger.latest_log_file = Some(log_file); - logger.latest_log_filepath = Some(latest_log_filepath); - } - - if logger.log_file.is_none() { - let log_filename = format!( - "patch-hub_{}.log", - chrono::Local::now().format("%Y%m%d-%H%M%S") - ); - let log_filepath = format!("{logs_path}/{log_filename}"); - - let log_file = OpenOptions::new() - .create(true) - .append(true) - .open(&log_filepath)?; - - logger.log_file = Some(log_file); - logger.log_filepath = Some(log_filepath); - } - - Ok(()) - } -} - -impl Display for LogMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}] {}", self.level, self.message) - } -} - -impl Display for LogLevel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LogLevel::Info => write!(f, "INFO"), - LogLevel::Warning => write!(f, "WARN"), - LogLevel::Error => write!(f, "ERROR"), - } - } -} diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs index f05edfcb..9365c568 100644 --- a/src/infrastructure/mod.rs +++ b/src/infrastructure/mod.rs @@ -1,5 +1,3 @@ pub mod errors; -pub mod logging; +pub mod monitoring; pub mod terminal; - -pub use logging::garbage_collector; diff --git a/src/infrastructure/logging/garbage_collector.rs b/src/infrastructure/monitoring/logging/garbage_collector.rs similarity index 85% rename from src/infrastructure/logging/garbage_collector.rs rename to src/infrastructure/monitoring/logging/garbage_collector.rs index e06892b4..c7709f85 100644 --- a/src/infrastructure/logging/garbage_collector.rs +++ b/src/infrastructure/monitoring/logging/garbage_collector.rs @@ -2,9 +2,9 @@ //! //! This module is responsible for cleaning up the log files. -use crate::app::config::Config; +use tracing::{event, Level}; -use super::Logger; +use crate::app::config::Config; /// Collects the garbage from the logs directory. /// Will check for log files `patch-hub_*.log` and remove them if they are older than the `max_log_age` in the config. @@ -16,7 +16,10 @@ pub fn collect_garbage(config: &Config) { let now = std::time::SystemTime::now(); let logs_path = config.logs_path(); let Ok(logs) = std::fs::read_dir(logs_path) else { - Logger::error("Failed to read the logs directory during garbage collection"); + event!( + Level::ERROR, + "Failed to read the logs directory during garbage collection" + ); return; }; @@ -41,10 +44,11 @@ pub fn collect_garbage(config: &Config) { let age = age.as_secs() / 60 / 60 / 24; if age as usize > config.max_log_age() && std::fs::remove_file(log.path()).is_err() { - Logger::warn(format!( + event!( + Level::WARN, "Failed to remove the log file: {}", log.path().to_string_lossy() - )); + ); } } } diff --git a/src/infrastructure/monitoring/logging/mod.rs b/src/infrastructure/monitoring/logging/mod.rs new file mode 100644 index 00000000..bd5fb044 --- /dev/null +++ b/src/infrastructure/monitoring/logging/mod.rs @@ -0,0 +1,80 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use multi_log_file_writer::{create_non_blocking_writer, get_fmt_layer, MultiLogFileWriter}; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::{reload::Handle, Layer, Registry}; + +pub mod garbage_collector; +pub mod multi_log_file_writer; + +const LATEST_LOG_FILENAME: &str = "latest.log"; +const TEMP_LOG_DIR: &str = "/tmp/temporary-patch-hub-logs"; + +pub struct InitLoggingLayerProduct { + pub logging_layer: Box + Send + Sync>, + pub multi_log_file_writer: MultiLogFileWriter, + pub guards_by_file_name: HashMap, + pub reload_handle: Handle + Send + Sync>, Registry>, +} +struct InitLoggingFileWritersProduct { + multi_log_file_writer: MultiLogFileWriter, + guards_by_file_name: HashMap, +} + +pub fn init_logging_layer() -> InitLoggingLayerProduct { + let InitLoggingFileWritersProduct { + multi_log_file_writer, + guards_by_file_name, + } = init_logging_file_writers(); + + let fmt_layer = get_fmt_layer(multi_log_file_writer.clone()); + + let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(fmt_layer); + + InitLoggingLayerProduct { + logging_layer: Box::new(reload_layer), + multi_log_file_writer, + guards_by_file_name, + reload_handle, + } +} + +fn init_logging_file_writers() -> InitLoggingFileWritersProduct { + let timestamp_log_file_name = format!( + "patch-hub_{}.log", + chrono::Local::now().format("%Y%m%d-%H%M%S") + ); + + // logging thread should be non-blocking so it does not interfere with the rest of the application + let (timestamp_log_writer, timestamp_log_writer_guard) = + create_non_blocking_writer(TEMP_LOG_DIR, timestamp_log_file_name.as_str()); + let (latest_log_writer, latest_log_writer_guard) = + create_non_blocking_writer(TEMP_LOG_DIR, LATEST_LOG_FILENAME); + + let writer_by_file_name = HashMap::from([ + ( + timestamp_log_file_name.clone(), + Arc::new(Mutex::new(timestamp_log_writer)), + ), + ( + LATEST_LOG_FILENAME.to_string(), + Arc::new(Mutex::new(latest_log_writer)), + ), + ]); + + let guards_by_file_name = HashMap::from([ + (timestamp_log_file_name, timestamp_log_writer_guard), + (LATEST_LOG_FILENAME.to_string(), latest_log_writer_guard), + ]); + + InitLoggingFileWritersProduct { + multi_log_file_writer: MultiLogFileWriter::new( + TEMP_LOG_DIR.to_string(), + writer_by_file_name, + ), + guards_by_file_name, + } +} diff --git a/src/infrastructure/monitoring/logging/multi_log_file_writer.rs b/src/infrastructure/monitoring/logging/multi_log_file_writer.rs new file mode 100644 index 00000000..0e9e65b7 --- /dev/null +++ b/src/infrastructure/monitoring/logging/multi_log_file_writer.rs @@ -0,0 +1,171 @@ +use std::{ + collections::HashMap, + fs::{File, OpenOptions}, + io::Write, + path::Path, + sync::{Arc, Mutex}, +}; + +use tracing::{event, Level}; +use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; +use tracing_subscriber::{reload::Handle, Layer, Registry}; + +use crate::app::config::Config; + +#[derive(Clone)] +pub struct MultiLogFileWriter { + directory: String, + writer_by_file_name: HashMap>>, +} + +impl MultiLogFileWriter { + pub fn new( + directory: String, + writer_by_file_name: HashMap>>, + ) -> Self { + Self { + writer_by_file_name, + directory, + } + } + + pub fn update_log_writer_with_config( + &mut self, + config: &Config, + current_guards_by_file_name: HashMap, + reload_handle: Handle + Send + Sync>, Registry>, + ) -> Vec { + let new_log_directory = config.logs_path(); + let guards = self.update_logging_dir( + new_log_directory, + current_guards_by_file_name, + reload_handle, + ); + + event!( + Level::INFO, + "Updated log file directory to: {}", + new_log_directory + ); + guards + } + + pub fn update_logging_dir( + &mut self, + new_directory: &str, + mut current_guards_by_file_name: HashMap, + reload_handle: Handle + Send + Sync>, Registry>, + ) -> Vec { + event!(Level::INFO, "Starting log directory update..."); + let mut new_guards = vec![]; + let old_directory = self.directory.clone(); + + for (file_name, current_writer) in self.writer_by_file_name.iter() { + let old_log_file_path = format!("{old_directory}/{file_name}"); + let new_log_file_path = format!("{new_directory}/{file_name}"); + + // first we drop current corresponding guard + if let Some(current_guard) = current_guards_by_file_name.remove(file_name) { + drop(current_guard); + // making sure we'll flush everything by the time we copy old file contents + std::thread::sleep(std::time::Duration::from_millis(50)); + } + + let (file_writer, file_writer_guard) = + create_non_blocking_writer(new_directory, file_name.as_str()); + + Self::copy_old_logs_to_new_path(old_log_file_path, new_log_file_path); + + // then we update the writers + let mut mutex_guard = current_writer.lock().expect("to get lock"); + *mutex_guard = file_writer; + + new_guards.push(file_writer_guard); + } + + // reloading layer + reload_handle + .reload(get_fmt_layer(self.clone())) + .expect("Failed reloading logging layer"); + + self.directory = new_directory.to_string(); + + new_guards + } + + fn copy_old_logs_to_new_path(old_log_file_path: String, new_log_file_path: String) { + let Ok(mut old_log_file_content) = File::open(&old_log_file_path) else { + event!( + Level::ERROR, + "Could not open old log file: {}", + old_log_file_path + ); + return; + }; + if let Some(parent_dir) = Path::new(&new_log_file_path).parent() { + // Create new log dir if it doesn't exist + std::fs::create_dir_all(parent_dir).expect("to create dir"); + } + let Ok(mut new_log_file_content) = OpenOptions::new() + .create(true) + .append(true) + .open(&new_log_file_path) + else { + event!( + Level::ERROR, + "Could not open new log file: {}", + new_log_file_path + ); + return; + }; + + let copy_result = std::io::copy(&mut old_log_file_content, &mut new_log_file_content); + if let Err(err) = copy_result { + event!(Level::ERROR, "Could not copy old file logs: {}", err); + }; + } +} + +impl Write for MultiLogFileWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + for writer in self.writer_by_file_name.values() { + writer + .lock() + .expect("to get lock") + .write_all(buf) + .expect("to write"); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + for writer in self.writer_by_file_name.values() { + writer + .lock() + .expect("to get lock") + .flush() + .expect("to flush"); + } + Ok(()) + } +} + +pub fn get_fmt_layer(writer: MultiLogFileWriter) -> Box + Send + Sync> { + tracing_subscriber::fmt::layer() + .with_writer(move || writer.clone()) + .with_file(true) + .with_line_number(true) + .with_timer(tracing_subscriber::fmt::time::SystemTime) + .json() + .boxed() +} + +pub fn create_non_blocking_writer(directory: &str, file_name: &str) -> (NonBlocking, WorkerGuard) { + // we have to truncate the file so if the file already exists we overwrite its content + // this is particularly important for the latest log desired behavior + let log_path = Path::new(directory).join(file_name); + let _ = File::create(&log_path); + + let file_appender = tracing_appender::rolling::never(directory, file_name); + tracing_appender::non_blocking(file_appender) +} diff --git a/src/infrastructure/monitoring/mod.rs b/src/infrastructure/monitoring/mod.rs new file mode 100644 index 00000000..ef8059fa --- /dev/null +++ b/src/infrastructure/monitoring/mod.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use logging::{ + init_logging_layer, multi_log_file_writer::MultiLogFileWriter, InitLoggingLayerProduct, +}; +use tracing::level_filters::LevelFilter; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::{ + filter::Targets, layer::SubscriberExt, reload::Handle, util::SubscriberInitExt, Layer, Registry, +}; + +pub mod logging; + +pub const PATH_HUB_TARGET: &str = "patch_hub"; + +pub struct InitMonitoringProduct { + pub multi_log_file_writer: MultiLogFileWriter, + pub logging_guards_by_file_name: HashMap, + pub logging_reload_handle: Handle + Send + Sync>, Registry>, +} + +pub fn init_monitoring() -> InitMonitoringProduct { + let InitLoggingLayerProduct { + logging_layer, + multi_log_file_writer, + guards_by_file_name: logging_guards_by_file_name, + reload_handle: logging_reload_handle, + } = init_logging_layer(); + + // the filter is separate from the logging layer because of a lib limitation: https://github.com/tokio-rs/tracing/issues/1629 + // otherwise we could not reload the logging layer after the logging dir is updated + let filter_patch_hub_target = Targets::new() + .with_target(PATH_HUB_TARGET, LevelFilter::TRACE) + .with_default(LevelFilter::OFF); + + // for future telemetry monitoring, we should just have to add another .with() in the registry + // with the new telemetry layer + tracing_subscriber::registry() + .with(logging_layer) + .with(filter_patch_hub_target) + .init(); + + InitMonitoringProduct { + multi_log_file_writer, + logging_guards_by_file_name, + logging_reload_handle, + } +} diff --git a/src/macros.rs b/src/macros.rs index 9664121a..dae1a5ee 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -51,7 +51,7 @@ macro_rules! loading_screen { #[macro_export] macro_rules! log_on_error { ($result:expr) => { - log_on_error!($crate::infrastructure::logging::LogLevel::Error, $result) + log_on_error!(tracing::Level::ERROR, $result) }; ($level:expr, $result:expr) => { match $result { @@ -59,17 +59,7 @@ macro_rules! log_on_error { Err(ref error) => { let error_message = format!("Error executing {:?}: {}", stringify!($result), &error); - match $level { - $crate::infrastructure::logging::LogLevel::Info => { - Logger::info(error_message); - } - $crate::infrastructure::logging::LogLevel::Warning => { - Logger::warn(error_message); - } - $crate::infrastructure::logging::LogLevel::Error => { - Logger::error(error_message); - } - } + tracing::event!($level, error_message); $result } } diff --git a/src/main.rs b/src/main.rs index 1db14403..9b6a0a4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,12 +12,21 @@ use cli::Cli; use color_eyre::eyre::bail; use handler::run_app; use infrastructure::{ - logging::Logger, + monitoring::{init_monitoring, InitMonitoringProduct}, terminal::{init, restore}, }; use std::ops::ControlFlow; +use tracing::{event, Level}; fn main() -> color_eyre::Result<()> { + // file writer guards should be propagated to main() so the logging thread lives enough + let InitMonitoringProduct { + logging_guards_by_file_name, + mut multi_log_file_writer, + logging_reload_handle, + .. + } = init_monitoring(); + let args = Cli::parse(); infrastructure::errors::install_hooks()?; @@ -26,6 +35,13 @@ fn main() -> color_eyre::Result<()> { let config = Config::build(); config.create_dirs(); + // with the config we can update log directory + let _guards = multi_log_file_writer.update_log_writer_with_config( + &config, + logging_guards_by_file_name, + logging_reload_handle, + ); + match args.resolve(terminal, &config) { ControlFlow::Break(b) => return b, ControlFlow::Continue(t) => terminal = t, @@ -33,15 +49,17 @@ fn main() -> color_eyre::Result<()> { let app = App::new(config)?; if !app.check_external_deps() { - Logger::error("patch-hub cannot be executed because some dependencies are missing"); + event!( + Level::WARN, + "patch-hub cannot be executed because some dependencies are missing" + ); bail!("patch-hub cannot be executed because some dependencies are missing, check logs for more information"); } run_app(terminal, app)?; restore()?; - Logger::info("patch-hub finished"); - Logger::flush(); + event!(Level::INFO, "patch-hub finished"); Ok(()) } diff --git a/src/ui/edit_config.rs b/src/ui/edit_config.rs index 3dbb2867..208920c8 100644 --- a/src/ui/edit_config.rs +++ b/src/ui/edit_config.rs @@ -5,8 +5,9 @@ use ratatui::{ widgets::{Block, Borders, Paragraph}, Frame, }; +use tracing::{event, Level}; -use crate::{app::App, infrastructure::logging::Logger}; +use crate::app::App; pub fn render_main(f: &mut Frame, app: &App, chunk: Rect) { let edit_config = app.edit_config.as_ref().unwrap(); @@ -30,7 +31,7 @@ pub fn render_main(f: &mut Frame, app: &App, chunk: Rect) { let (config, value) = match edit_config.config(i) { Some((cfg, val)) => (cfg, val), None => { - Logger::error(format!("Invalid configuration index: {i}")); + event!(Level::ERROR, "Invalid configuration index: {}", i); return; } };