Prometheus instrumentation for actix-web. This middleware is heavily influenced by the work in sd2k/rocket_prometheus. We track the same default metrics and allow for adding user defined metrics.
By default two metrics are tracked (this assumes the namespace actix_web_prom
):
actix_web_prom_http_requests_total
(labels: endpoint, method, status): the total number
of HTTP requests handled by the actix HttpServer.
actix_web_prom_http_requests_duration_seconds
(labels: endpoint, method, status): the
request duration for all HTTP requests handled by the actix HttpServer.
First add actix-web-prom
to your Cargo.toml
:
toml
[dependencies]
actix-web-prom = "0.2"
You then instantiate the prometheus middleware and pass it to .wrap()
:
```rust use std::collections::HashMap;
use actixweb::{web, App, HttpResponse, HttpServer}; use actixweb_prom::PrometheusMetrics;
fn health() -> HttpResponse { HttpResponse::Ok().finish() }
async fn main() -> std::io::Result<()> { let mut labels = HashMap::new(); labels.insert("label1".tostring(), "value1".tostring()); let prometheus = PrometheusMetrics::new("api", Some("/metrics"), Some(labels)); HttpServer::new(move || { App::new() .wrap(prometheus.clone()) .service(web::resource("/health").to(health)) }) .bind("127.0.0.1:8080")? .run() .await } ```
Using the above as an example, a few things are worth mentioning:
- api
is the metrics namespace
- /metrics
will be auto exposed (GET requests only)
- Some(labels)
is used to add fixed labels to the metrics; None
can be passed instead
if no additional labels are necessary.
A call to the /metrics endpoint will expose your metrics:
```shell $ curl http://localhost:8080/metrics
apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.005"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.01"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.025"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.05"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.1"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.25"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="0.5"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="1"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="2.5"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="5"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="10"} 1 apihttprequestsdurationsecondsbucket{endpoint="/metrics",label1="value1",method="GET",status="200",le="+Inf"} 1 apihttprequestsdurationsecondssum{endpoint="/metrics",label1="value1",method="GET",status="200"} 0.00003 apihttprequestsdurationsecondscount{endpoint="/metrics",label1="value1",method="GET",status="200"} 1
apihttprequests_total{endpoint="/metrics",label1="value1",method="GET",status="200"} 1 ```
You instantiate PrometheusMetrics
and then use its .registry
to register your custom
metric (in this case, we use a IntCounterVec
.
Then you can pass this counter through .data()
to have it available within the resource
responder.
```rust use actixweb::{web, App, HttpResponse, HttpServer}; use actixweb_prom::PrometheusMetrics; use prometheus::{opts, IntCounterVec};
fn health(counter: web::Data
async fn main() -> std::io::Result<()> { let prometheus = PrometheusMetrics::new("api", Some("/metrics"), None);
let counter_opts = opts!("counter", "some random counter").namespace("api");
let counter = IntCounterVec::new(counter_opts, &["endpoint", "method", "status"]).unwrap();
prometheus
.registry
.register(Box::new(counter.clone()))
.unwrap();
HttpServer::new(move || {
App::new()
.wrap(prometheus.clone())
.data(counter.clone())
.service(web::resource("/health").to(health))
})
.bind("127.0.0.1:8080")?
.run()
.await
} ```