about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs8
-rw-r--r--src/metrics.rs65
2 files changed, 73 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 6a62dcc..77a6c11 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,7 @@ mod database;
 mod frontend;
 mod indieauth;
 mod micropub;
+mod metrics;
 
 use crate::indieauth::IndieAuthMiddleware;
 use crate::micropub::CORSMiddleware;
@@ -42,6 +43,7 @@ where
             .build())
     });
     app.at("/")
+        .with(CORSMiddleware {})
         .with(frontend::ErrorHandlerMiddleware {})
         .get(frontend::mainpage)
         .post(frontend::onboarding_receiver);
@@ -54,7 +56,13 @@ where
     app.at("/coffee")
         .with(frontend::ErrorHandlerMiddleware {})
         .get(frontend::coffee);
+    // TODO make sure the health check actually checks the backend or something
+    // otherwise it'll get false-negatives for application faults like resource
+    // exhaustion
     app.at("/health").get(|_| async { Ok("OK") });
+    app.at("/metrics").get(metrics::gather);
+
+    app.with(metrics::InstrumentationMiddleware {});
 
     app
 }
diff --git a/src/metrics.rs b/src/metrics.rs
new file mode 100644
index 0000000..0537b9d
--- /dev/null
+++ b/src/metrics.rs
@@ -0,0 +1,65 @@
+use tide::{Request, Response, Result, Next};
+use prometheus::{self, IntCounterVec, HistogramVec, TextEncoder, Encoder, register_int_counter_vec, register_histogram_vec};
+use lazy_static::lazy_static;
+use async_trait::async_trait;
+use std::time::{Instant, Duration};
+
+// Copied from https://docs.rs/prometheus/0.12.0/src/prometheus/histogram.rs.html#885-889
+#[inline]
+fn duration_to_seconds(d: Duration) -> f64 {
+    let nanos = f64::from(d.subsec_nanos()) / 1e9;
+    d.as_secs() as f64 + nanos
+}
+
+lazy_static! {
+    static ref HTTP_CONNS_COUNTER: IntCounterVec = register_int_counter_vec!(
+        "http_requests_total", "Number of processed HTTP requests",
+        &["code", "method", "url"]
+    ).unwrap();
+
+    static ref HTTP_REQUEST_DURATION_HISTOGRAM: HistogramVec = register_histogram_vec!(
+        "http_request_duration_seconds", "Duration of HTTP requests",
+        &["code", "method", "url"]
+    ).unwrap();
+}
+
+pub struct InstrumentationMiddleware {}
+
+
+#[async_trait]
+impl<S> tide::Middleware<S> for InstrumentationMiddleware
+where
+    S: Send + Sync + Clone + 'static,
+{
+    async fn handle(
+        &self,
+        req: Request<S>,
+        next: Next<'_, S>,
+    ) -> Result {
+        let url = req.url().to_string();
+        let method = req.method().to_string();
+        // Execute the request
+        let instant = Instant::now();
+        let res = next.run(req).await;
+        let elapsed = duration_to_seconds(instant.elapsed());
+        // Get the code from the response
+        let code = res.status().to_string();
+
+        HTTP_CONNS_COUNTER.with_label_values(&[&code, &method, &url]).inc();
+        HTTP_REQUEST_DURATION_HISTOGRAM.with_label_values(&[&code, &method, &url]).observe(elapsed);
+
+        Ok(res)
+    }
+}
+
+pub async fn gather<S>(_: Request<S>) -> Result
+where
+    S: Send + Sync + Clone
+{
+    let mut buffer: Vec<u8> = vec![];
+    let encoder = TextEncoder::new();
+    let metric_families = prometheus::gather();
+    encoder.encode(&metric_families, &mut buffer).unwrap();
+
+    Ok(Response::builder(200).body(buffer).build())
+}