diff --git a/.vscode/settings.json b/.vscode/settings.json index 87c1b66..728be30 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,9 +9,11 @@ "dotenv", "dotenvy", "graphiql", + "mainmatter", "minijinja", "nocapture", "oneshot", + "opentelemetry", "pemfile", "prost", "protobuf", diff --git a/Cargo.lock b/Cargo.lock index a06a9c9..83cd069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,7 +162,7 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "ahash 0.8.7", + "ahash 0.8.11", "base64 0.22.1", "bitflags 2.4.1", "brotli", @@ -436,7 +436,7 @@ dependencies = [ "actix-tls", "actix-utils", "actix-web-codegen", - "ahash 0.8.7", + "ahash 0.8.11", "bytes 1.6.0", "bytestring", "cfg-if 1.0.0", @@ -504,7 +504,7 @@ dependencies = [ "actix-utils", "actix-web", "actix-web-lab-derive", - "ahash 0.8.7", + "ahash 0.8.11", "arc-swap", "async-trait", "bytes 1.6.0", @@ -643,9 +643,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if 1.0.0", "const-random", @@ -1110,6 +1110,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + [[package]] name = "autocfg" version = "1.1.0" @@ -1571,6 +1577,51 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes 1.6.0", + "futures-util", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", + "itoa 1.0.9", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite 0.2.13", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes 1.6.0", + "futures-util", + "http 0.2.9", + "http-body 0.4.5", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "background-jobs" version = "1.0.0" @@ -1846,7 +1897,7 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce21468c1c9c154a85696bb25c20582511438edb6ad67f846ba1378ffdd80222" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "base64 0.13.1", "bitvec", "hex", @@ -3508,6 +3559,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -3589,7 +3650,7 @@ dependencies = [ "no-std-compat", "nonzero_ext", "parking_lot 0.12.3", - "quanta", + "quanta 0.11.1", "rand 0.8.5", "smallvec", ] @@ -3736,7 +3797,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", ] [[package]] @@ -3745,7 +3806,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "allocator-api2", ] @@ -4031,7 +4092,7 @@ dependencies = [ "hyper 0.14.27", "log", "rustls 0.21.12", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio 1.37.0", "tokio-rustls 0.24.1", ] @@ -4053,6 +4114,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.27", + "pin-project-lite 0.2.13", + "tokio 1.37.0", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.4.3" @@ -4763,12 +4836,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -4806,6 +4894,45 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" +dependencies = [ + "ahash 0.8.11", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" +dependencies = [ + "base64 0.22.1", + "indexmap 2.1.0", + "metrics", + "metrics-util", + "quanta 0.12.3", + "thiserror", +] + +[[package]] +name = "metrics-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.2", + "metrics", + "num_cpus", + "quanta 0.12.3", + "sketches-ddsketch", +] + [[package]] name = "middleware-encrypted-payloads" version = "1.0.0" @@ -5074,6 +5201,12 @@ dependencies = [ "tokio-util 0.7.11", ] +[[package]] +name = "mutually_exclusive_features" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" + [[package]] name = "mysql" version = "1.0.0" @@ -5281,6 +5414,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi 0.3.9", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -5421,6 +5564,89 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite 0.2.13", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb" +dependencies = [ + "async-trait", + "futures-core", + "http 0.2.9", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio 1.37.0", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910" + +[[package]] +name = "opentelemetry_sdk" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "ordered-float", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio 1.37.0", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -5437,6 +5663,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" version = "3.5.0" @@ -5789,6 +6021,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "postgres-protocol" version = "0.6.6" @@ -5996,7 +6234,22 @@ dependencies = [ "libc", "mach2", "once_cell", - "raw-cpuid", + "raw-cpuid 10.7.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid 11.0.2", "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi 0.3.9", @@ -6124,6 +6377,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "raw-cpuid" +version = "11.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "redis" version = "0.24.0" @@ -6196,8 +6458,17 @@ checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -6208,7 +6479,7 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] [[package]] @@ -6217,6 +6488,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -6388,7 +6665,7 @@ version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "bitflags 2.4.1", "instant", "num-traits", @@ -6669,6 +6946,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -7167,6 +7457,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + [[package]] name = "slab" version = "0.4.9" @@ -7338,7 +7634,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "atoi", "byteorder", "bytes 1.6.0", @@ -8044,6 +8340,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite 0.2.13", + "tokio 1.37.0", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -8270,6 +8576,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes 1.6.0", + "h2 0.3.26", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "tokio 1.37.0", + "tokio-rustls 0.25.0", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -8278,9 +8615,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite 0.2.13", + "rand 0.8.5", + "slab", "tokio 1.37.0", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", @@ -8310,6 +8651,21 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-actix-web" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "opentelemetry", + "pin-project", + "tracing", + "tracing-opentelemetry", + "uuid 1.7.0", +] + [[package]] name = "tracing-attributes" version = "0.1.27" @@ -8321,6 +8677,24 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tracing-bunyan-formatter" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373" +dependencies = [ + "ahash 0.8.11", + "gethostname", + "log", + "serde", + "serde_json", + "time", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -8351,15 +8725,95 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[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-mainmatter-workshop" +version = "1.0.0" +dependencies = [ + "actix-web", + "actix-web-lab", + "dotenvy", + "metrics", + "metrics-exporter-prometheus", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "tonic", + "tracing", + "tracing-actix-web", + "tracing-bunyan-formatter", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9be14ba1bbe4ab79e9229f7f89fab8d120b865859f10527f31c033e599d2284" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log 0.2.0", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", "sharded-slab", + "smallvec", "thread_local", + "tracing", "tracing-core", + "tracing-log 0.1.4", + "tracing-serde", ] [[package]] @@ -8687,6 +9141,7 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ + "atomic", "getrandom 0.2.12", "serde", ] @@ -8910,6 +9365,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/Cargo.toml b/Cargo.toml index b2b219a..cb975ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,72 +1,73 @@ [workspace] resolver = "2" members = [ - "auth/casbin", - "auth/cookie-auth", - "auth/cookie-session", - "auth/redis-session", - "auth/simple-auth-server", - "background-jobs", - "basics/basics", - "basics/error-handling", - "basics/hello-world", - "basics/nested-routing", - "basics/state", - "basics/static-files", - "basics/todo", - "cors/backend", - "data-factory", - "databases/diesel", - "databases/mongodb", - "databases/mysql", - "databases/postgres", - "databases/redis", - # "databases/sqlite", - "docker", - "forms/form", - "forms/multipart-s3", - "forms/multipart", - "graphql/async-graphql", - "graphql/juniper-advanced", - "graphql/juniper", - "guards", - "http-proxy", - "https-tls/acme-letsencrypt", - "https-tls/awc-https", - "https-tls/cert-watch", - "https-tls/openssl", - "https-tls/rustls-client-cert", - "https-tls/rustls", - "json/json-decode-error", - "json/json-error", - "json/json-validation", - "json/json", - "json/jsonrpc", - "middleware/encrypted-payloads", - "middleware/http-to-https", - "middleware/rate-limit", - "middleware/request-extensions", - "middleware/various", - "protobuf", - "run-in-thread", - "server-sent-events", - "shutdown-server", - "templating/askama", - "templating/fluent", - "templating/handlebars", - "templating/minijinja", - "templating/sailfish", - "templating/tera", - "templating/tinytemplate", - "templating/yarte", - "unix-socket", - "websockets/autobahn", - "websockets/chat-actorless", - "websockets/chat-broker", - "websockets/chat-tcp", - "websockets/chat", - "websockets/echo-actorless", - "websockets/echo", + "auth/casbin", + "auth/cookie-auth", + "auth/cookie-session", + "auth/redis-session", + "auth/simple-auth-server", + "background-jobs", + "basics/basics", + "basics/error-handling", + "basics/hello-world", + "basics/nested-routing", + "basics/state", + "basics/static-files", + "basics/todo", + "cors/backend", + "data-factory", + "databases/diesel", + "databases/mongodb", + "databases/mysql", + "databases/postgres", + "databases/redis", + # "databases/sqlite", + "docker", + "forms/form", + "forms/multipart-s3", + "forms/multipart", + "graphql/async-graphql", + "graphql/juniper-advanced", + "graphql/juniper", + "guards", + "http-proxy", + "https-tls/acme-letsencrypt", + "https-tls/awc-https", + "https-tls/cert-watch", + "https-tls/openssl", + "https-tls/rustls-client-cert", + "https-tls/rustls", + "json/json-decode-error", + "json/json-error", + "json/json-validation", + "json/json", + "json/jsonrpc", + "middleware/encrypted-payloads", + "middleware/http-to-https", + "middleware/rate-limit", + "middleware/request-extensions", + "middleware/various", + "protobuf", + "run-in-thread", + "server-sent-events", + "shutdown-server", + "templating/askama", + "templating/fluent", + "templating/handlebars", + "templating/minijinja", + "templating/sailfish", + "templating/tera", + "templating/tinytemplate", + "templating/yarte", + "tracing/mainmatter-workshop", + "unix-socket", + "websockets/autobahn", + "websockets/chat-actorless", + "websockets/chat-broker", + "websockets/chat-tcp", + "websockets/chat", + "websockets/echo-actorless", + "websockets/echo", ] [workspace.package] @@ -99,9 +100,14 @@ chrono = { version = "0.4.30", features = ["serde"] } derive_more = "0.99.7" dotenvy = "0.15" env_logger = "0.11" -eyre = { version = "0.6", default-features = false, features = ["auto-install", "track-caller"] } +eyre = { version = "0.6", default-features = false, features = [ + "auto-install", + "track-caller", +] } color-eyre = "0.6" -futures-util = { version = "0.3.17", default-features = false, features = ["std"] } +futures-util = { version = "0.3.17", default-features = false, features = [ + "std", +] } log = "0.4" openssl = { version = "0.10.60", features = ["v110"] } rand = "0.8" @@ -112,3 +118,5 @@ serde_json = "1" tokio = { version = "1.24.2", features = ["sync", "io-util"] } tokio-util = "0.7.4" tokio-stream = "0.1.1" +tracing = "0.1.30" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/databases/redis/Cargo.toml b/databases/redis/Cargo.toml index 11aae16..3bf96d4 100644 --- a/databases/redis/Cargo.toml +++ b/databases/redis/Cargo.toml @@ -8,5 +8,8 @@ actix-web.workspace = true env_logger.workspace = true log.workspace = true -redis = { version = "0.24", default-features = false, features = ["tokio-comp", "connection-manager"] } +redis = { version = "0.24", default-features = false, features = [ + "tokio-comp", + "connection-manager", +] } serde.workspace = true diff --git a/templating/yarte/Cargo.toml b/templating/yarte/Cargo.toml index fe1520e..b99ea6e 100644 --- a/templating/yarte/Cargo.toml +++ b/templating/yarte/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [package.metadata.cargo-machete] ignored = [ - "yarte_helpers", # only build dep + "yarte_helpers", # only build dep ] [dependencies] diff --git a/tracing/mainmatter-workshop/.env.example b/tracing/mainmatter-workshop/.env.example new file mode 100644 index 0000000..8525a59 --- /dev/null +++ b/tracing/mainmatter-workshop/.env.example @@ -0,0 +1,2 @@ +RUST_LOG="info,mainmatter_workshop=trace" +HONEYCOMB_API_KEY="..." diff --git a/tracing/mainmatter-workshop/.gitignore b/tracing/mainmatter-workshop/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/tracing/mainmatter-workshop/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tracing/mainmatter-workshop/Cargo.toml b/tracing/mainmatter-workshop/Cargo.toml new file mode 100644 index 0000000..2cc9a2e --- /dev/null +++ b/tracing/mainmatter-workshop/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tracing-mainmatter-workshop" +version = "1.0.0" +publish.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +actix-web.workspace = true +actix-web-lab.workspace = true +dotenvy.workspace = true +metrics = "0.23" +metrics-exporter-prometheus = { version = "0.15", default-features = false } +opentelemetry = "0.22" +opentelemetry_sdk = { version = "0.22", features = ["rt-tokio-current-thread"] } +opentelemetry-otlp = { version = "0.15", features = ["tls-roots"] } +tonic = "0.11" +tracing-actix-web = { version = "0.7", features = ["opentelemetry_0_22", "uuid_v7"] } +tracing-opentelemetry = "0.23" +tracing-subscriber.workspace = true +tracing-bunyan-formatter = "0.3" +tracing.workspace = true diff --git a/tracing/mainmatter-workshop/README.md b/tracing/mainmatter-workshop/README.md new file mode 100644 index 0000000..aa4cadb --- /dev/null +++ b/tracing/mainmatter-workshop/README.md @@ -0,0 +1,23 @@ +# Telemetry Workshop Solution + +## Overview + +A solution to the capstone project at the end of [Mainmatter's telemetry workshop](https://github.com/mainmatter/rust-telemetry-workshop). + +As stated in the exercise brief, this example will: + +- Configure a `tracing` subscriber that exports data to both Honeycomb and stdout, in JSON format; +- Configure a suitable panic hook; +- Configure a `metric` recorder that exposes metric data at `/metrics`~~, using a different port than your API endpoints~~ (this example shows how to use the existing HTTP server); +- Add one or more middleware that: + - Create a top-level INFO span for each incoming request; + - Track the number of concurrent requests using a gauge; + - Track request duration using a histogram; + - Track the number of handled requests All metrics should include success/failure as a label. + +## Usage + +```console +$ cd tracing/mainmatter-workshop +$ cargo run +``` diff --git a/tracing/mainmatter-workshop/src/logging.rs b/tracing/mainmatter-workshop/src/logging.rs new file mode 100644 index 0000000..970279d --- /dev/null +++ b/tracing/mainmatter-workshop/src/logging.rs @@ -0,0 +1,57 @@ +use std::{io, time::Duration}; + +use opentelemetry::KeyValue; +use opentelemetry_otlp::WithExportConfig as _; +use opentelemetry_sdk::{runtime, trace::Tracer, Resource}; +use tonic::metadata::MetadataMap; +use tracing::level_filters::LevelFilter; +use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; +use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter}; + +pub(crate) fn init() { + let app_name = "actix-web-mainmatter-telemetry-workshop-capstone"; + + let tracer = opentelemetry_tracer(app_name); + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + + // we prefer the bunyan formatting layer in this example because it captures + // span enters and exits by default, making a good way to observe request + // info like duration when + let stdout_log = BunyanFormattingLayer::new(app_name.to_owned(), io::stdout); + + tracing_subscriber::registry() + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) + .with(telemetry) + .with(JsonStorageLayer) + .with(stdout_log) + .init(); +} + +fn opentelemetry_tracer(app_name: &str) -> Tracer { + let honeycomb_key = + std::env::var("HONEYCOMB_API_KEY").expect("`HONEYCOMB_API_KEY` should be set in your .env"); + + let mut metadata = MetadataMap::with_capacity(1); + metadata.insert("x-honeycomb-team", honeycomb_key.try_into().unwrap()); + + let trace_config = + opentelemetry_sdk::trace::Config::default().with_resource(Resource::new(vec![ + KeyValue::new("service.name", app_name.to_owned()), + ])); + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint("https://api.honeycomb.io/api/traces") + .with_timeout(Duration::from_secs(5)) + .with_metadata(metadata); + + opentelemetry_otlp::new_pipeline() + .tracing() + .with_trace_config(trace_config) + .with_exporter(exporter) + .install_batch(runtime::TokioCurrentThread) + .unwrap() +} diff --git a/tracing/mainmatter-workshop/src/main.rs b/tracing/mainmatter-workshop/src/main.rs new file mode 100644 index 0000000..ce99109 --- /dev/null +++ b/tracing/mainmatter-workshop/src/main.rs @@ -0,0 +1,32 @@ +use std::io; + +use actix_web::{App, HttpServer}; +use actix_web_lab::{extract::ThinData, middleware::from_fn}; +use tracing_actix_web::TracingLogger; + +mod logging; +mod metric_names; +mod middleware; +mod prometheus; +mod routes; + +#[actix_web::main] +async fn main() -> io::Result<()> { + dotenvy::dotenv().ok(); + logging::init(); + let handle = prometheus::init(); + + HttpServer::new(move || { + App::new() + .app_data(ThinData(handle.clone())) + .service(routes::hello) + .service(routes::sleep) + .service(routes::metrics) + .wrap(from_fn(middleware::request_telemetry)) + .wrap(TracingLogger::default()) + }) + .workers(2) + .bind(("127.0.0.1", 8080))? + .run() + .await +} diff --git a/tracing/mainmatter-workshop/src/metric_names.rs b/tracing/mainmatter-workshop/src/metric_names.rs new file mode 100644 index 0000000..3afc45a --- /dev/null +++ b/tracing/mainmatter-workshop/src/metric_names.rs @@ -0,0 +1,2 @@ +pub(crate) const HISTOGRAM_HTTP_REQUEST_DURATION: &str = "http_request_duration"; +pub(crate) const GAUGE_HTTP_CONCURRENT_REQUESTS: &str = "http_concurrent_requests"; diff --git a/tracing/mainmatter-workshop/src/middleware.rs b/tracing/mainmatter-workshop/src/middleware.rs new file mode 100644 index 0000000..bef2340 --- /dev/null +++ b/tracing/mainmatter-workshop/src/middleware.rs @@ -0,0 +1,40 @@ +use std::time::Instant; + +use actix_web::HttpMessage as _; +use actix_web::{ + body::MessageBody, + dev::{ServiceRequest, ServiceResponse}, + http::header::{HeaderName, HeaderValue}, +}; +use actix_web_lab::middleware::Next; +use tracing_actix_web::RequestId; + +use crate::metric_names::*; + +pub(crate) async fn request_telemetry( + req: ServiceRequest, + next: Next, +) -> actix_web::Result> { + let now = Instant::now(); + + metrics::gauge!(GAUGE_HTTP_CONCURRENT_REQUESTS).increment(1); + + let mut res = next.call(req).await?; + + let req_id = res.request().extensions().get::().copied(); + + if let Some(req_id) = req_id { + res.headers_mut().insert( + HeaderName::from_static("request-id"), + // this unwrap never fails, since UUIDs are valid ASCII strings + HeaderValue::from_str(&req_id.to_string()).unwrap(), + ); + }; + + let diff = now.elapsed(); + metrics::histogram!(HISTOGRAM_HTTP_REQUEST_DURATION).record(diff); + + metrics::gauge!(GAUGE_HTTP_CONCURRENT_REQUESTS).decrement(1); + + Ok(res) +} diff --git a/tracing/mainmatter-workshop/src/prometheus.rs b/tracing/mainmatter-workshop/src/prometheus.rs new file mode 100644 index 0000000..5b8e58e --- /dev/null +++ b/tracing/mainmatter-workshop/src/prometheus.rs @@ -0,0 +1,28 @@ +use std::array; + +use metrics::Unit; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; + +use crate::metric_names::*; + +pub(crate) fn init() -> PrometheusHandle { + metrics::describe_histogram!( + HISTOGRAM_HTTP_REQUEST_DURATION, + Unit::Seconds, + "Duration (in seconds) a request took to be processed" + ); + + PrometheusBuilder::new() + .set_buckets_for_metric( + metrics_exporter_prometheus::Matcher::Full(HISTOGRAM_HTTP_REQUEST_DURATION.to_owned()), + &exp_buckets::<28>(0.001), // values from ~0.3ms -> ~33s + ) + .unwrap() + .install_recorder() + .unwrap() +} + +fn exp_buckets(base: f64) -> [f64; N] { + const RATIO: f64 = 1.5; + array::from_fn(|i| base * RATIO.powi(i as i32 - 3)).map(|val| (val * 1_e7).round() / 1_e7) +} diff --git a/tracing/mainmatter-workshop/src/routes.rs b/tracing/mainmatter-workshop/src/routes.rs new file mode 100644 index 0000000..12d3313 --- /dev/null +++ b/tracing/mainmatter-workshop/src/routes.rs @@ -0,0 +1,21 @@ +use std::time::Duration; + +use actix_web::{get, HttpResponse, Responder}; +use actix_web_lab::extract::ThinData; +use metrics_exporter_prometheus::PrometheusHandle; + +#[get("/hello")] +pub(crate) async fn hello() -> impl Responder { + "Hello, World!" +} + +#[get("/sleep")] +pub(crate) async fn sleep() -> impl Responder { + actix_web::rt::time::sleep(Duration::from_millis(500)).await; + HttpResponse::Ok() +} + +#[get("/metrics")] +pub(crate) async fn metrics(metrics_handle: ThinData) -> impl Responder { + metrics_handle.render() +} diff --git a/websockets/chat-tcp/src/client.rs b/websockets/chat-tcp/src/client.rs index 16ef0a3..cc344aa 100644 --- a/websockets/chat-tcp/src/client.rs +++ b/websockets/chat-tcp/src/client.rs @@ -24,7 +24,7 @@ async fn main() { return; } - if cmd == "/exit" { + if cmd.trim() == "/exit" { println!("exiting input loop"); return; }