#![allow(clippy::needless_question_mark)]
use armature_core::handler::handler;
use armature_core::*;
use armature_metrics::*;
use armature_proc_macro::use_middleware;
use std::sync::LazyLock;
static PAGE_VIEWS: LazyLock<Counter> = LazyLock::new(|| {
CounterBuilder::new("page_views_total", "Total page views")
.register()
.expect("failed to register page_views_total")
});
static ACTIVE_USERS: LazyLock<Gauge> = LazyLock::new(|| {
GaugeBuilder::new("active_users", "Number of active users")
.register()
.expect("failed to register active_users")
});
static API_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
HistogramBuilder::new("api_latency_seconds", "API call latency")
.latency_buckets()
.register()
.expect("failed to register api_latency_seconds")
});
#[use_middleware(RequestMetricsMiddleware::new())]
async fn home(_req: HttpRequest) -> Result<HttpResponse, Error> {
PAGE_VIEWS.inc();
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"message": "Metrics Example API",
"endpoints": {
"/": "Home",
"/api/users": "List users",
"/api/posts": "List posts",
"/metrics": "Prometheus metrics"
}
}))?)
}
#[use_middleware(RequestMetricsMiddleware::new())]
async fn list_users(_req: HttpRequest) -> Result<HttpResponse, Error> {
let start = std::time::Instant::now();
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
ACTIVE_USERS.inc();
let duration = start.elapsed().as_secs_f64();
API_LATENCY.observe(duration);
ACTIVE_USERS.dec();
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 3, "name": "Charlie"}
]
}))?)
}
#[use_middleware(RequestMetricsMiddleware::new())]
async fn list_posts(_req: HttpRequest) -> Result<HttpResponse, Error> {
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
Ok(HttpResponse::ok().with_json(&serde_json::json!({
"posts": [
{"id": 1, "title": "First Post"},
{"id": 2, "title": "Second Post"}
]
}))?)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = LogConfig::default().init();
info!("Prometheus Metrics Example");
info!("===========================");
info!("\nCreating custom metrics...");
LazyLock::force(&PAGE_VIEWS);
LazyLock::force(&ACTIVE_USERS);
LazyLock::force(&API_LATENCY);
info!("✓ Registered custom metrics");
let mut router = Router::new();
router.add_route(Route {
method: HttpMethod::GET,
path: "/".to_string(),
handler: handler(home),
constraints: None,
});
router.add_route(Route {
method: HttpMethod::GET,
path: "/api/users".to_string(),
handler: handler(list_users),
constraints: None,
});
router.add_route(Route {
method: HttpMethod::GET,
path: "/api/posts".to_string(),
handler: handler(list_posts),
constraints: None,
});
router.add_route(Route {
method: HttpMethod::GET,
path: "/metrics".to_string(),
handler: create_metrics_handler(),
constraints: None,
});
info!(
"\n✓ RequestMetricsMiddleware is wired onto every route above via #[use_middleware(...)]"
);
let container = Container::new();
let app = Application::new(container, router);
info!("\n✓ Server started on http://localhost:3000");
info!("\nAvailable endpoints:");
info!(" GET http://localhost:3000/");
info!(" GET http://localhost:3000/api/users");
info!(" GET http://localhost:3000/api/posts");
info!(" GET http://localhost:3000/metrics ← Prometheus metrics");
info!("\nMetrics being collected:");
info!(" - page_views_total (Counter)");
info!(" - active_users (Gauge)");
info!(" - api_latency_seconds (Histogram)");
info!(" - http_requests_total (Counter)");
info!(" - http_request_duration_seconds (Histogram)");
info!(" - http_requests_in_flight (Gauge)");
info!(" - http_request_size_bytes (Histogram)");
info!(" - http_response_size_bytes (Histogram)");
info!("\nPress Ctrl+C to stop");
app.listen(3000).await?;
Ok(())
}