diff --git a/jive-api/src/main.rs b/jive-api/src/main.rs index 82c21bdb..3c03939b 100644 --- a/jive-api/src/main.rs +++ b/jive-api/src/main.rs @@ -24,6 +24,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; // 使用库中的模块 use jive_money_api::{handlers, services, ws}; +mod metrics; // 导入处理器 use handlers::accounts::*; @@ -278,6 +279,8 @@ async fn main() -> Result<(), Box> { "/api/v1/transactions/statistics", get(get_transaction_statistics), ) + // Metrics endpoint + .route("/metrics", get(metrics::metrics_handler)) // 收款人管理 API .route("/api/v1/payees", get(list_payees)) .route("/api/v1/payees", post(create_payee)) diff --git a/jive-api/src/metrics.rs b/jive-api/src/metrics.rs new file mode 100644 index 00000000..57bd88da --- /dev/null +++ b/jive-api/src/metrics.rs @@ -0,0 +1,65 @@ +use crate::AppState; +use axum::{http::StatusCode, response::IntoResponse}; +use sqlx::PgPool; + +// Produce Prometheus-style metrics text. +pub async fn metrics_handler( + axum::extract::State(state): axum::extract::State, +) -> impl IntoResponse { + let pool: &PgPool = &state.pool; + // Query hash distribution (best-effort) + let (b2a, b2b, b2y, a2id) = if let Ok(row) = sqlx::query( + "SELECT \ + COUNT(*) FILTER (WHERE password_hash LIKE '$2a$%') AS b2a,\ + COUNT(*) FILTER (WHERE password_hash LIKE '$2b$%') AS b2b,\ + COUNT(*) FILTER (WHERE password_hash LIKE '$2y$%') AS b2y,\ + COUNT(*) FILTER (WHERE password_hash LIKE '$argon2id$%') AS a2id\ + FROM users", + ) + .fetch_one(pool) + .await + { + use sqlx::Row; + ( + row.try_get::("b2a").unwrap_or(0), + row.try_get::("b2b").unwrap_or(0), + row.try_get::("b2y").unwrap_or(0), + row.try_get::("a2id").unwrap_or(0), + ) + } else { + (0, 0, 0, 0) + }; + + let rehash_count = state.metrics.get_rehash_count(); + let mut buf = String::new(); + buf.push_str("# HELP jive_password_rehash_total Total successful bcrypt to argon2id password rehashes.\n"); + buf.push_str("# TYPE jive_password_rehash_total counter\n"); + buf.push_str(&format!("jive_password_rehash_total {}\n", rehash_count)); + buf.push_str("# HELP jive_password_hash_users Users by password hash algorithm variant.\n"); + buf.push_str("# TYPE jive_password_hash_users gauge\n"); + buf.push_str(&format!( + "jive_password_hash_users{{algo=\"bcrypt_2a\"}} {}\n", + b2a + )); + buf.push_str(&format!( + "jive_password_hash_users{{algo=\"bcrypt_2b\"}} {}\n", + b2b + )); + buf.push_str(&format!( + "jive_password_hash_users{{algo=\"bcrypt_2y\"}} {}\n", + b2y + )); + buf.push_str(&format!( + "jive_password_hash_users{{algo=\"argon2id\"}} {}\n", + a2id + )); + + ( + StatusCode::OK, + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], + buf, + ) +} diff --git a/jive-api/target/.rustc_info.json b/jive-api/target/.rustc_info.json index 660295c8..2e254c01 100644 --- a/jive-api/target/.rustc_info.json +++ b/jive-api/target/.rustc_info.json @@ -1 +1 @@ -{"rustc_fingerprint":1863893085117187729,"outputs":{"18122065246313386177":{"success":true,"status":"","code":0,"stdout":"rustc 1.89.0 (29483883e 2025-08-04)\nbinary: rustc\ncommit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2\ncommit-date: 2025-08-04\nhost: aarch64-apple-darwin\nrelease: 1.89.0\nLLVM version: 20.1.7\n","stderr":""},"13007759520587589747":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/huazhou/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file +{"rustc_fingerprint":8876508001675379479,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.89.0 (29483883e 2025-08-04)\nbinary: rustc\ncommit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2\ncommit-date: 2025-08-04\nhost: aarch64-apple-darwin\nrelease: 1.89.0\nLLVM version: 20.1.7\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/huazhou/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/jive-api/target/release/jive-api b/jive-api/target/release/jive-api index 5473600b..2834273d 100755 Binary files a/jive-api/target/release/jive-api and b/jive-api/target/release/jive-api differ