Skip to content

Commit 177bece

Browse files
committed
Add automatic status page with metrics dashboard
Introduces a built-in status page to RustAPI, providing real-time metrics for all endpoints including request counts, success rates, average latency, and last access time. Adds `status_page()` and `status_page_with_config()` methods to `RustApi` for easy enablement and customization. Includes a new middleware layer for tracking metrics, a self-contained HTML dashboard, tests, an example, and documentation in the cookbook.
1 parent 9852324 commit 177bece

File tree

11 files changed

+630
-19
lines changed

11 files changed

+630
-19
lines changed

Cargo.lock

Lines changed: 17 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ members = [
2121
]
2222

2323
[workspace.package]
24-
version = "0.1.194"
24+
version = "0.1.195"
2525
edition = "2021"
2626
authors = ["RustAPI Contributors"]
2727
license = "MIT OR Apache-2.0"
@@ -120,3 +120,4 @@ rustls-pemfile = "2.2"
120120
rcgen = "0.13"
121121

122122

123+

crates/rustapi-core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ h3-quinn = { version = "0.0.10", optional = true }
7676
rustls = { workspace = true, optional = true }
7777
rustls-pemfile = { workspace = true, optional = true }
7878
rcgen = { workspace = true, optional = true }
79+
chrono = "0.4.43"
7980

8081
[dev-dependencies]
8182
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
@@ -98,3 +99,4 @@ http3 = ["dep:quinn", "dep:h3", "dep:h3-quinn", "dep:rustls", "dep:rustls-pemfil
9899
http3-dev = ["http3", "dep:rcgen"]
99100

100101

102+

crates/rustapi-core/src/app.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct RustApi {
3434
interceptors: InterceptorChain,
3535
#[cfg(feature = "http3")]
3636
http3_config: Option<crate::http3::Http3Config>,
37+
status_config: Option<crate::status::StatusConfig>,
3738
}
3839

3940
impl RustApi {
@@ -61,6 +62,7 @@ impl RustApi {
6162
interceptors: InterceptorChain::new(),
6263
#[cfg(feature = "http3")]
6364
http3_config: None,
65+
status_config: None,
6466
}
6567
}
6668

@@ -869,6 +871,54 @@ impl RustApi {
869871
.route(path, docs_router)
870872
}
871873

874+
/// Enable automatic status page with default configuration
875+
pub fn status_page(self) -> Self {
876+
self.status_page_with_config(crate::status::StatusConfig::default())
877+
}
878+
879+
/// Enable automatic status page with custom configuration
880+
pub fn status_page_with_config(mut self, config: crate::status::StatusConfig) -> Self {
881+
self.status_config = Some(config);
882+
self
883+
}
884+
885+
// Helper to apply status page logic (monitor, layer, route)
886+
fn apply_status_page(&mut self) {
887+
if let Some(config) = &self.status_config {
888+
let monitor = std::sync::Arc::new(crate::status::StatusMonitor::new());
889+
890+
// 1. Add middleware layer
891+
self.layers
892+
.push(Box::new(crate::status::StatusLayer::new(monitor.clone())));
893+
894+
// 2. Add status route
895+
use crate::router::MethodRouter;
896+
use std::collections::HashMap;
897+
898+
let monitor = monitor.clone();
899+
let config = config.clone();
900+
let path = config.path.clone(); // Clone path before moving config
901+
902+
let handler: crate::handler::BoxedHandler = std::sync::Arc::new(move |_| {
903+
let monitor = monitor.clone();
904+
let config = config.clone();
905+
Box::pin(async move {
906+
crate::status::status_handler(monitor, config)
907+
.await
908+
.into_response()
909+
})
910+
});
911+
912+
let mut handlers = HashMap::new();
913+
handlers.insert(http::Method::GET, handler);
914+
let method_router = MethodRouter::from_boxed(handlers);
915+
916+
// We need to take the router out to call route() which consumes it
917+
let router = std::mem::replace(&mut self.router, crate::router::Router::new());
918+
self.router = router.route(&path, method_router);
919+
}
920+
}
921+
872922
/// Run the server
873923
///
874924
/// # Example
@@ -880,6 +930,9 @@ impl RustApi {
880930
/// .await
881931
/// ```
882932
pub async fn run(mut self, addr: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
933+
// Apply status page if configured
934+
self.apply_status_page();
935+
883936
// Apply body limit layer if configured (should be first in the chain)
884937
if let Some(limit) = self.body_limit {
885938
// Prepend body limit layer so it's the first to process requests
@@ -899,9 +952,10 @@ impl RustApi {
899952
where
900953
F: std::future::Future<Output = ()> + Send + 'static,
901954
{
902-
// Apply body limit layer if configured (should be first in the chain)
955+
// Apply status page if configured
956+
self.apply_status_page();
957+
903958
if let Some(limit) = self.body_limit {
904-
// Prepend body limit layer so it's the first to process requests
905959
self.layers.prepend(Box::new(BodyLimitLayer::new(limit)));
906960
}
907961

@@ -944,6 +998,9 @@ impl RustApi {
944998
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
945999
use std::sync::Arc;
9461000

1001+
// Apply status page if configured
1002+
self.apply_status_page();
1003+
9471004
// Apply body limit layer if configured
9481005
if let Some(limit) = self.body_limit {
9491006
self.layers.prepend(Box::new(BodyLimitLayer::new(limit)));
@@ -980,6 +1037,9 @@ impl RustApi {
9801037
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
9811038
use std::sync::Arc;
9821039

1040+
// Apply status page if configured
1041+
self.apply_status_page();
1042+
9831043
// Apply body limit layer if configured
9841044
if let Some(limit) = self.body_limit {
9851045
self.layers.prepend(Box::new(BodyLimitLayer::new(limit)));

crates/rustapi-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ mod router;
7474
mod server;
7575
pub mod sse;
7676
pub mod static_files;
77+
pub mod status;
7778
pub mod stream;
7879
pub mod typed_path;
7980
pub mod validation;

0 commit comments

Comments
 (0)