feat: add explicit error logging and startup validation

- Add DATABASE_URL validation on server startup (exit early with clear error)
- Add tracing::error! to all server functions in api/auth.rs
- Bulk add tracing::error! to all database error handlers in api/posts.rs
- Server 500 errors now log detailed context to terminal
This commit is contained in:
xfy 2026-06-01 17:57:26 +08:00
parent b6cabe489f
commit 5695b1c62b
3 changed files with 198 additions and 48 deletions

View File

@ -91,12 +91,18 @@ pub async fn register(
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Register DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let admin_count: i64 = client
.query_one("SELECT COUNT(*) FROM users WHERE role = 'admin'", &[])
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?
.map_err(|e| {
tracing::error!("Register admin count query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?
.get(0);
if admin_count > 0 {
@ -108,7 +114,10 @@ pub async fn register(
}
let password_hash = password::hash_password(&password)
.map_err(|e| ServerFnError::new(format!("密码哈希失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Register password hash failed: {}", e);
ServerFnError::new(format!("密码哈希失败: {}", e))
})?;
let result = client
.query_one(
@ -146,7 +155,10 @@ pub async fn login(
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Login DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let row = match client
.query_opt(
@ -163,12 +175,18 @@ pub async fn login(
token: None,
});
}
Err(e) => return Err(ServerFnError::new(format!("查询失败: {}", e))),
Err(e) => {
tracing::error!("Login user query failed: {}", e);
return Err(ServerFnError::new(format!("查询失败: {}", e)));
}
};
let password_hash: String = row.get("password_hash");
let valid = password::verify_password(&password, &password_hash)
.map_err(|e| ServerFnError::new(format!("密码验证失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Login password verify failed: {}", e);
ServerFnError::new(format!("密码验证失败: {}", e))
})?;
if !valid {
return Ok(AuthResponse {
@ -188,7 +206,10 @@ pub async fn login(
&[&user_id, &token, &expires_at],
)
.await
.map_err(|e| ServerFnError::new(format!("创建 session 失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Login session insert failed: {}", e);
ServerFnError::new(format!("创建 session 失败: {}", e))
})?;
let cookie = format!(
"session={token}; HttpOnly; Path=/; Max-Age={}; SameSite=Lax",
@ -224,7 +245,10 @@ pub async fn logout() -> Result<AuthResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Logout DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
// 清除 cookie
if let Some(ctx) = dioxus::fullstack::FullstackContext::current() {
@ -241,7 +265,10 @@ pub async fn logout() -> Result<AuthResponse, ServerFnError> {
client
.execute("DELETE FROM sessions WHERE token = $1", &[&t])
.await
.map_err(|e| ServerFnError::new(format!("删除 session 失败: {}", e)))?;
.map_err(|e| {
tracing::error!("Logout session delete failed: {}", e);
ServerFnError::new(format!("删除 session 失败: {}", e))
})?;
}
Ok(AuthResponse {
@ -277,7 +304,10 @@ pub async fn get_current_user() -> Result<CurrentUserResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("GetCurrentUser DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let row = client
.query_opt(
@ -288,7 +318,10 @@ pub async fn get_current_user() -> Result<CurrentUserResponse, ServerFnError> {
&[&token],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("GetCurrentUser session query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let user = match row {
Some(row) => {

View File

@ -48,7 +48,10 @@ async fn get_current_admin_user() -> Result<User, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let row = client
.query_opt(
@ -59,7 +62,10 @@ async fn get_current_admin_user() -> Result<User, ServerFnError> {
&[&token],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let user = match row {
Some(row) => {
@ -221,7 +227,10 @@ async fn set_post_tags(
client
.execute("DELETE FROM post_tags WHERE post_id = $1", &[&post_id])
.await
.map_err(|e| ServerFnError::new(format!("删除标签关联失败: {}", e)))?;
.map_err(|e| {
tracing::error!("delete tag links failed: {}", e);
ServerFnError::new(format!("删除标签关联失败: {}", e))
})?;
for tag_name in tags {
let tag_name = tag_name.trim();
@ -237,7 +246,10 @@ async fn set_post_tags(
&[&tag_name],
)
.await
.map_err(|e| ServerFnError::new(format!("创建标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("create tag failed: {}", e);
ServerFnError::new(format!("创建标签失败: {}", e))
})?;
match row {
Some(r) => r.get(0),
@ -246,7 +258,10 @@ async fn set_post_tags(
let row = client
.query_opt("SELECT id FROM tags WHERE name = $1", &[&tag_name])
.await
.map_err(|e| ServerFnError::new(format!("查询标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query tag failed: {}", e);
ServerFnError::new(format!("查询标签失败: {}", e))
})?;
row.map(|r| r.get(0))
.ok_or_else(|| ServerFnError::new(format!("标签不存在: {}", tag_name)))?
}
@ -259,7 +274,10 @@ async fn set_post_tags(
&[&post_id, &tag_id],
)
.await
.map_err(|e| ServerFnError::new(format!("关联标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("link tag failed: {}", e);
ServerFnError::new(format!("关联标签失败: {}", e))
})?;
}
Ok(())
@ -401,7 +419,10 @@ pub async fn create_post(
let mut client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let final_slug = ensure_unique_slug(&client, &base_slug, None).await?;
let content_html = render_markdown(&content_md);
@ -417,7 +438,10 @@ pub async fn create_post(
let tx = client
.transaction()
.await
.map_err(|e| ServerFnError::new(format!("事务开始失败: {}", e)))?;
.map_err(|e| {
tracing::error!("transaction start failed: {}", e);
ServerFnError::new(format!("事务开始失败: {}", e))
})?;
let row = tx
.query_one(
@ -436,7 +460,10 @@ pub async fn create_post(
],
)
.await
.map_err(|e| ServerFnError::new(format!("创建文章失败: {}", e)))?;
.map_err(|e| {
tracing::error!("create post failed: {}", e);
ServerFnError::new(format!("创建文章失败: {}", e))
})?;
let post_id: i32 = row.get(0);
@ -458,7 +485,10 @@ pub async fn create_post(
&[&tag_name.as_str()],
)
.await
.map_err(|e| ServerFnError::new(format!("创建标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("create tag failed: {}", e);
ServerFnError::new(format!("创建标签失败: {}", e))
})?;
match row {
Some(r) => r.get(0),
@ -466,7 +496,10 @@ pub async fn create_post(
let row = tx
.query_opt("SELECT id FROM tags WHERE name = $1", &[&tag_name.as_str()])
.await
.map_err(|e| ServerFnError::new(format!("查询标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query tag failed: {}", e);
ServerFnError::new(format!("查询标签失败: {}", e))
})?;
row.map(|r| r.get(0))
.ok_or_else(|| ServerFnError::new(format!("标签不存在: {}", tag_name)))?
}
@ -478,13 +511,19 @@ pub async fn create_post(
&[&post_id, &tag_id],
)
.await
.map_err(|e| ServerFnError::new(format!("关联标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("link tag failed: {}", e);
ServerFnError::new(format!("关联标签失败: {}", e))
})?;
}
}
tx.commit()
.await
.map_err(|e| ServerFnError::new(format!("事务提交失败: {}", e)))?;
.map_err(|e| {
tracing::error!("transaction commit failed: {}", e);
ServerFnError::new(format!("事务提交失败: {}", e))
})?;
Ok(CreatePostResponse {
success: true,
@ -509,7 +548,10 @@ pub async fn update_post(
let mut client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
// Verify ownership
let exists: bool = client
@ -554,7 +596,10 @@ pub async fn update_post(
let tx = client
.transaction()
.await
.map_err(|e| ServerFnError::new(format!("事务开始失败: {}", e)))?;
.map_err(|e| {
tracing::error!("transaction start failed: {}", e);
ServerFnError::new(format!("事务开始失败: {}", e))
})?;
// Check if status changed to published and was not published before
let old_status_row = tx
@ -563,7 +608,10 @@ pub async fn update_post(
&[&post_id],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let published_at = if post_status == PostStatus::Published {
let was_published = old_status_row
@ -600,7 +648,10 @@ pub async fn update_post(
],
)
.await
.map_err(|e| ServerFnError::new(format!("更新文章失败: {}", e)))?;
.map_err(|e| {
tracing::error!("update post failed: {}", e);
ServerFnError::new(format!("更新文章失败: {}", e))
})?;
// Update tags
let tags_cleaned: Vec<String> = tags
@ -611,7 +662,10 @@ pub async fn update_post(
tx.execute("DELETE FROM post_tags WHERE post_id = $1", &[&post_id])
.await
.map_err(|e| ServerFnError::new(format!("删除旧标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("delete old tags failed: {}", e);
ServerFnError::new(format!("删除旧标签失败: {}", e))
})?;
for tag_name in &tags_cleaned {
let tag_id: i32 = {
@ -621,7 +675,10 @@ pub async fn update_post(
&[&tag_name.as_str()],
)
.await
.map_err(|e| ServerFnError::new(format!("创建标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("create tag failed: {}", e);
ServerFnError::new(format!("创建标签失败: {}", e))
})?;
match row {
Some(r) => r.get(0),
@ -629,7 +686,10 @@ pub async fn update_post(
let row = tx
.query_opt("SELECT id FROM tags WHERE name = $1", &[&tag_name.as_str()])
.await
.map_err(|e| ServerFnError::new(format!("查询标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query tag failed: {}", e);
ServerFnError::new(format!("查询标签失败: {}", e))
})?;
row.map(|r| r.get(0))
.ok_or_else(|| ServerFnError::new(format!("标签不存在: {}", tag_name)))?
}
@ -641,12 +701,18 @@ pub async fn update_post(
&[&post_id, &tag_id],
)
.await
.map_err(|e| ServerFnError::new(format!("关联标签失败: {}", e)))?;
.map_err(|e| {
tracing::error!("link tag failed: {}", e);
ServerFnError::new(format!("关联标签失败: {}", e))
})?;
}
tx.commit()
.await
.map_err(|e| ServerFnError::new(format!("事务提交失败: {}", e)))?;
.map_err(|e| {
tracing::error!("transaction commit failed: {}", e);
ServerFnError::new(format!("事务提交失败: {}", e))
})?;
Ok(CreatePostResponse {
success: true,
@ -661,7 +727,10 @@ pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, Server
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let row = client
.query_opt(
@ -671,7 +740,10 @@ pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, Server
&[&slug],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let post = match row {
Some(row) => Some(row_to_post(&client, &row).await),
@ -686,7 +758,10 @@ pub async fn list_published_posts() -> Result<PostListResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let rows = client
.query(
@ -697,7 +772,10 @@ pub async fn list_published_posts() -> Result<PostListResponse, ServerFnError> {
&[],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let mut posts = Vec::new();
for row in &rows {
@ -714,7 +792,10 @@ pub async fn list_posts() -> Result<PostListResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let rows = client
.query(
@ -725,7 +806,10 @@ pub async fn list_posts() -> Result<PostListResponse, ServerFnError> {
&[],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let mut posts = Vec::new();
for row in &rows {
@ -742,7 +826,10 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let result = client
.execute(
@ -750,7 +837,10 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
&[&post_id],
)
.await
.map_err(|e| ServerFnError::new(format!("删除失败: {}", e)))?;
.map_err(|e| {
tracing::error!("delete failed: {}", e);
ServerFnError::new(format!("删除失败: {}", e))
})?;
if result == 0 {
return Ok(CreatePostResponse {
@ -774,7 +864,10 @@ pub async fn list_tags() -> Result<TagListResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let rows = client
.query(
@ -787,7 +880,10 @@ pub async fn list_tags() -> Result<TagListResponse, ServerFnError> {
&[],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let tags: Vec<Tag> = rows
.iter()
@ -806,7 +902,10 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let rows = client
.query(
@ -819,7 +918,10 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
&[&tag_name],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let mut posts = Vec::new();
for row in &rows {
@ -836,7 +938,10 @@ pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let total: i64 = client
.query_one("SELECT COUNT(*) FROM posts WHERE deleted_at IS NULL", &[])
@ -876,7 +981,10 @@ pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnErr
let client = DB_POOL
.get()
.await
.map_err(|e| ServerFnError::new(format!("数据库连接失败: {}", e)))?;
.map_err(|e| {
tracing::error!("DB connection failed: {}", e);
ServerFnError::new(format!("数据库连接失败: {}", e))
})?;
let search_pattern = format!("%{}%", query);
@ -890,7 +998,10 @@ pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnErr
&[&search_pattern],
)
.await
.map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?;
.map_err(|e| {
tracing::error!("query failed: {}", e);
ServerFnError::new(format!("查询失败: {}", e))
})?;
let mut posts = Vec::new();
for row in &rows {

View File

@ -20,6 +20,12 @@ fn main() {
)
.init();
if std::env::var("DATABASE_URL").is_err() {
tracing::error!("DATABASE_URL environment variable not set. Make sure .env exists or the variable is exported.");
eprintln!("ERROR: DATABASE_URL environment variable not set");
std::process::exit(1);
}
dioxus::server::serve(|| async move {
use dioxus::server::{axum, DioxusRouterExt, ServeConfig};
use tower_http::trace::TraceLayer;