actix-web-prom

Build Status docs.rs crates.io MIT licensed

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):

Usage

First add actix-web-prom to your Cargo.toml:

toml [dependencies] actix-web-prom = "0.6.0-beta.1"

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, PrometheusMetricsBuilder};

fn health() -> HttpResponse { HttpResponse::Ok().finish() }

[actix_web::main]

async fn main() -> std::io::Result<()> { let mut labels = HashMap::new(); labels.insert("label1".tostring(), "value1".tostring()); let prometheus = PrometheusMetricsBuilder::new("api") .endpoint("/metrics") .const_labels(labels) .build() .unwrap();

    HttpServer::new(move || {
        App::new()
            .wrap(prometheus.clone())
            .service(web::resource("/health").to(health))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await?;
Ok(())

} ```

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) with Content-Type header content-type: text/plain; version=0.0.4; charset=utf-8 - 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

HELP apihttprequestsdurationseconds HTTP request duration in seconds for all requests

TYPE apihttprequestsdurationseconds histogram

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

HELP apihttprequests_total Total number of HTTP requests

TYPE apihttprequests_total counter

apihttprequests_total{endpoint="/metrics",label1="value1",method="GET",status="200"} 1 ```

Custom metrics

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, PrometheusMetricsBuilder}; use prometheus::{opts, IntCounterVec};

fn health(counter: web::Data) -> HttpResponse { counter.withlabelvalues(&["endpoint", "method", "status"]).inc(); HttpResponse::Ok().finish() }

[actix_web::main]

async fn main() -> std::io::Result<()> { let prometheus = PrometheusMetricsBuilder::new("api") .endpoint("/metrics") .build() .unwrap();

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?;
Ok(())

} ```

Custom Registry

Some apps might have more than one actix_web::HttpServer. If that's the case, you might want to use your own registry:

```rust use actixweb::{web, App, HttpResponse, HttpServer}; use actixwebprom::{PrometheusMetrics, PrometheusMetricsBuilder}; use actixweb::rt::System; use prometheus::Registry; use std::thread;

fn public_handler() -> HttpResponse { HttpResponse::Ok().body("Everyone can see it!") }

fn private_handler() -> HttpResponse { HttpResponse::Ok().body("This can be hidden behind a firewall") }

fn main() -> std::io::Result<()> { let shared_registry = Registry::new();

let private_metrics = PrometheusMetricsBuilder::new("private_api")
    .registry(shared_registry.clone())
    .endpoint("/metrics")
    .build()
    // It is safe to unwrap when __no other app has the same namespace__
    .unwrap();

let public_metrics = PrometheusMetricsBuilder::new("public_api")
    .registry(shared_registry.clone())
    // Metrics should not be available from the outside
    // so no endpoint is registered
    .build()
    .unwrap();

let private_thread = thread::spawn(move || {
    let mut sys = System::new();
    let srv = HttpServer::new(move || {
        App::new()
            .wrap(private_metrics.clone())
            .service(web::resource("/test").to(private_handler))
    })
    .bind("127.0.0.1:8081")
    .unwrap()
    .run();
    sys.block_on(srv).unwrap();
});

let public_thread = thread::spawn(|| {
    let mut sys = System::new();
    let srv = HttpServer::new(move || {
        App::new()
            .wrap(public_metrics.clone())
            .service(web::resource("/test").to(public_handler))
    })
    .bind("127.0.0.1:8082")
    .unwrap()
    .run();
    sys.block_on(srv).unwrap();
});

private_thread.join().unwrap();
public_thread.join().unwrap();
Ok(())

}

```