From 6a6a5e77b86bcba0f7c30167c78d423f01adbc6e Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sun, 13 Apr 2025 13:38:08 -0300 Subject: [PATCH 1/7] feat: introduce and create a proof of concept for the tracing lib This commit introduces tracing, tracing_subscriber and tracing_appender libs. The objective is replace the current logging in-house module, for a more extensible architecture which supports the same features as the current logging method, but which is simpler to build, maintain, and well known in the community. Signed-off-by: Lorenzo Bertin Salvador --- Cargo.lock | 471 ++++++++++++++++------- Cargo.toml | 3 + src/infrastructure/mod.rs | 1 + src/infrastructure/monitoring/logging.rs | 104 +++++ src/infrastructure/monitoring/mod.rs | 22 ++ src/main.rs | 10 + 6 files changed, 476 insertions(+), 135 deletions(-) create mode 100644 src/infrastructure/monitoring/logging.rs create mode 100644 src/infrastructure/monitoring/mod.rs 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/infrastructure/mod.rs b/src/infrastructure/mod.rs index f05edfcb..a47be8cd 100644 --- a/src/infrastructure/mod.rs +++ b/src/infrastructure/mod.rs @@ -1,5 +1,6 @@ pub mod errors; pub mod logging; +pub mod monitoring; pub mod terminal; pub use logging::garbage_collector; diff --git a/src/infrastructure/monitoring/logging.rs b/src/infrastructure/monitoring/logging.rs new file mode 100644 index 00000000..bc766326 --- /dev/null +++ b/src/infrastructure/monitoring/logging.rs @@ -0,0 +1,104 @@ +use std::{ + fs::File, + io::Write, + path::Path, + sync::{Arc, Mutex}, +}; + +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::{Layer, Registry}; + +const PATH_HUB_TARGET: &str = "patch_hub"; +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 file_writer_guards: Vec, +} +struct InitLoggingFileWritersProduct { + multi_file_writer: MultiFileWriter, + file_writer_guards: Vec, +} + +#[derive(Clone)] +pub struct MultiFileWriter { + writers: Vec>>, +} + +pub fn init_logging_layer() -> InitLoggingLayerProduct { + let InitLoggingFileWritersProduct { + multi_file_writer, + file_writer_guards, + } = init_logging_file_writers(); + + let filter_patch_hub_target = + tracing_subscriber::filter::filter_fn(|metadata| metadata.target() == PATH_HUB_TARGET); + + let logging_layer = Box::new( + tracing_subscriber::fmt::layer() + .with_writer(move || multi_file_writer.clone()) + .with_file(true) + .with_line_number(true) + .with_timer(tracing_subscriber::fmt::time::SystemTime) + .json() + .with_filter(filter_patch_hub_target), + ); + + InitLoggingLayerProduct { + logging_layer, + file_writer_guards, + } +} + +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") + ); + + // we have to truncate the file so if the file already exists we overwrite its content + let latest_log_path = Path::new(TEMP_LOG_DIR).join(LATEST_LOG_FILENAME); + let _ = File::create(&latest_log_path); + + let timestamp_log_file_appender = + tracing_appender::rolling::never(TEMP_LOG_DIR, timestamp_log_file_name); + let latest_log_file_appender = + tracing_appender::rolling::never(TEMP_LOG_DIR, LATEST_LOG_FILENAME); + + // 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) = + tracing_appender::non_blocking(timestamp_log_file_appender); + let (latest_log_writer, latest_log_writer_guard) = + tracing_appender::non_blocking(latest_log_file_appender); + + InitLoggingFileWritersProduct { + multi_file_writer: MultiFileWriter::new(vec![ + Arc::new(Mutex::new(timestamp_log_writer)), + Arc::new(Mutex::new(latest_log_writer)), + ]), + file_writer_guards: vec![timestamp_log_writer_guard, latest_log_writer_guard], + } +} + +impl MultiFileWriter { + pub fn new(writers: Vec>>) -> Self { + Self { writers } + } +} + +impl Write for MultiFileWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + for writer in &self.writers { + let _ = writer.lock().expect("to get lock").write_all(buf); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + for writer in &self.writers { + writer.lock().expect("to get lock").flush()?; + } + Ok(()) + } +} diff --git a/src/infrastructure/monitoring/mod.rs b/src/infrastructure/monitoring/mod.rs new file mode 100644 index 00000000..49805d9c --- /dev/null +++ b/src/infrastructure/monitoring/mod.rs @@ -0,0 +1,22 @@ +use logging::{init_logging_layer, InitLoggingLayerProduct}; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +pub mod logging; + +pub struct InitMonitoringProduct { + pub file_writer_guards: Vec, +} + +pub fn init_monitoring() -> InitMonitoringProduct { + let InitLoggingLayerProduct { + logging_layer, + file_writer_guards, + } = init_logging_layer(); + + // 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).init(); + + InitMonitoringProduct { file_writer_guards } +} diff --git a/src/main.rs b/src/main.rs index 1db14403..58615998 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,11 +13,18 @@ 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 { + file_writer_guards: _file_writer_guards, + } = init_monitoring(); + let args = Cli::parse(); infrastructure::errors::install_hooks()?; @@ -41,6 +48,9 @@ fn main() -> color_eyre::Result<()> { restore()?; Logger::info("patch-hub finished"); + // event! usage example as an alternative for Logger module + event!(Level::INFO, "patch-hub finished"); + Logger::flush(); Ok(()) From 019c3663c73ddb3cf68c649a4bdeb340d42afcdb Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sun, 13 Apr 2025 18:31:59 -0300 Subject: [PATCH 2/7] feat: use config log path and deal with logs before its initialization This commit changes the new logging logic so logs are stored in the path defined at patch-hub's config. Since logging should encompass the whole application lifecycle, we have to correctly deal with logs produced before/during Config initialization. To solve this firstly we save the logs to a temporary path, and then, after Config intitialization we move the existing logs to the path defined at the Config and reload the logging layer so new logs are stored in the updated path. Signed-off-by: Lorenzo Bertin Salvador --- src/infrastructure/monitoring/logging.rs | 105 ++++------- .../logging/multi_log_file_writer.rs | 171 ++++++++++++++++++ src/infrastructure/monitoring/mod.rs | 38 +++- src/main.rs | 12 +- .../logging/multi_log_file_writer.rs | 171 ++++++++++++++++++ 5 files changed, 425 insertions(+), 72 deletions(-) create mode 100644 src/infrastructure/monitoring/logging/multi_log_file_writer.rs create mode 100644 src/monitoring/logging/multi_log_file_writer.rs diff --git a/src/infrastructure/monitoring/logging.rs b/src/infrastructure/monitoring/logging.rs index bc766326..23ee01a5 100644 --- a/src/infrastructure/monitoring/logging.rs +++ b/src/infrastructure/monitoring/logging.rs @@ -1,104 +1,79 @@ use std::{ - fs::File, - io::Write, - path::Path, + 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::{Layer, Registry}; +use tracing_subscriber::{reload::Handle, Layer, Registry}; -const PATH_HUB_TARGET: &str = "patch_hub"; -const LATEST_LOG_FILENAME: &str = "latest.log"; +pub mod multi_log_file_writer; + +const LATEST_LOG_FILENAME: &str = "latest.tracing.log"; const TEMP_LOG_DIR: &str = "/tmp/temporary-patch-hub-logs"; pub struct InitLoggingLayerProduct { pub logging_layer: Box + Send + Sync>, - pub file_writer_guards: Vec, + pub multi_log_file_writer: MultiLogFileWriter, + pub guards_by_file_name: HashMap, + pub reload_handle: Handle + Send + Sync>, Registry>, } struct InitLoggingFileWritersProduct { - multi_file_writer: MultiFileWriter, - file_writer_guards: Vec, -} - -#[derive(Clone)] -pub struct MultiFileWriter { - writers: Vec>>, + multi_log_file_writer: MultiLogFileWriter, + guards_by_file_name: HashMap, } pub fn init_logging_layer() -> InitLoggingLayerProduct { let InitLoggingFileWritersProduct { - multi_file_writer, - file_writer_guards, + multi_log_file_writer, + guards_by_file_name, } = init_logging_file_writers(); - let filter_patch_hub_target = - tracing_subscriber::filter::filter_fn(|metadata| metadata.target() == PATH_HUB_TARGET); + let fmt_layer = get_fmt_layer(multi_log_file_writer.clone()); - let logging_layer = Box::new( - tracing_subscriber::fmt::layer() - .with_writer(move || multi_file_writer.clone()) - .with_file(true) - .with_line_number(true) - .with_timer(tracing_subscriber::fmt::time::SystemTime) - .json() - .with_filter(filter_patch_hub_target), - ); + let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(fmt_layer); InitLoggingLayerProduct { - logging_layer, - file_writer_guards, + 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", + "patch-hub_{}.tracing.log", chrono::Local::now().format("%Y%m%d-%H%M%S") ); - // we have to truncate the file so if the file already exists we overwrite its content - let latest_log_path = Path::new(TEMP_LOG_DIR).join(LATEST_LOG_FILENAME); - let _ = File::create(&latest_log_path); - - let timestamp_log_file_appender = - tracing_appender::rolling::never(TEMP_LOG_DIR, timestamp_log_file_name); - let latest_log_file_appender = - tracing_appender::rolling::never(TEMP_LOG_DIR, LATEST_LOG_FILENAME); - // 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) = - tracing_appender::non_blocking(timestamp_log_file_appender); + create_non_blocking_writer(TEMP_LOG_DIR, timestamp_log_file_name.as_str()); let (latest_log_writer, latest_log_writer_guard) = - tracing_appender::non_blocking(latest_log_file_appender); + create_non_blocking_writer(TEMP_LOG_DIR, LATEST_LOG_FILENAME); - InitLoggingFileWritersProduct { - multi_file_writer: MultiFileWriter::new(vec![ + 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)), - ]), - file_writer_guards: vec![timestamp_log_writer_guard, latest_log_writer_guard], - } -} + ), + ]); -impl MultiFileWriter { - pub fn new(writers: Vec>>) -> Self { - Self { writers } - } -} + let guards_by_file_name = HashMap::from([ + (timestamp_log_file_name, timestamp_log_writer_guard), + (LATEST_LOG_FILENAME.to_string(), latest_log_writer_guard), + ]); -impl Write for MultiFileWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - for writer in &self.writers { - let _ = writer.lock().expect("to get lock").write_all(buf); - } - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - for writer in &self.writers { - writer.lock().expect("to get lock").flush()?; - } - Ok(()) + 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 index 49805d9c..ef8059fa 100644 --- a/src/infrastructure/monitoring/mod.rs +++ b/src/infrastructure/monitoring/mod.rs @@ -1,22 +1,48 @@ -use logging::{init_logging_layer, InitLoggingLayerProduct}; +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::{layer::SubscriberExt, util::SubscriberInitExt}; +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 file_writer_guards: Vec, + 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, - file_writer_guards, + 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).init(); + tracing_subscriber::registry() + .with(logging_layer) + .with(filter_patch_hub_target) + .init(); - InitMonitoringProduct { file_writer_guards } + InitMonitoringProduct { + multi_log_file_writer, + logging_guards_by_file_name, + logging_reload_handle, + } } diff --git a/src/main.rs b/src/main.rs index 58615998..091de3ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,10 @@ 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 { - file_writer_guards: _file_writer_guards, + logging_guards_by_file_name, + mut multi_log_file_writer, + logging_reload_handle, + .. } = init_monitoring(); let args = Cli::parse(); @@ -33,6 +36,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, diff --git a/src/monitoring/logging/multi_log_file_writer.rs b/src/monitoring/logging/multi_log_file_writer.rs new file mode 100644 index 00000000..a65255f1 --- /dev/null +++ b/src/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) +} From af1bf29dfffa8a595b510b1898ce87c78c6dceaf Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 19 Apr 2025 11:29:30 -0300 Subject: [PATCH 3/7] THIS COMMIT SHOULD BE DROPPED This commit is just a way of testing the log behavior regarding logs created before and after config initialization Signed-off-by: Lorenzo Bertin Salvador --- src/infrastructure/monitoring/logging.rs | 2 +- src/main.rs | 14 ++++++++++++++ src/monitoring/logging/multi_log_file_writer.rs | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/infrastructure/monitoring/logging.rs b/src/infrastructure/monitoring/logging.rs index 23ee01a5..2fccb584 100644 --- a/src/infrastructure/monitoring/logging.rs +++ b/src/infrastructure/monitoring/logging.rs @@ -10,7 +10,7 @@ use tracing_subscriber::{reload::Handle, Layer, Registry}; pub mod multi_log_file_writer; const LATEST_LOG_FILENAME: &str = "latest.tracing.log"; -const TEMP_LOG_DIR: &str = "/tmp/temporary-patch-hub-logs"; +const TEMP_LOG_DIR: &str = "./temporary-logs-test/temporary-patch-hub-logs/"; pub struct InitLoggingLayerProduct { pub logging_layer: Box + Send + Sync>, diff --git a/src/main.rs b/src/main.rs index 091de3ac..61c22dab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,11 @@ use std::ops::ControlFlow; use tracing::{event, Level}; fn main() -> color_eyre::Result<()> { + event!( + Level::INFO, + "log before logging initialization (should not appear anywhere)" + ); + // file writer guards should be propagated to main() so the logging thread lives enough let InitMonitoringProduct { logging_guards_by_file_name, @@ -28,6 +33,8 @@ fn main() -> color_eyre::Result<()> { .. } = init_monitoring(); + event!(Level::INFO, "log before config initialization"); + let args = Cli::parse(); infrastructure::errors::install_hooks()?; @@ -36,6 +43,11 @@ fn main() -> color_eyre::Result<()> { let config = Config::build(); config.create_dirs(); + event!( + Level::INFO, + "log after config initialization but before logging layer reload" + ); + // with the config we can update log directory let _guards = multi_log_file_writer.update_log_writer_with_config( &config, @@ -43,6 +55,8 @@ fn main() -> color_eyre::Result<()> { logging_reload_handle, ); + event!(Level::INFO, "log after logging layer reload"); + match args.resolve(terminal, &config) { ControlFlow::Break(b) => return b, ControlFlow::Continue(t) => terminal = t, diff --git a/src/monitoring/logging/multi_log_file_writer.rs b/src/monitoring/logging/multi_log_file_writer.rs index a65255f1..358a711b 100644 --- a/src/monitoring/logging/multi_log_file_writer.rs +++ b/src/monitoring/logging/multi_log_file_writer.rs @@ -31,11 +31,11 @@ impl MultiLogFileWriter { pub fn update_log_writer_with_config( &mut self, - config: &Config, + _config: &Config, current_guards_by_file_name: HashMap, reload_handle: Handle + Send + Sync>, Registry>, ) -> Vec { - let new_log_directory = config.logs_path(); + let new_log_directory = "./temporary-logs-test/config-dir-logs-test/"; let guards = self.update_logging_dir( new_log_directory, current_guards_by_file_name, From 25e9eef76d225a9b3e85878c8087e2eac01f1726 Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 19 Apr 2025 11:30:26 -0300 Subject: [PATCH 4/7] Revert "THIS COMMIT SHOULD BE DROPPED". THIS COMMIT ALSO SHOULD BE DROPPED This reverts commit 948f13d0be0770509fdcdc62bf89a70dede6db3f. --- src/infrastructure/monitoring/logging.rs | 2 +- src/main.rs | 14 -------------- src/monitoring/logging/multi_log_file_writer.rs | 4 ++-- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/infrastructure/monitoring/logging.rs b/src/infrastructure/monitoring/logging.rs index 2fccb584..23ee01a5 100644 --- a/src/infrastructure/monitoring/logging.rs +++ b/src/infrastructure/monitoring/logging.rs @@ -10,7 +10,7 @@ use tracing_subscriber::{reload::Handle, Layer, Registry}; pub mod multi_log_file_writer; const LATEST_LOG_FILENAME: &str = "latest.tracing.log"; -const TEMP_LOG_DIR: &str = "./temporary-logs-test/temporary-patch-hub-logs/"; +const TEMP_LOG_DIR: &str = "/tmp/temporary-patch-hub-logs"; pub struct InitLoggingLayerProduct { pub logging_layer: Box + Send + Sync>, diff --git a/src/main.rs b/src/main.rs index 61c22dab..091de3ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,11 +20,6 @@ use std::ops::ControlFlow; use tracing::{event, Level}; fn main() -> color_eyre::Result<()> { - event!( - Level::INFO, - "log before logging initialization (should not appear anywhere)" - ); - // file writer guards should be propagated to main() so the logging thread lives enough let InitMonitoringProduct { logging_guards_by_file_name, @@ -33,8 +28,6 @@ fn main() -> color_eyre::Result<()> { .. } = init_monitoring(); - event!(Level::INFO, "log before config initialization"); - let args = Cli::parse(); infrastructure::errors::install_hooks()?; @@ -43,11 +36,6 @@ fn main() -> color_eyre::Result<()> { let config = Config::build(); config.create_dirs(); - event!( - Level::INFO, - "log after config initialization but before logging layer reload" - ); - // with the config we can update log directory let _guards = multi_log_file_writer.update_log_writer_with_config( &config, @@ -55,8 +43,6 @@ fn main() -> color_eyre::Result<()> { logging_reload_handle, ); - event!(Level::INFO, "log after logging layer reload"); - match args.resolve(terminal, &config) { ControlFlow::Break(b) => return b, ControlFlow::Continue(t) => terminal = t, diff --git a/src/monitoring/logging/multi_log_file_writer.rs b/src/monitoring/logging/multi_log_file_writer.rs index 358a711b..a65255f1 100644 --- a/src/monitoring/logging/multi_log_file_writer.rs +++ b/src/monitoring/logging/multi_log_file_writer.rs @@ -31,11 +31,11 @@ impl MultiLogFileWriter { pub fn update_log_writer_with_config( &mut self, - _config: &Config, + config: &Config, current_guards_by_file_name: HashMap, reload_handle: Handle + Send + Sync>, Registry>, ) -> Vec { - let new_log_directory = "./temporary-logs-test/config-dir-logs-test/"; + let new_log_directory = config.logs_path(); let guards = self.update_logging_dir( new_log_directory, current_guards_by_file_name, From f8ba52d75e7a658f02c92c321cdac976bfe28e0e Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 19 Apr 2025 11:56:43 -0300 Subject: [PATCH 5/7] refactor: replace Logger occurrences by tracing::event! This commit finishes making patch-hub new log logic equal to the previous one (with the Logger module). The log file names are the same and every log is produced by tracing::event! macro. Signed-off-by: Lorenzo Bertin Salvador --- src/app/cover_renderer.rs | 5 +- src/app/mod.rs | 30 ++- src/app/patch_renderer.rs | 19 +- src/handler/mod.rs | 8 +- .../logging/garbage_collector.rs | 14 +- .../monitoring/{logging.rs => logging/mod.rs} | 4 +- src/macros.rs | 14 +- src/main.rs | 7 +- .../logging/multi_log_file_writer.rs | 171 ------------------ src/ui/edit_config.rs | 5 +- 10 files changed, 60 insertions(+), 217 deletions(-) rename src/infrastructure/monitoring/{logging.rs => logging/mod.rs} (96%) delete mode 100644 src/monitoring/logging/multi_log_file_writer.rs 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..1b32d873 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,6 +6,7 @@ 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}; @@ -81,7 +82,8 @@ impl App { // Initialize the logger before the app starts Logger::init_log_file(&config)?; - Logger::info("patch-hub started"); + + event!(Level::INFO, "patch-hub started"); garbage_collector::collect_garbage(&config); Ok(App { @@ -216,7 +218,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 +230,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 +410,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/logging/garbage_collector.rs b/src/infrastructure/logging/garbage_collector.rs index e06892b4..c7709f85 100644 --- a/src/infrastructure/logging/garbage_collector.rs +++ b/src/infrastructure/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.rs b/src/infrastructure/monitoring/logging/mod.rs similarity index 96% rename from src/infrastructure/monitoring/logging.rs rename to src/infrastructure/monitoring/logging/mod.rs index 23ee01a5..444429c2 100644 --- a/src/infrastructure/monitoring/logging.rs +++ b/src/infrastructure/monitoring/logging/mod.rs @@ -9,7 +9,7 @@ use tracing_subscriber::{reload::Handle, Layer, Registry}; pub mod multi_log_file_writer; -const LATEST_LOG_FILENAME: &str = "latest.tracing.log"; +const LATEST_LOG_FILENAME: &str = "latest.log"; const TEMP_LOG_DIR: &str = "/tmp/temporary-patch-hub-logs"; pub struct InitLoggingLayerProduct { @@ -43,7 +43,7 @@ pub fn init_logging_layer() -> InitLoggingLayerProduct { fn init_logging_file_writers() -> InitLoggingFileWritersProduct { let timestamp_log_file_name = format!( - "patch-hub_{}.tracing.log", + "patch-hub_{}.log", chrono::Local::now().format("%Y%m%d-%H%M%S") ); 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 091de3ac..804cae17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,15 +50,16 @@ 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"); - // event! usage example as an alternative for Logger module event!(Level::INFO, "patch-hub finished"); Logger::flush(); diff --git a/src/monitoring/logging/multi_log_file_writer.rs b/src/monitoring/logging/multi_log_file_writer.rs deleted file mode 100644 index a65255f1..00000000 --- a/src/monitoring/logging/multi_log_file_writer.rs +++ /dev/null @@ -1,171 +0,0 @@ -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/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; } }; From 594771e54d3ce0a4518b1aafd90243b61db47bb4 Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 19 Apr 2025 12:25:44 -0300 Subject: [PATCH 6/7] refactor: move garbage_collector and log_on_error location The garbage_collector function continues to work with the new logging methods since it just depends on log path and file names which didn't change. Temporary logs are on /tmp which is already a temporary directory, so garbage_collector doesn't need to deal with them. The log_on_error macro was already adapted to deal with tracing::Level log level. Signed-off-by: Lorenzo Bertin Salvador --- src/app/mod.rs | 4 ++-- src/infrastructure/logging/mod.rs | 2 -- src/infrastructure/mod.rs | 2 -- .../{ => monitoring}/logging/garbage_collector.rs | 0 src/infrastructure/monitoring/logging/mod.rs | 1 + 5 files changed, 3 insertions(+), 6 deletions(-) rename src/infrastructure/{ => monitoring}/logging/garbage_collector.rs (100%) diff --git a/src/app/mod.rs b/src/app/mod.rs index 1b32d873..f04a12f5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -11,7 +11,7 @@ use tracing::{event, Level}; use std::collections::{HashMap, HashSet}; use crate::{ - infrastructure::{garbage_collector, logging::Logger}, + infrastructure::{logging::Logger, monitoring::logging::garbage_collector::collect_garbage}, log_on_error, lore::{ lore_api_client::BlockingLoreAPIClient, @@ -84,7 +84,7 @@ impl App { Logger::init_log_file(&config)?; event!(Level::INFO, "patch-hub started"); - garbage_collector::collect_garbage(&config); + collect_garbage(&config); Ok(App { current_screen: CurrentScreen::MailingListSelection, diff --git a/src/infrastructure/logging/mod.rs b/src/infrastructure/logging/mod.rs index e7ceb67e..1a571cf7 100644 --- a/src/infrastructure/logging/mod.rs +++ b/src/infrastructure/logging/mod.rs @@ -1,5 +1,3 @@ -pub mod garbage_collector; - use chrono::Local; use std::{ diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs index a47be8cd..c48a066f 100644 --- a/src/infrastructure/mod.rs +++ b/src/infrastructure/mod.rs @@ -2,5 +2,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 100% rename from src/infrastructure/logging/garbage_collector.rs rename to src/infrastructure/monitoring/logging/garbage_collector.rs diff --git a/src/infrastructure/monitoring/logging/mod.rs b/src/infrastructure/monitoring/logging/mod.rs index 444429c2..bd5fb044 100644 --- a/src/infrastructure/monitoring/logging/mod.rs +++ b/src/infrastructure/monitoring/logging/mod.rs @@ -7,6 +7,7 @@ use multi_log_file_writer::{create_non_blocking_writer, get_fmt_layer, MultiLogF 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"; From 051d53bfbbdb897ea1ead47d6ec74a9358e63783 Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 19 Apr 2025 12:31:29 -0300 Subject: [PATCH 7/7] refactor: remove Logger module The module occurrences and usage was already replaced in previous commits, so it's possible to remove the whole module. Signed-off-by: Lorenzo Bertin Salvador --- src/app/mod.rs | 8 +- src/infrastructure/errors.rs | 3 +- src/infrastructure/logging/mod.rs | 299 ------------------------------ src/infrastructure/mod.rs | 1 - src/main.rs | 3 - 5 files changed, 3 insertions(+), 311 deletions(-) delete mode 100644 src/infrastructure/logging/mod.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index f04a12f5..5afe930b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -11,7 +11,7 @@ use tracing::{event, Level}; use std::collections::{HashMap, HashSet}; use crate::{ - infrastructure::{logging::Logger, monitoring::logging::garbage_collector::collect_garbage}, + infrastructure::monitoring::logging::garbage_collector::collect_garbage, log_on_error, lore::{ lore_api_client::BlockingLoreAPIClient, @@ -60,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 /// @@ -80,9 +79,6 @@ impl App { let lore_api_client = BlockingLoreAPIClient::default(); - // Initialize the logger before the app starts - Logger::init_log_file(&config)?; - event!(Level::INFO, "patch-hub started"); collect_garbage(&config); 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 1a571cf7..00000000 --- a/src/infrastructure/logging/mod.rs +++ /dev/null @@ -1,299 +0,0 @@ -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 c48a066f..9365c568 100644 --- a/src/infrastructure/mod.rs +++ b/src/infrastructure/mod.rs @@ -1,4 +1,3 @@ pub mod errors; -pub mod logging; pub mod monitoring; pub mod terminal; diff --git a/src/main.rs b/src/main.rs index 804cae17..9b6a0a4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,6 @@ use cli::Cli; use color_eyre::eyre::bail; use handler::run_app; use infrastructure::{ - logging::Logger, monitoring::{init_monitoring, InitMonitoringProduct}, terminal::{init, restore}, }; @@ -62,7 +61,5 @@ fn main() -> color_eyre::Result<()> { event!(Level::INFO, "patch-hub finished"); - Logger::flush(); - Ok(()) }