1#![forbid(unsafe_code)]
9
10use std::sync::OnceLock;
11
12use axum::{routing::get, Router};
13use prometheus::{Encoder, Registry, TextEncoder};
14use tracing_subscriber::{fmt, prelude::*, EnvFilter};
15
16pub use prometheus;
17pub use tracing;
18
19pub fn registry() -> &'static Registry {
21 static R: OnceLock<Registry> = OnceLock::new();
22 R.get_or_init(Registry::new)
23}
24
25pub fn encode_metrics() -> Result<Vec<u8>, prometheus::Error> {
27 let encoder = TextEncoder::new();
28 let mut buf = Vec::with_capacity(8192);
29 encoder.encode(®istry().gather(), &mut buf)?;
30 Ok(buf)
31}
32
33pub fn init(service: &'static str) {
38 static INIT: OnceLock<()> = OnceLock::new();
39 INIT.get_or_init(|| {
40 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
41 let json = fmt::layer()
42 .json()
43 .with_current_span(true)
44 .with_span_list(false)
45 .with_target(true);
46
47 tracing_subscriber::registry()
48 .with(filter)
49 .with(json)
50 .try_init()
51 .ok();
52
53 tracing::info!(
54 service = %service,
55 version = %cgn_core::build::VERSION,
56 "telemetry initialised"
57 );
58 });
59}
60
61pub fn admin_router() -> Router {
65 Router::new()
66 .route("/metrics", get(metrics_handler))
67 .route("/healthz", get(|| async { "ok" }))
68 .route("/readyz", get(|| async { "ok" }))
69}
70
71async fn metrics_handler() -> axum::response::Response {
72 use axum::http::{header, StatusCode};
73 use axum::response::IntoResponse;
74 match encode_metrics() {
75 Ok(body) => (
76 StatusCode::OK,
77 [(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
78 body,
79 )
80 .into_response(),
81 Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("metrics: {e}")).into_response(),
82 }
83}
84
85#[macro_export]
87macro_rules! counter {
88 ($name:expr, $help:expr) => {{
89 let c = $crate::prometheus::IntCounter::new($name, $help).expect("counter create");
90 $crate::registry().register(Box::new(c.clone())).ok();
91 c
92 }};
93}
94
95#[macro_export]
97macro_rules! gauge {
98 ($name:expr, $help:expr) => {{
99 let g = $crate::prometheus::IntGauge::new($name, $help).expect("gauge create");
100 $crate::registry().register(Box::new(g.clone())).ok();
101 g
102 }};
103}
104
105#[macro_export]
108macro_rules! latency_histogram {
109 ($name:expr, $help:expr) => {{
110 let opts = $crate::prometheus::HistogramOpts::new($name, $help).buckets(vec![
111 0.00005, 0.0001, 0.0005, 0.001, 0.003, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0,
112 2.0,
113 ]);
114 let h = $crate::prometheus::Histogram::with_opts(opts).expect("histogram create");
115 $crate::registry().register(Box::new(h.clone())).ok();
116 h
117 }};
118}