static_web_server/
metrics.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! Module providing the experimental metrics endpoint.
7//!
8
9use headers::{ContentType, HeaderMapExt};
10use hyper::{Body, Request, Response};
11use prometheus::{default_registry, Encoder, TextEncoder};
12
13use crate::{handler::RequestHandlerOpts, http_ext::MethodExt, Error};
14
15/// Initializes the metrics endpoint.
16pub fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) {
17    handler_opts.experimental_metrics = enabled;
18    tracing::info!("metrics endpoint (experimental): enabled={enabled}");
19
20    if enabled {
21        default_registry()
22            .register(Box::new(
23                tokio_metrics_collector::default_runtime_collector(),
24            ))
25            .unwrap();
26    }
27}
28
29/// Handles metrics requests
30pub fn pre_process<T>(
31    opts: &RequestHandlerOpts,
32    req: &Request<T>,
33) -> Option<Result<Response<Body>, Error>> {
34    if !opts.experimental_metrics {
35        return None;
36    }
37
38    let uri = req.uri();
39    if uri.path() != "/metrics" {
40        return None;
41    }
42
43    let method = req.method();
44    if !method.is_get() && !method.is_head() {
45        return None;
46    }
47
48    let body = if method.is_get() {
49        let encoder = TextEncoder::new();
50        let mut buffer = Vec::new();
51        encoder
52            .encode(&default_registry().gather(), &mut buffer)
53            .unwrap();
54        let data = String::from_utf8(buffer).unwrap();
55        Body::from(data)
56    } else {
57        Body::empty()
58    };
59    let mut resp = Response::new(body);
60    resp.headers_mut()
61        .typed_insert(ContentType::from(mime_guess::mime::TEXT_PLAIN_UTF_8));
62    Some(Ok(resp))
63}
64
65#[cfg(test)]
66mod tests {
67    use super::pre_process;
68    use crate::handler::RequestHandlerOpts;
69    use hyper::{Body, Request};
70
71    fn make_request(method: &str, uri: &str) -> Request<Body> {
72        Request::builder()
73            .method(method)
74            .uri(uri)
75            .body(Body::empty())
76            .unwrap()
77    }
78
79    #[test]
80    fn test_metrics_disabled() {
81        assert!(pre_process(
82            &RequestHandlerOpts {
83                experimental_metrics: false,
84                ..Default::default()
85            },
86            &make_request("GET", "/metrics")
87        )
88        .is_none());
89    }
90
91    #[test]
92    fn test_wrong_uri() {
93        assert!(pre_process(
94            &RequestHandlerOpts {
95                experimental_metrics: true,
96                ..Default::default()
97            },
98            &make_request("GET", "/metrics2")
99        )
100        .is_none());
101    }
102
103    #[test]
104    fn test_wrong_method() {
105        assert!(pre_process(
106            &RequestHandlerOpts {
107                experimental_metrics: true,
108                ..Default::default()
109            },
110            &make_request("POST", "/metrics")
111        )
112        .is_none());
113    }
114
115    #[test]
116    fn test_correct_request() {
117        assert!(pre_process(
118            &RequestHandlerOpts {
119                experimental_metrics: true,
120                ..Default::default()
121            },
122            &make_request("GET", "/metrics")
123        )
124        .is_some());
125    }
126}