Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 101 additions & 4 deletions crates/aisix-admin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ pub fn build_router(state: AdminState) -> Router {
// subsequent handler call is a free lookup.
let _ = openapi::merged_openapi();

Router::new()
let mut router = Router::new()
.route("/livez", get(livez))
.route("/metrics", get(metrics_handler))
// OpenAPI scalar UI is unauthenticated like /metrics — admin
// listener is private in production.
.route("/admin/openapi.json", get(openapi::openapi_json))
Expand Down Expand Up @@ -150,8 +149,28 @@ pub fn build_router(state: AdminState) -> Router {
.route(
"/playground/chat/completions",
post(playground_handler::playground_chat_completions),
)
.with_state(state)
);

if state.prometheus.enabled {
router = router.route(
&normalized_prometheus_path(&state.prometheus.path),
get(metrics_handler),
);
}
Comment thread
jarvis9443 marked this conversation as resolved.

router.with_state(state)
}

fn normalized_prometheus_path(path: &str) -> String {
let path = path.trim();
if path.is_empty() {
return "/metrics".to_string();
}
if path.starts_with('/') {
path.to_string()
} else {
format!("/{path}")
}
}

async fn livez(
Expand Down Expand Up @@ -335,6 +354,84 @@ mod tests {
assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn metrics_endpoint_uses_configured_path() {
use aisix_core::config::PrometheusConfig;
use aisix_obs::Metrics;

let state = build_state()
.with_metrics(Arc::new(Metrics::new(false)))
.with_prometheus_config(PrometheusConfig {
enabled: true,
path: "/internal/prom".into(),
});
let app = build_router(state);

let resp = run(
app.clone(),
Request::builder()
.uri("/metrics")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);

let resp = run(
app,
Request::builder()
.uri("/internal/prom")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
}

#[tokio::test]
async fn metrics_endpoint_normalizes_configured_path() {
use aisix_core::config::PrometheusConfig;
use aisix_obs::Metrics;

let state = build_state()
.with_metrics(Arc::new(Metrics::new(false)))
.with_prometheus_config(PrometheusConfig {
enabled: true,
path: "internal/prom".into(),
});
let app = build_router(state);

let resp = run(
app,
Request::builder()
.uri("/internal/prom")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(resp.status(), StatusCode::OK);
}

#[tokio::test]
async fn metrics_endpoint_can_be_disabled() {
use aisix_core::config::PrometheusConfig;
use aisix_obs::Metrics;

let state = build_state()
.with_metrics(Arc::new(Metrics::new(false)))
.with_prometheus_config(PrometheusConfig {
enabled: false,
path: "/metrics".into(),
});
let app = build_router(state);
let req = Request::builder()
.uri("/metrics")
.body(Body::empty())
.unwrap();
let resp = run(app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn livez_reports_plain_ok_by_default() {
let app = build_router(build_state());
Expand Down
8 changes: 8 additions & 0 deletions crates/aisix-admin/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//! wire an etcd-backed impl and tests can use `InMemoryStore` via the
//! same type.

use aisix_core::config::PrometheusConfig;
use aisix_core::snapshot::SnapshotHandle;
use aisix_core::{AdminConfig, AisixSnapshot};
use aisix_etcd::WatchStatus;
Expand All @@ -27,6 +28,7 @@ pub struct AdminState {
pub admin_keys: Arc<[String]>,
pub store: Arc<dyn ConfigStore>,
pub metrics: Option<Arc<Metrics>>,
pub prometheus: PrometheusConfig,
/// Shared in-process health tracker from the proxy. Used by the
/// `/admin/v1/health` endpoint to report per-model health status.
pub health_tracker: Option<Arc<HealthTracker>>,
Expand Down Expand Up @@ -59,6 +61,7 @@ impl AdminState {
admin_keys: Arc::from(cfg.admin_keys.clone()),
store,
metrics: None,
prometheus: PrometheusConfig::default(),
health_tracker: None,
runtime_status_tracker: None,
watch_status: None,
Expand All @@ -83,6 +86,11 @@ impl AdminState {
self
}

pub fn with_prometheus_config(mut self, prometheus: PrometheusConfig) -> Self {
self.prometheus = prometheus;
self
}

/// Attach the in-process health tracker from the proxy. When set,
/// `GET /admin/v1/health` reflects per-model upstream health.
pub fn with_health_tracker(mut self, tracker: Arc<HealthTracker>) -> Self {
Expand Down
5 changes: 4 additions & 1 deletion crates/aisix-obs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ use aisix_core::ObservabilityConfig;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

pub use access_log::AccessLog;
pub use metrics::{Metrics, RequestOutcome};
pub use metrics::{
BudgetGauges, BudgetLabels, DeploymentLabels, DeploymentState, LlmUsage, Metrics,
RequestLabels, RequestOutcome, UsageLabels,
};
pub use otlp::{install_otlp_tracer, shutdown_otlp, OtlpError, OtlpHandle};
pub use otlp_http_sink::OtlpHttpFanOut;
pub use usage::{UsageEvent, UsageSink};
Expand Down
Loading
Loading