Skip to main content

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}