Observability #6
@@ -12,8 +12,12 @@
|
||||
"containerEnv": {
|
||||
"SHELL": "/bin/bash"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["fill-labs.dependi", "rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
||||
}
|
||||
},
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind",
|
||||
"workspaceFolder": "/workspaces/herald",
|
||||
"runArgs": ["--userns=keep-id", "--security-opt", "label=disable"],
|
||||
"appPort": [3000]
|
||||
}
|
||||
|
||||
@@ -17,3 +17,6 @@ GITEA_TIMEOUT=30
|
||||
SENTRY_DSN=
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
|
||||
METRICS_BIND_ADDR=
|
||||
|
qpismont marked this conversation as resolved
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.check.command": "clippy"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"lsp": {
|
||||
"rust-analyzer": {
|
||||
"initialization_options": {
|
||||
"check": {
|
||||
"command": "clippy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"bytestring",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"foldhash 0.1.5",
|
||||
"futures-core",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
@@ -129,7 +129,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"foldhash 0.1.5",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"impl-more",
|
||||
@@ -280,15 +280,6 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
@@ -341,18 +332,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.10.0"
|
||||
@@ -379,31 +358,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctutils"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
|
||||
dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
@@ -523,18 +490,6 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"ctutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
@@ -605,6 +560,17 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evmap"
|
||||
version = "11.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8"
|
||||
dependencies = [
|
||||
"hashbag",
|
||||
"left-right",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
@@ -641,6 +607,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -726,6 +698,21 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -778,6 +765,21 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbag"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
@@ -786,7 +788,7 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "herald"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -794,15 +796,15 @@ dependencies = [
|
||||
"dotenvy",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac",
|
||||
"metrics",
|
||||
"metrics-exporter-prometheus",
|
||||
"openrouter-rs",
|
||||
"reqwest 0.12.28",
|
||||
"ring",
|
||||
"sentry",
|
||||
"sentry-anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -819,15 +821,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.2"
|
||||
@@ -895,15 +888,6 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
@@ -1103,7 +1087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1142,6 +1126,17 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "left-right"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bc015ded5d9b3054dbbdb63332cdd6ee42352ccef19e911e25117490e2f48ee"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"loom",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
@@ -1181,6 +1176,19 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
@@ -1208,12 +1216,70 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "metrics"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
"rapidhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics-exporter-prometheus"
|
||||
version = "0.18.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"evmap",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
"metrics",
|
||||
"metrics-util",
|
||||
"quanta",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics-util"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.16.1",
|
||||
"metrics",
|
||||
"quanta",
|
||||
"rand",
|
||||
"rand_xoshiro",
|
||||
"rapidhash",
|
||||
"sketches-ddsketch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -1455,9 +1521,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "openrouter-rs"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7101578df2f54d9013594e94367adbe656521ef6002f03e4577f1e00e57cb4"
|
||||
checksum = "25918cfeef40cfd3adea348b268496ee5206eb0408f2aacfe4bdfef2bfbe3bf9"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"dotenvy_macro",
|
||||
@@ -1602,6 +1668,12 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -1635,6 +1707,21 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
@@ -1734,6 +1821,33 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rapidhash"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@@ -1816,6 +1930,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
@@ -2007,6 +2122,12 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -2268,17 +2389,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -2304,6 +2414,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sketches-ddsketch"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
@@ -2516,9 +2632,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -2701,12 +2817,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "uname"
|
||||
version = "0.1.1"
|
||||
@@ -2716,6 +2826,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
@@ -2981,6 +3097,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "herald"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
@@ -8,7 +8,7 @@ debug = 1
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
tokio = { version = "1.52", features = ["full"] }
|
||||
tokio = { version = "1.53", features = ["full"] }
|
||||
tokio-stream = "0.1"
|
||||
tokio-util = "0.7"
|
||||
futures-util = "0.3"
|
||||
@@ -16,7 +16,7 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sentry = { version = "0.48", features = ["tower-axum-matched-path"] }
|
||||
sentry-anyhow = { version = "0.48", features = ["backtrace"] }
|
||||
openrouter-rs = "0.10"
|
||||
openrouter-rs = "0.12"
|
||||
dotenvy = "0.15"
|
||||
tower = "0.5"
|
||||
tower-http = {version = "0.6", features = ["trace"] }
|
||||
@@ -25,8 +25,8 @@ tracing-subscriber = { version = "0.3", features=["env-filter"] }
|
||||
axum = "0.8"
|
||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||
thiserror = "2.0"
|
||||
hmac = "0.13"
|
||||
sha2 = "0.11"
|
||||
ring = "0.17"
|
||||
hex = "0.4"
|
||||
subtle = "2.6"
|
||||
bytes = "1.11"
|
||||
bytes = "1.1"
|
||||
metrics = "0.24"
|
||||
metrics-exporter-prometheus = { version = "0.18", default-features = false, features = ["http-listener"] }
|
||||
|
||||
@@ -9,6 +9,7 @@ Herald is a Gitea bot that performs automated AI-powered code reviews on pull re
|
||||
- Concurrent review processing with configurable parallelism
|
||||
- Graceful shutdown — in-progress reviews finish before the process exits
|
||||
- Error tracking via Sentry
|
||||
- Prometheus metrics endpoint for monitoring
|
||||
- Tiny memory footprint (~4MB) thanks to Rust
|
||||
|
||||
## Installation
|
||||
@@ -34,6 +35,7 @@ Herald reads its configuration from environment variables (a `.env` file is supp
|
||||
| `GITEA_URL` | Base URL of your Gitea instance |
|
||||
| `GITEA_TOKEN` | Gitea API token |
|
||||
| `GITEA_TIMEOUT` | Gitea API request timeout in seconds |
|
||||
| `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. |
|
||||
| `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking |
|
||||
| `RUST_LOG` | *(optional)* Log level, defaults to `info` |
|
||||
|
||||
@@ -51,3 +53,40 @@ cargo run
|
||||
```
|
||||
|
||||
Copy `.env.example` to `.env` and fill in your values before running.
|
||||
|
||||
## Metrics
|
||||
|
||||
Herald optionally exposes a Prometheus metrics endpoint, useful for scraping with an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) or any Prometheus-compatible scraper.
|
||||
|
||||
Set `METRICS_BIND_ADDR` (e.g. `0.0.0.0:9100`) to enable it. The metrics are then available at `http://<host>:9100/metrics`.
|
||||
|
||||
### Exposed metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|---|---|---|
|
||||
| `herald_webhooks_received_total` | counter | Total webhooks received (label: `event_type`) |
|
||||
| `herald_webhooks_duplicate_total` | counter | Webhooks rejected as duplicates (label: `event_type`) |
|
||||
| `herald_webhooks_channel_full_total` | counter | Webhooks dropped because the bot channel was full (label: `event_type`) |
|
||||
| `herald_bot_tasks_active` | gauge | Bot tasks currently in progress |
|
||||
| `herald_bot_tasks_completed_total` | counter | Bot tasks completed successfully (label: `event_type`) |
|
||||
| `herald_bot_tasks_failed_total` | counter | Bot tasks that failed (label: `event_type`) |
|
||||
| `herald_openrouter_cost_cents_total` | counter | Total OpenRouter cost in cents (divide by 100 for USD) |
|
||||
|
||||
### OTel collector example
|
||||
|
||||
```yaml
|
||||
receivers:
|
||||
prometheus:
|
||||
config:
|
||||
scrape_configs:
|
||||
- job_name: herald
|
||||
scrape_interval: 15s
|
||||
static_configs:
|
||||
- targets: ["herald:9100"]
|
||||
|
||||
service:
|
||||
pipelines:
|
||||
metrics:
|
||||
receivers: [prometheus]
|
||||
exporters: [otlp]
|
||||
```
|
||||
@@ -4,12 +4,10 @@ use axum::http::Request;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use reqwest::StatusCode;
|
||||
use ring::hmac;
|
||||
use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer};
|
||||
use serde_json::Value;
|
||||
use sha2::Sha256;
|
||||
use subtle::ConstantTimeEq;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{info, instrument};
|
||||
@@ -19,6 +17,7 @@ use tokio_util::sync::CancellationToken;
|
||||
use crate::consts::{GITEA_EVENT_TYPE_HEADER_NAME, GITEA_SIG_HEADER_NAME, MAX_WEBHOOK_BODY_SIZE};
|
||||
use crate::errors::AppError;
|
||||
use crate::gitea::WebhookType;
|
||||
use crate::metrics;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn start(app_state: AppState, shutdown: CancellationToken) -> anyhow::Result<()> {
|
||||
@@ -58,13 +57,18 @@ async fn webhook(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
tracing::Span::current().record("webhook_type", tracing::field::debug(&wb));
|
||||
|
||||
let event_type = wb.event_type_str();
|
||||
metrics::webhook_received(event_type);
|
||||
|
||||
let event_id = wb.event_id();
|
||||
if app_state.bot.check_and_mark(event_id).await {
|
||||
metrics::webhook_duplicate(event_type);
|
||||
return Err(AppError::AlreadyProcessedErr);
|
||||
}
|
||||
|
||||
if app_state.bot_tx.try_send(wb).is_err() {
|
||||
app_state.bot.unmark(event_id).await;
|
||||
metrics::webhook_channel_full(event_type);
|
||||
return Err(AppError::ChannelFullErr);
|
||||
}
|
||||
|
||||
@@ -105,7 +109,7 @@ where
|
||||
});
|
||||
});
|
||||
|
||||
let webhook = parse_webhook(&type_header, &app_state.config.bot_name, &body_bytes)?;
|
||||
let webhook = parse_webhook(&type_header, &app_state.bot.name(), &body_bytes)?;
|
||||
Ok(WebhookExtract(webhook))
|
||||
}
|
||||
}
|
||||
@@ -137,12 +141,7 @@ fn parse_webhook(header: &str, bot_name: &str, body_bytes: &[u8]) -> Result<Webh
|
||||
fn verify_signature(secret_key: &[u8], sig_header: &str, body: &[u8]) -> Result<(), AppError> {
|
||||
let sig_header_decoded =
|
||||
hex::decode(sig_header).map_err(|_| AppError::WebHookSigHeaderInvalidErr)?;
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(secret_key).map_err(anyhow::Error::from)?;
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key);
|
||||
|
||||
mac.update(body);
|
||||
|
||||
let generated_hmac = mac.finalize().into_bytes();
|
||||
bool::from(generated_hmac.ct_eq(&sig_header_decoded))
|
||||
.then_some(())
|
||||
.ok_or(AppError::WebHookSigHeaderInvalidErr)
|
||||
hmac::verify(&key, body, &sig_header_decoded).map_err(|_| AppError::WebHookSigHeaderInvalidErr)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use crate::{
|
||||
env::EnvConfig,
|
||||
gitea::{GiteaAPI, WebhookType},
|
||||
open_router::OpenRouterClient,
|
||||
gitea::{GiteaAPI, WebhookType}, metrics, open_router::OpenRouterClient,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, instrument};
|
||||
@@ -26,33 +24,37 @@ pub struct ReviewItem {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bot {
|
||||
config: EnvConfig,
|
||||
bot_name: String,
|
||||
gitea_api: GiteaAPI,
|
||||
open_router_client: OpenRouterClient,
|
||||
http_client: reqwest::Client,
|
||||
max_concurrent: usize,
|
||||
open_router_model: String,
|
||||
actions_handled: Arc<Mutex<HashSet<u64>>>,
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
pub fn new(config: EnvConfig) -> anyhow::Result<Self> {
|
||||
let gitea_timeout = config.gitea_timeout;
|
||||
let open_router_timeout = config.open_router_timeout;
|
||||
|
||||
Ok(Self {
|
||||
gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token, gitea_timeout)?,
|
||||
open_router_client: OpenRouterClient::new(
|
||||
&config.open_router_api_key,
|
||||
&config.open_router_model,
|
||||
open_router_timeout,
|
||||
)?,
|
||||
max_concurrent: config.bot_max_concurrent,
|
||||
config,
|
||||
pub fn new(
|
||||
bot_name: String,
|
||||
gitea_api: GiteaAPI,
|
||||
open_router_client: OpenRouterClient,
|
||||
http_client: reqwest::Client,
|
||||
max_concurrent: usize,
|
||||
open_router_model: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
bot_name,
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
http_client,
|
||||
max_concurrent,
|
||||
open_router_model,
|
||||
actions_handled: Arc::new(Mutex::new(HashSet::new())),
|
||||
http_client: reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(gitea_timeout))
|
||||
.build()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
|
qpismont marked this conversation as resolved
Herald
commented
La méthode La méthode `name()` retourne une `String` en clonant `self.bot_name`. Cela peut être inefficace si appelée fréquemment. Envisagez de retourner un `&str` pour éviter une allocation, ou changez le champ en `Arc<str>` si un partage est nécessaire.
|
||||
self.bot_name.clone()
|
||||
}
|
||||
|
||||
pub async fn start(
|
||||
@@ -84,10 +86,12 @@ impl Bot {
|
||||
info!(queued = rx.len(), active = tasks.len(), "Webhook received");
|
||||
let permit = sem.clone().acquire_owned().await?;
|
||||
let self_clone = self.clone();
|
||||
metrics::increment_task_active();
|
||||
|
||||
tasks.spawn(async move {
|
||||
self_clone.exec(wb).await;
|
||||
drop(permit);
|
||||
metrics::decrement_task_active();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,6 +103,8 @@ impl Bot {
|
||||
|
||||
#[instrument(skip(self, webhook), fields(repo, pr))]
|
||||
pub async fn exec(&self, webhook: WebhookType) {
|
||||
let event_type_str = webhook.event_type_str();
|
||||
|
||||
match &webhook {
|
||||
WebhookType::Review(p) => {
|
||||
tracing::Span::current().record("repo", &p.repository.full_name);
|
||||
@@ -111,15 +117,19 @@ impl Bot {
|
||||
&self.gitea_api,
|
||||
&self.open_router_client,
|
||||
&self.http_client,
|
||||
&self.config.open_router_model,
|
||||
&self.open_router_model,
|
||||
review_payload,
|
||||
),
|
||||
}
|
||||
.await;
|
||||
|
||||
match exec_result {
|
||||
Ok(_) => info!("Task completed"),
|
||||
Ok(_) => {
|
||||
metrics::task_completed(event_type_str);
|
||||
info!("Task completed");
|
||||
}
|
||||
Err(err) => {
|
||||
metrics::task_failed(event_type_str);
|
||||
error!(%err, "Task error");
|
||||
sentry_anyhow::capture_anyhow(&err);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ use tokio_util::io::StreamReader;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{
|
||||
bot::ReviewResult,
|
||||
consts::{BOT_PROCESS_MSG, MAX_DIFF_SIZE, REVIEW_PROMPT},
|
||||
gitea::{GiteaAPI, ReviewPayload},
|
||||
open_router::OpenRouterClient,
|
||||
bot::ReviewResult, consts::{BOT_PROCESS_MSG, MAX_DIFF_SIZE, REVIEW_PROMPT}, gitea::{GiteaAPI, ReviewPayload}, metrics, open_router::OpenRouterClient,
|
||||
};
|
||||
|
||||
#[instrument(skip(gitea_api, open_router_client, http_client, review_payload))]
|
||||
@@ -48,6 +45,9 @@ pub async fn exec_review(
|
||||
let mut review_result = serde_json::from_str::<ReviewResult>(&chat_result.message)?;
|
||||
|
||||
review_result.cost = chat_result.cost;
|
||||
if let Some(cost) = review_result.cost {
|
||||
metrics::openrouter_cost_usd(cost);
|
||||
}
|
||||
|
||||
let final_review_markdown = review_result_to_markdown(&review_result);
|
||||
|
||||
|
||||
@@ -7,16 +7,15 @@ pub struct EnvConfig {
|
||||
pub open_router_api_key: String,
|
||||
pub open_router_model: String,
|
||||
pub open_router_timeout: u64,
|
||||
pub bot_name: String,
|
||||
pub bot_max_concurrent: usize,
|
||||
pub gitea_url: String,
|
||||
pub gitea_token: String,
|
||||
pub gitea_timeout: u64,
|
||||
pub metrics_bind_addr: Option<String>,
|
||||
}
|
||||
|
||||
pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
let http_port = try_get_env("HTTP_PORT")?.parse()?;
|
||||
let bot_name = try_get_env("BOT_NAME")?;
|
||||
let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?;
|
||||
let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?;
|
||||
let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?;
|
||||
@@ -25,11 +24,11 @@ pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
let gitea_url = try_get_env("GITEA_URL")?;
|
||||
let gitea_token = try_get_env("GITEA_TOKEN")?;
|
||||
let gitea_timeout = try_get_env("GITEA_TIMEOUT")?.parse()?;
|
||||
let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok();
|
||||
|
||||
Ok(EnvConfig {
|
||||
http_port,
|
||||
webhook_secret,
|
||||
bot_name,
|
||||
open_router_api_key,
|
||||
open_router_model,
|
||||
open_router_timeout,
|
||||
@@ -37,6 +36,7 @@ pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
gitea_url,
|
||||
gitea_token,
|
||||
gitea_timeout,
|
||||
metrics_bind_addr,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,21 @@ impl GiteaAPI {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn get_authorized_user(&self) -> anyhow::Result<User> {
|
||||
let url = format!("{}/api/v1/user", self.base_url);
|
||||
let res = self.client.get(url).send().await?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to get authorized user: {}",
|
||||
res.status()
|
||||
));
|
||||
}
|
||||
|
||||
res.json::<User>().await.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn comment(
|
||||
&self,
|
||||
@@ -164,6 +179,12 @@ impl WebhookType {
|
||||
WebhookType::Review(payload) => payload.comment.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_type_str(&self) -> &'static str {
|
||||
match self {
|
||||
WebhookType::Review(_) => "review",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -192,6 +213,7 @@ pub struct Comment {
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct User {
|
||||
pub id: u64,
|
||||
pub login: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -248,7 +270,8 @@ mod tests {
|
||||
"id": 7,
|
||||
"body": "@test_bot LGTM",
|
||||
"user": {
|
||||
"id": 100
|
||||
"id": 100,
|
||||
"login": "test_user"
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -312,7 +335,8 @@ mod tests {
|
||||
"id": 1,
|
||||
"body": "@test_bot body",
|
||||
"user": {
|
||||
"id": 1
|
||||
"id": 1,
|
||||
"login": "test_user"
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -343,7 +367,8 @@ mod tests {
|
||||
"id": 12,
|
||||
"body": "Needs work",
|
||||
"user": {
|
||||
"id": 200
|
||||
"id": 200,
|
||||
"login": "test_user"
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -380,7 +405,8 @@ mod tests {
|
||||
"id": 1,
|
||||
"body": "@other_bot do something",
|
||||
"user": {
|
||||
"id": 1
|
||||
"id": 1,
|
||||
"login": "test_user"
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -406,7 +432,8 @@ mod tests {
|
||||
"id": 1,
|
||||
"body": "just a comment without bot mention",
|
||||
"user": {
|
||||
"id": 1
|
||||
"id": 1,
|
||||
"login": "test_user"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{bot::Bot, gitea::WebhookType, state::AppState};
|
||||
use crate::{bot::Bot, gitea::{GiteaAPI, WebhookType}, open_router::OpenRouterClient, state::AppState};
|
||||
|
||||
use dotenvy::dotenv;
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
@@ -15,6 +13,7 @@ mod consts;
|
||||
mod env;
|
||||
mod errors;
|
||||
mod gitea;
|
||||
mod metrics;
|
||||
mod open_router;
|
||||
mod state;
|
||||
|
||||
@@ -51,17 +50,38 @@ fn main() -> anyhow::Result<()> {
|
||||
async fn run() -> anyhow::Result<()> {
|
||||
let config = env::load_config()?;
|
||||
|
||||
if let Some(metric_bind_addr) = &config.metrics_bind_addr {
|
||||
|
qpismont marked this conversation as resolved
Herald
commented
Le démarrage conditionnel de l'exportateur Prometheus est une bonne pratique. Toutefois, si l'installation échoue, l'erreur est propagée via Le démarrage conditionnel de l'exportateur Prometheus est une bonne pratique. Toutefois, si l'installation échoue, l'erreur est propagée via `?`, ce qui met fin à l'application. Cela peut être acceptable, mais un message d'erreur plus explicite (ex: avec `tracing::error`) aiderait au débogage.
|
||||
metrics::install(metric_bind_addr)?;
|
||||
}
|
||||
|
||||
let gitea_api = GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout)?;
|
||||
let gitea_user = gitea_api.get_authorized_user().await?;
|
||||
|
||||
info!(
|
||||
port = config.http_port,
|
||||
model = %config.open_router_model,
|
||||
gitea_url = %config.gitea_url,
|
||||
bot_name = %config.bot_name,
|
||||
bot_name = %gitea_user.login,
|
||||
"Starting Herald"
|
||||
);
|
||||
|
||||
let open_router_client = OpenRouterClient::new(
|
||||
&config.open_router_api_key,
|
||||
&config.open_router_model,
|
||||
config.open_router_timeout,
|
||||
)?;
|
||||
|
||||
let shutdown = CancellationToken::new();
|
||||
|
||||
let bot = Bot::new(config.clone())?;
|
||||
let bot = Bot::new(
|
||||
gitea_user.login,
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
reqwest::Client::new(),
|
||||
config.bot_max_concurrent,
|
||||
config.open_router_model.clone(),
|
||||
);
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<WebhookType>(config.bot_max_concurrent * 2);
|
||||
let app_state = AppState {
|
||||
bot_tx: tx,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use metrics::{Unit, describe_counter, describe_gauge, counter, gauge};
|
||||
|
||||
pub fn webhook_received(event_type: &str) {
|
||||
counter!("herald_webhooks_received_total", "event_type" => event_type.to_string())
|
||||
.increment(1);
|
||||
}
|
||||
|
||||
pub fn webhook_duplicate(event_type: &str) {
|
||||
counter!("herald_webhooks_duplicate_total", "event_type" => event_type.to_string())
|
||||
.increment(1);
|
||||
}
|
||||
|
||||
pub fn webhook_channel_full(event_type: &str) {
|
||||
counter!("herald_webhooks_channel_full_total", "event_type" => event_type.to_string())
|
||||
.increment(1);
|
||||
}
|
||||
|
||||
pub fn increment_task_active() {
|
||||
gauge!("herald_bot_tasks_active").increment(1.0);
|
||||
}
|
||||
|
||||
pub fn decrement_task_active() {
|
||||
gauge!("herald_bot_tasks_active").decrement(1.0);
|
||||
}
|
||||
|
||||
pub fn task_completed(event_type: &str) {
|
||||
counter!("herald_bot_tasks_completed_total", "event_type" => event_type.to_string())
|
||||
.increment(1);
|
||||
}
|
||||
|
||||
pub fn task_failed(event_type: &str) {
|
||||
counter!("herald_bot_tasks_failed_total", "event_type" => event_type.to_string())
|
||||
.increment(1);
|
||||
}
|
||||
|
||||
pub fn openrouter_cost_usd(cost: f64) {
|
||||
|
qpismont marked this conversation as resolved
Outdated
Herald
commented
La métrique La métrique `herald_openrouter_cost_usd_total` utilise un compteur de type `Count`, mais elle représente un coût en dollars. Bien que cela soit fonctionnel, il serait plus clair d'utiliser un type `Gauge` ou d'ajouter une unité personnalisée si possible. Actuellement l'unité `Count` peut prêter à confusion pour les opérateurs.
|
||||
counter!("herald_openrouter_cost_cents_total").increment((cost * 100.0).round() as u64);
|
||||
}
|
||||
|
||||
pub fn describe() {
|
||||
describe_counter!("herald_webhooks_received_total", Unit::Count, "Total webhooks received");
|
||||
describe_counter!("herald_webhooks_duplicate_total", Unit::Count, "Webhooks rejected as duplicates");
|
||||
describe_counter!("herald_webhooks_channel_full_total", Unit::Count, "Webhooks dropped because the bot channel was full");
|
||||
describe_gauge!("herald_bot_tasks_active", Unit::Count, "Bot tasks currently in progress");
|
||||
describe_counter!("herald_bot_tasks_completed_total", Unit::Count, "Bot tasks completed successfully");
|
||||
describe_counter!("herald_bot_tasks_failed_total", Unit::Count, "Bot tasks that failed");
|
||||
describe_counter!("herald_openrouter_cost_cents_total", Unit::Count, "Total OpenRouter cost in cents (divide by 100 for USD)");
|
||||
}
|
||||
|
||||
pub fn install(bind_addr: &str) -> anyhow::Result<()> {
|
||||
describe();
|
||||
|
||||
let builder = metrics_exporter_prometheus::PrometheusBuilder::new();
|
||||
builder
|
||||
.with_http_listener(SocketAddr::from_str(bind_addr)?)
|
||||
.install()?;
|
||||
|
||||
tracing::info!(bind_addr, "Prometheus metrics exporter installed");
|
||||
Ok(())
|
||||
}
|
||||
La variable d'environnement
METRICS_BIND_ADDRest définie sans valeur par défaut ni commentaire. Il serait judicieux d'ajouter un exemple de format (ex:0.0.0.0:9090) ou un commentaire indiquant le format attendu, afin d'éviter des erreurs de configuration.