Skip to main content

hoprd_api/middleware/
prometheus.rs

1use axum::{extract::Request, middleware::Next, response::Response};
2#[cfg(all(feature = "telemetry", not(test)))]
3use hopr_lib::AsUnixTimestamp;
4
5#[cfg(all(feature = "telemetry", not(test)))]
6lazy_static::lazy_static! {
7    static ref METRIC_COUNT_API_CALLS:  hopr_metrics::MultiCounter =  hopr_metrics::MultiCounter::new(
8        "hopr_http_api_call_count",
9        "Number of different REST API calls and their statuses",
10        &["path", "method", "status"]
11    )
12    .unwrap();
13    static ref METRIC_COUNT_API_CALLS_TIMING:  hopr_metrics::MultiHistogram =  hopr_metrics::MultiHistogram::new(
14        "hopr_http_api_call_timing_sec",
15        "Timing of different REST API calls in seconds",
16        vec![0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0],
17        &["path", "method"]
18    )
19    .unwrap();
20    static ref METRIC_API_LAST_TIME:  hopr_metrics::SimpleGauge =  hopr_metrics::SimpleGauge::new(
21        "hopr_http_api_last_used_time",
22        "The unix timestamp in seconds at which any API endpoint was last fetched"
23    ).unwrap();
24
25    // Matches Ethereum addresses which contains 40 hex characters prefixed by '0x'
26    // Matches ChannelsIds which contains 64 hex characters prefixed by '0x'
27    // Matches PeerIds which contains 12D3KooW followed by 44 base58 characters
28    // Matches IPv4 addresses with ports in format x.x.x.x/port
29    static ref ID_REGEX: regex::Regex = regex::Regex::new(r"(0x[0-9A-Fa-f]{40})|(0x[0-9A-Fa-f]{64})|(12D3KooW[A-Za-z0-9]{44})|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,5})").unwrap();
30}
31
32/// Custom prometheus recording middleware
33#[cfg(all(feature = "telemetry", not(test)))]
34pub(crate) async fn record(
35    uri: axum::extract::OriginalUri,
36    method: axum::http::Method,
37    request: Request,
38    next: Next,
39) -> Response {
40    let path = uri.path().to_owned();
41
42    let start = std::time::Instant::now();
43    let response: Response = next.run(request).await;
44    let response_duration = start.elapsed();
45
46    let status = response.status();
47
48    // We're not interested in metrics for other than our own API endpoints
49    if path.starts_with("/api/v4/") && !path.contains("node/metrics") {
50        let path = ID_REGEX.replace(&path, "<id>");
51        METRIC_COUNT_API_CALLS.increment(&[&path, method.as_str(), &status.to_string()]);
52        METRIC_COUNT_API_CALLS_TIMING.observe(&[&path, method.as_str()], response_duration.as_secs_f64());
53    }
54
55    // Set for any API call
56    METRIC_API_LAST_TIME.set(std::time::SystemTime::now().as_unix_timestamp().as_secs_f64());
57
58    response
59}
60
61#[cfg(any(not(feature = "telemetry"), test))]
62pub(crate) async fn record(request: Request, next: Next) -> Response {
63    next.run(request).await
64}