diff --git a/Cargo.toml b/Cargo.toml index 28b24bb..cfd4c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,14 @@ exclude = [".gitignore", ".travis.yml"] [badges] travis-ci = { repository = "nlopes/actix-web-prom", branch = "master" } +[features] +default = [] +regex-replace = ["regex"] + [dependencies] actix-service = "0.4" actix-web = "1.0.0" futures = "0.1" prometheus = "0.7" +regex = { version = "1.3.1", optional = true } +log = "0.4.8" diff --git a/src/lib.rs b/src/lib.rs index 3bf1607..d92f34f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,9 @@ use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use prometheus::{opts, Encoder, HistogramVec, IntCounterVec, Registry, TextEncoder}; +#[cfg(feature = "regex-replace")] +use regex::Regex; + #[derive(Clone)] #[must_use = "must be set up as middleware for actix-web"] /// By default two metrics are tracked (this assumes the namespace `actix_web_prom`): @@ -221,11 +224,15 @@ pub struct PrometheusMetrics { pub registry: Registry, pub(crate) namespace: String, pub(crate) endpoint: Option, + + #[cfg(feature = "regex-replace")] + pub(crate) replacements: Vec<(Regex, String)>, } impl PrometheusMetrics { /// Create a new PrometheusMetrics. You set the namespace and the metrics endpoint /// through here. + #[cfg(not(feature = "regex-replace"))] pub fn new(namespace: &str, endpoint: Option<&str>) -> Self { let registry = Registry::new(); @@ -234,20 +241,65 @@ impl PrometheusMetrics { } /// Create a new PrometheusMetrics. - /// Throws error if "<`namespace`>_http_requests_total" already registered + /// Returns an error if "<`namespace`>_http_requests_total" already registered. + /// Request paths are adjusted by executing the supplied replacements before storing the metrics. + #[cfg(feature = "regex-replace")] + pub fn new_with_registry( + registry: Registry, + namespace: &str, + endpoint: Option<&str>, + replacements: Vec<(Regex, String)>, + ) -> Result> { + let (http_requests_total, http_requests_duration_seconds) = + Self::register_metrics(®istry, namespace)?; + Ok(PrometheusMetrics { + http_requests_total, + http_requests_duration_seconds, + registry, + namespace: namespace.to_string(), + endpoint: if let Some(url) = endpoint { + Some(url.to_string()) + } else { + None + }, + replacements, + }) + } + + /// Create a new PrometheusMetrics. + /// Returns an error if "<`namespace`>_http_requests_total" already registered + #[cfg(not(feature = "regex-replace"))] pub fn new_with_registry( registry: Registry, namespace: &str, endpoint: Option<&str>, ) -> Result> { + let (http_requests_total, http_requests_duration_seconds) = + Self::register_metrics(®istry, namespace)?; + Ok(PrometheusMetrics { + http_requests_total, + http_requests_duration_seconds, + registry, + namespace: namespace.to_string(), + endpoint: if let Some(url) = endpoint { + Some(url.to_string()) + } else { + None + }, + }) + } + + fn register_metrics( + registry: &Registry, + namespace: &str, + ) -> prometheus::Result<(IntCounterVec, HistogramVec)> { let http_requests_total_opts = opts!("http_requests_total", "Total number of HTTP requests").namespace(namespace); let http_requests_total = IntCounterVec::new(http_requests_total_opts, &["endpoint", "method", "status"]) .unwrap(); - registry - .register(Box::new(http_requests_total.clone())) - .unwrap(); + + registry.register(Box::new(http_requests_total.clone()))?; let http_requests_duration_seconds_opts = opts!( "http_requests_duration_seconds", @@ -259,19 +311,10 @@ impl PrometheusMetrics { &["endpoint", "method", "status"], ) .unwrap(); + registry.register(Box::new(http_requests_duration_seconds.clone()))?; - Ok(PrometheusMetrics { - http_requests_total, - http_requests_duration_seconds, - registry, - namespace: namespace.to_string(), - endpoint: if let Some(url) = endpoint { - Some(url.to_string()) - } else { - None - }, - }) + Ok((http_requests_total, http_requests_duration_seconds)) } fn metrics(&self) -> String { @@ -290,7 +333,29 @@ impl PrometheusMetrics { } } + #[cfg(feature = "regex-replace")] fn update_metrics(&self, path: &str, method: &Method, status: StatusCode, clock: SystemTime) { + log::trace!("About to replacing path capture groups: {}", path); + let mut path = String::from(path); + for (exp, repl) in self.replacements.clone() { + path = String::from(exp.replace_all(&path, &repl as &str)) + } + log::trace!("Replaced path capture groups: {}", path); + self.update_metrics_inner(&path, method, status, clock) + } + + #[cfg(not(feature = "regex-replace"))] + fn update_metrics(&self, path: &str, method: &Method, status: StatusCode, clock: SystemTime) { + self.update_metrics_inner(path, method, status, clock) + } + + fn update_metrics_inner( + &self, + path: &str, + method: &Method, + status: StatusCode, + clock: SystemTime, + ) { let method = method.to_string(); let status = status.as_u16().to_string(); @@ -304,6 +369,7 @@ impl PrometheusMetrics { self.http_requests_total .with_label_values(&[&path, &method, &status]) .inc(); + log::trace!("updated metrics"); } }