{"id":25453168,"url":"https://github.com/nlopes/actix-web-prom","last_synced_at":"2025-05-15T11:06:01.381Z","repository":{"id":34872712,"uuid":"185838119","full_name":"nlopes/actix-web-prom","owner":"nlopes","description":"Actix-web middleware to expose Prometheus metrics","archived":false,"fork":false,"pushed_at":"2025-05-10T12:16:10.000Z","size":113,"stargazers_count":98,"open_issues_count":12,"forks_count":57,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-10T12:36:43.758Z","etag":null,"topics":["actix-web","middleware","prometheus","prometheus-metrics"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nlopes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["nlopes"]}},"created_at":"2019-05-09T16:48:18.000Z","updated_at":"2025-05-10T12:16:14.000Z","dependencies_parsed_at":"2024-08-24T09:43:44.584Z","dependency_job_id":"bbc363b5-f44b-4a31-8ea9-70e948fe2f24","html_url":"https://github.com/nlopes/actix-web-prom","commit_stats":{"total_commits":70,"total_committers":18,"mean_commits":3.888888888888889,"dds":"0.48571428571428577","last_synced_commit":"d4d6e50aae7c18a7d949c6fed583a9a7b8eb9119"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Factix-web-prom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Factix-web-prom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Factix-web-prom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nlopes%2Factix-web-prom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nlopes","download_url":"https://codeload.github.com/nlopes/actix-web-prom/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328385,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actix-web","middleware","prometheus","prometheus-metrics"],"created_at":"2025-02-17T23:49:29.987Z","updated_at":"2025-05-15T11:06:01.365Z","avatar_url":"https://github.com/nlopes.png","language":"Rust","funding_links":["https://github.com/sponsors/nlopes"],"categories":[],"sub_categories":[],"readme":"# actix-web-prom\n\n[![CI Status](https://github.com/nlopes/actix-web-prom/workflows/Test/badge.svg)](https://github.com/nlopes/actix-web-prom/actions)\n[![docs.rs](https://docs.rs/actix-web-prom/badge.svg)](https://docs.rs/actix-web-prom)\n[![crates.io](https://img.shields.io/crates/v/actix-web-prom.svg)](https://crates.io/crates/actix-web-prom)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nlopes/actix-web-prom/blob/master/LICENSE)\n\nPrometheus instrumentation for [actix-web](https://github.com/actix/actix-web). This middleware is heavily influenced by the work in [sd2k/rocket_prometheus](https://github.com/sd2k/rocket_prometheus). We track the same default metrics and allow for adding user defined metrics.\n\nBy default two metrics are tracked (this assumes the namespace `actix_web_prom`):\n\n  - `actix_web_prom_http_requests_total` (labels: endpoint, method, status): the total number\n    of HTTP requests handled by the actix HttpServer.\n\n  - `actix_web_prom_http_requests_duration_seconds` (labels: endpoint, method, status): the\n    request duration for all HTTP requests handled by the actix HttpServer.\n\n\n## Usage\n\nFirst add `actix-web-prom` to your `Cargo.toml`:\n\n```toml\n[dependencies]\nactix-web-prom = \"0.10.0\"\n```\n\nYou then instantiate the prometheus middleware and pass it to `.wrap()`:\n\n```rust\nuse std::collections::HashMap;\n\nuse actix_web::{web, App, HttpResponse, HttpServer};\nuse actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder};\n\nasync fn health() -\u003e HttpResponse {\n    HttpResponse::Ok().finish()\n}\n\n#[actix_web::main]\nasync fn main() -\u003e std::io::Result\u003c()\u003e {\n    let mut labels = HashMap::new();\n    labels.insert(\"label1\".to_string(), \"value1\".to_string());\n    let prometheus = PrometheusMetricsBuilder::new(\"api\")\n        .endpoint(\"/metrics\")\n        .const_labels(labels)\n        .build()\n        .unwrap();\n\n        HttpServer::new(move || {\n            App::new()\n                .wrap(prometheus.clone())\n                .service(web::resource(\"/health\").to(health))\n        })\n        .bind(\"127.0.0.1:8080\")?\n        .run()\n        .await?;\n    Ok(())\n}\n```\n\nUsing the above as an example, a few things are worth mentioning:\n - `api` is the metrics namespace\n - `/metrics` will be auto exposed (GET requests only) with Content-Type header `content-type: text/plain; version=0.0.4; charset=utf-8`\n - `Some(labels)` is used to add fixed labels to the metrics; `None` can be passed instead\n   if no additional labels are necessary.\n\n\nA call to the /metrics endpoint will expose your metrics:\n\n```shell\n$ curl http://localhost:8080/metrics\n# HELP api_http_requests_duration_seconds HTTP request duration in seconds for all requests\n# TYPE api_http_requests_duration_seconds histogram\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.005\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.01\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.025\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.05\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.1\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.25\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"0.5\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"1\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"2.5\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"5\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"10\"} 1\napi_http_requests_duration_seconds_bucket{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\",le=\"+Inf\"} 1\napi_http_requests_duration_seconds_sum{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\"} 0.00003\napi_http_requests_duration_seconds_count{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\"} 1\n# HELP api_http_requests_total Total number of HTTP requests\n# TYPE api_http_requests_total counter\napi_http_requests_total{endpoint=\"/metrics\",label1=\"value1\",method=\"GET\",status=\"200\"} 1\n```\n\n### Features\nIf you enable `process` feature of this crate, default process metrics will also be collected.\n[Default process metrics](https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics)\n\n```shell\n# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 0.22\n# HELP process_max_fds Maximum number of open file descriptors.\n# TYPE process_max_fds gauge\nprocess_max_fds 1048576\n# HELP process_open_fds Number of open file descriptors.\n# TYPE process_open_fds gauge\nprocess_open_fds 78\n# HELP process_resident_memory_bytes Resident memory size in bytes.\n# TYPE process_resident_memory_bytes gauge\nprocess_resident_memory_bytes 17526784\n# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.\n# TYPE process_start_time_seconds gauge\nprocess_start_time_seconds 1628105774.92\n# HELP process_virtual_memory_bytes Virtual memory size in bytes.\n# TYPE process_virtual_memory_bytes gauge\nprocess_virtual_memory_bytes 1893163008\n```\n\n### Custom metrics\n\nYou instantiate `PrometheusMetrics` and then use its `.registry` to register your custom\nmetric (in this case, we use a `IntCounterVec`).\n\nThen you can pass this counter through `.data()` to have it available within the resource\nresponder.\n\n```rust\nuse actix_web::{web, App, HttpResponse, HttpServer};\nuse actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder};\nuse prometheus::{opts, IntCounterVec};\n\nasync fn health(counter: web::Data\u003cIntCounterVec\u003e) -\u003e HttpResponse {\n    counter.with_label_values(\u0026[\"endpoint\", \"method\", \"status\"]).inc();\n    HttpResponse::Ok().finish()\n}\n\n#[actix_web::main]\nasync fn main() -\u003e std::io::Result\u003c()\u003e {\n    let prometheus = PrometheusMetricsBuilder::new(\"api\")\n        .endpoint(\"/metrics\")\n        .build()\n        .unwrap();\n\n    let counter_opts = opts!(\"counter\", \"some random counter\").namespace(\"api\");\n    let counter = IntCounterVec::new(counter_opts, \u0026[\"endpoint\", \"method\", \"status\"]).unwrap();\n    prometheus\n        .registry\n        .register(Box::new(counter.clone()))\n        .unwrap();\n\n        HttpServer::new(move || {\n            App::new()\n                .wrap(prometheus.clone())\n                .data(counter.clone())\n                .service(web::resource(\"/health\").to(health))\n        })\n        .bind(\"127.0.0.1:8080\")?\n        .run()\n        .await?;\n    Ok(())\n}\n```\n\n### Custom `Registry`\n\nSome apps might have more than one `actix_web::HttpServer`.\nIf that's the case, you might want to use your own registry:\n\n```rust\nuse actix_web::{web, App, HttpResponse, HttpServer};\nuse actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder};\nuse actix_web::rt::System;\nuse prometheus::Registry;\nuse std::thread;\n\nasync fn public_handler() -\u003e HttpResponse {\n    HttpResponse::Ok().body(\"Everyone can see it!\")\n}\n\nasync fn private_handler() -\u003e HttpResponse {\n    HttpResponse::Ok().body(\"This can be hidden behind a firewall\")\n}\n\nfn main() -\u003e std::io::Result\u003c()\u003e {\n    let shared_registry = Registry::new();\n\n    let private_metrics = PrometheusMetricsBuilder::new(\"private_api\")\n        .registry(shared_registry.clone())\n        .endpoint(\"/metrics\")\n        .build()\n        // It is safe to unwrap when __no other app has the same namespace__\n        .unwrap();\n\n    let public_metrics = PrometheusMetricsBuilder::new(\"public_api\")\n        .registry(shared_registry.clone())\n        // Metrics should not be available from the outside\n        // so no endpoint is registered\n        .build()\n        .unwrap();\n\n    let private_thread = thread::spawn(move || {\n        let mut sys = System::new();\n        let srv = HttpServer::new(move || {\n            App::new()\n                .wrap(private_metrics.clone())\n                .service(web::resource(\"/test\").to(private_handler))\n        })\n        .bind(\"127.0.0.1:8081\")\n        .unwrap()\n        .run();\n        sys.block_on(srv).unwrap();\n    });\n\n    let public_thread = thread::spawn(|| {\n        let mut sys = System::new();\n        let srv = HttpServer::new(move || {\n            App::new()\n                .wrap(public_metrics.clone())\n                .service(web::resource(\"/test\").to(public_handler))\n        })\n        .bind(\"127.0.0.1:8082\")\n        .unwrap()\n        .run();\n        sys.block_on(srv).unwrap();\n    });\n\n    private_thread.join().unwrap();\n    public_thread.join().unwrap();\n    Ok(())\n}\n\n```\n\n### Configurable routes pattern cardinality\n\nLet's say you have on your app a route to fetch posts by language and by slug `GET /posts/{language}/{slug}`.\nBy default, actix-web-prom will provide metrics for the whole route with the label `endpoint` set to the pattern `/posts/{language}/{slug}`.\nThis is great but you cannot differentiate metrics across languages (as there is only a limited set of them).\nActix-web-prom can be configured to allow for more cardinality on some route params.\n\nFor that you need to add a middleware to pass some [extensions data](https://blog.adamchalmers.com/what-are-extensions/), specifically the `MetricsConfig` struct that contains the list of params you want to keep cardinality on.\n\n```rust\nuse actix_web::{dev::Service, web, HttpMessage, HttpResponse};\nuse actix_web_prom::MetricsConfig;\n\nasync fn handler() -\u003e HttpResponse {\n    HttpResponse::Ok().finish()\n}\n\nweb::resource(\"/posts/{language}/{slug}\")\n    .wrap_fn(|req, srv| {\n        req.extensions_mut().insert::\u003cMetricsConfig\u003e(\n            MetricsConfig { cardinality_keep_params: vec![\"language\".to_string()] }\n        );\n        srv.call(req)\n    })\n    .route(web::get().to(handler));\n```\n\nSee the full example `with_cardinality_on_params.rs`.\n\n### Configurable metric names\n\nIf you want to rename the default metrics, you can use `ActixMetricsConfiguration` to do so.\n\n```rust\nuse actix_web_prom::{PrometheusMetricsBuilder, ActixMetricsConfiguration};\n\nPrometheusMetricsBuilder::new(\"api\")\n    .endpoint(\"/metrics\")\n    .metrics_configuration(\n        ActixMetricsConfiguration::default()\n        .http_requests_duration_seconds_name(\"my_http_request_duration\"),\n    )\n    .build()\n    .unwrap();\n```\n\nSee full example `configuring_default_metrics.rs`.\n\n### Masking unknown paths\n\nThis is useful to avoid producting lots and lots of useless metrics due to bots on the internet.\n\nWhat this does is transform a path that will never be found (404) into *one single\nmetric*. So, if you want metrics about every single path that is hit, even if it doesn't\nexist, avoid this section altogether.\n\n```rust\nuse actix_web_prom::PrometheusMetricsBuilder;\n\nPrometheusMetricsBuilder::new(\"api\")\n    .endpoint(\"/metrics\")\n    .mask_unmatched_patterns(\"UNKNOWN\")\n    .build()\n    .unwrap();\n```\n\nThe above will convert all `/\u003cnonexistent-path\u003e` into `UNKNOWN`:\n\n```\nhttp_requests_duration_seconds_sum{endpoint=\"/favicon.ico\",method=\"GET\",status=\"400\"} 0.000424898\n```\n\nbecomes\n\n```\nhttp_requests_duration_seconds_sum{endpoint=\"UNKNOWN\",method=\"GET\",status=\"400\"} 0.000424898\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnlopes%2Factix-web-prom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnlopes%2Factix-web-prom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnlopes%2Factix-web-prom/lists"}