huskarl_core/http/metrics_client.rs
1use bytes::Bytes;
2use http::Request;
3
4use super::{HttpClient, HttpResponse, Idempotency};
5use crate::{error::Error, platform::MaybeSendBoxFuture};
6
7/// An [`HttpClient`] wrapper that records a `huskarl.http.request` counter for each request.
8///
9/// Wrap your HTTP client with this type to instrument all outbound requests made by the
10/// huskarl library — including JWKS fetches and token introspection calls.
11///
12/// # Labels
13///
14/// | Label | Values | Description |
15/// |-----------|----------------------------|-------------------------------------|
16/// | `host` | request hostname | Identifies the remote server |
17/// | `method` | `GET`, `POST`, etc. | HTTP method |
18/// | `outcome` | `success`, `error` | Whether the request succeeded |
19///
20/// `outcome` is `success` when the server returned a 2xx response, and `error` for
21/// connection failures or non-2xx responses.
22///
23/// # Example
24///
25/// ```rust,no_run
26/// # use huskarl_core::http::MetricsHttpClient;
27/// # let inner = (); // your HTTP backend, e.g. `huskarl_reqwest::ReqwestClient`
28/// let client = MetricsHttpClient::builder().inner(inner).build();
29/// # let _ = client;
30/// ```
31pub struct MetricsHttpClient<C> {
32 inner: C,
33}
34
35#[bon::bon]
36impl<C> MetricsHttpClient<C> {
37 /// Creates a new [`MetricsHttpClient`].
38 #[builder]
39 pub fn new(inner: C) -> Self {
40 Self { inner }
41 }
42}
43
44impl<C> MetricsHttpClient<C> {
45 /// Returns a reference to the inner client.
46 pub fn inner(&self) -> &C {
47 &self.inner
48 }
49
50 /// Unwraps the inner client.
51 pub fn into_inner(self) -> C {
52 self.inner
53 }
54}
55
56impl<C: HttpClient> HttpClient for MetricsHttpClient<C> {
57 fn execute(
58 &self,
59 request: Request<Bytes>,
60 idempotency: Idempotency,
61 ) -> MaybeSendBoxFuture<'_, Result<HttpResponse, Error>> {
62 Box::pin(async move {
63 let method = request.method().to_string();
64 let host = request.uri().host().unwrap_or("").to_owned();
65
66 let result = self.inner.execute(request, idempotency).await;
67
68 let outcome = match &result {
69 Ok(resp) if resp.status.is_success() => "success",
70 _ => "error",
71 };
72
73 ::metrics::counter!(
74 "huskarl.http.request",
75 "host" => host,
76 "method" => method,
77 "outcome" => outcome,
78 )
79 .increment(1);
80
81 result
82 })
83 }
84
85 fn uses_mtls(&self) -> bool {
86 self.inner.uses_mtls()
87 }
88}