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
3 changes: 3 additions & 0 deletions jive-api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

// 使用库中的模块
use jive_money_api::{handlers, services, ws};
mod metrics;

// 导入处理器
use handlers::accounts::*;
Expand Down Expand Up @@ -278,6 +279,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"/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))
Expand Down
65 changes: 65 additions & 0 deletions jive-api/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -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<AppState>,
) -> impl IntoResponse {
let pool: &PgPool = &state.pool;
// Query hash distribution (best-effort)
let (b2a, b2b, b2y, a2id) = if let Ok(row) = sqlx::query(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for querying the password hash distribution (lines 9-24) is identical to the one in the health_check function in main.rs. Duplicating code makes maintenance harder and increases the chance of bugs (like an issue I've pointed out separately). Consider extracting this logic into a shared function, for example as a new method on AppState or in a database utility module, to improve maintainability.

"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::<i64, _>("b2a").unwrap_or(0),
row.try_get::<i64, _>("b2b").unwrap_or(0),
row.try_get::<i64, _>("b2y").unwrap_or(0),
row.try_get::<i64, _>("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,
)
}
2 changes: 1 addition & 1 deletion jive-api/target/.rustc_info.json
Original file line number Diff line number Diff line change
@@ -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":{}}
{"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":{}}
Binary file modified jive-api/target/release/jive-api
Binary file not shown.
Loading