1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
#![forbid(future_incompatible, unsafe_code)]
//! Tide middleware for [`prometheus`] with a few default metrics.
//!
//! ## Example
//!
//! ```rust
//! # async_std::task::block_on(async {
//! let mut server = tide::new();
//!
//! server.with(tide_prometheus::Prometheus::new("tide"));
//!
//! // Optionally serve these metrics on the same server:
//! server.at("/metrics").get(tide_prometheus::metrics_endpoint);
//! # });
//! ```
//!
//! ## Metrics
//!
//! The `{prefix}` below is the string you put in [`Prometheus::new`].
//!
//! * `{prefix}_http_requests` ([`prometheus::IntCounterVec`]) with labels:
//! * `method` as the request method.
//! * `status` as the response status.
//!
//! ## Features
//!
//! * `process` will enable the [`prometheus`] `process` feature, recording
//! various metrics of the process.
pub use prometheus;
use prometheus::Encoder;
/// Tide middleware for [`prometheus`] with a few default metrics.
#[derive(Clone, Debug)]
pub struct Prometheus {
/// The `{prefix}_http_requests` counter.
http_requests: prometheus::IntCounterVec,
}
impl Prometheus {
/// Creates a new Prometheus middleware. This also creates and registers the
/// metrics in the [`prometheus::default_registry`].
pub fn new(prefix: &str) -> Self {
Self {
http_requests: Self::http_requests(prefix),
}
}
/// Creates, registers and returns the `{prefix}_http_requests` counter.
fn http_requests(prefix: &str) -> prometheus::IntCounterVec {
let name = format!("{}_http_requests", prefix);
let opts = prometheus::Opts::new(name, "Counts http requests");
prometheus::register_int_counter_vec!(opts, &["method", "code"]).unwrap()
}
}
#[tide::utils::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State>
for Prometheus
{
async fn handle(
&self,
request: tide::Request<State>,
next: tide::Next<'_, State>,
) -> tide::Result {
let method = request.method();
let response = next.run(request).await;
let status_code = response.status().to_string();
self
.http_requests
.with_label_values(&[method.as_ref(), &status_code])
.inc();
Ok(response)
}
}
/// A convencience [`tide::Endpoint`] that gathers the metrics with
/// [`prometheus::gather`] and then returns them inside the response body as
/// specified by [the Prometheus docs](https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format).
///
/// ## Example
///
/// ```rust
/// # async_std::task::block_on(async {
/// let mut server = tide::new();
/// server.with(tide_prometheus::Prometheus::new("tide"));
/// server.at("/metrics").get(tide_prometheus::metrics_endpoint);
/// # });
/// ```
///
/// Note that serving the metrics on the same server they're counted on will
/// make Prometheus's scraping also get counted in them. If you want to avoid
/// this you can use something like
/// [`tide-fluent-routes`](https://crates.io/crates/tide-fluent-routes) and have
/// separate trees for your main routes and the one for the metrics endpoint. Or
/// you can just run a completely separate server in the same program.
pub async fn metrics_endpoint<State>(
_request: tide::Request<State>,
) -> tide::Result {
let encoder = prometheus::TextEncoder::new();
let metric_families = prometheus::gather();
let mut buffer = vec![];
encoder.encode(&metric_families, &mut buffer)?;
let metrics = String::from_utf8(buffer)?;
Ok(
tide::Response::builder(tide::StatusCode::Ok)
.content_type(prometheus::TEXT_FORMAT)
.body(metrics)
.build(),
)
}