diff --git a/internal/leetcode-auth-stealer-service/src/prom.py b/internal/leetcode-auth-stealer-service/src/prom.py index eafed3cb4..0e79d7408 100644 --- a/internal/leetcode-auth-stealer-service/src/prom.py +++ b/internal/leetcode-auth-stealer-service/src/prom.py @@ -1,9 +1,34 @@ """Prometheus collectors, typed metric updates, and exposition.""" -from typing import Literal +from collections.abc import Iterable, Mapping, Sequence +from typing import Literal, get_args from prometheus_client import CONTENT_TYPE_LATEST, Counter, generate_latest + +def _counter( + name: str, + documentation: str, + labelnames: Sequence[str] = (), + initial_labels: Iterable[Mapping[str, str]] = (), +) -> Counter: + """ + create a counter whose label combinations are exported at 0 before first use. + + https://prometheus.io/docs/practices/the_zen/#avoid-missing-metrics + """ + initial = list(initial_labels) + if labelnames and not initial: + raise ValueError(f"{name}: labeled counters must declare initial_labels") + + counter = Counter(name, documentation, labelnames) + + for labels in initial: + counter.labels(**labels) + + return counter + + type AuthFailureReason = Literal[ "invalid_argument", "bad_credentials", @@ -15,10 +40,15 @@ "incomplete_authentication", ] -_AUTH_CALLS = Counter( +_AUTH_CALLS = _counter( "leetcode_auth_calls_total", "Completed authentication calls by outcome and failure reason.", ("outcome", "reason"), + [{"outcome": "success", "reason": ""}] + + [ + {"outcome": "failure", "reason": reason} + for reason in get_args(AuthFailureReason.__value__) + ], )