From 5695b1c62b3940c54b47a71e228a9926d3bb455b Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 1 Jun 2026 17:57:26 +0800 Subject: [PATCH] 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 --- src/api/auth.rs | 55 +++++++++++--- src/api/posts.rs | 185 +++++++++++++++++++++++++++++++++++++---------- src/main.rs | 6 ++ 3 files changed, 198 insertions(+), 48 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index efcb751..0548d95 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -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 { 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 { 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 { 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 { &[&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) => { diff --git a/src/api/posts.rs b/src/api/posts.rs index c6c95bc..e584ed4 100644 --- a/src/api/posts.rs +++ b/src/api/posts.rs @@ -48,7 +48,10 @@ async fn get_current_admin_user() -> Result { 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 { &[&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 = 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 Result Some(row_to_post(&client, &row).await), @@ -686,7 +758,10 @@ pub async fn list_published_posts() -> Result { 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 { &[], ) .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 { 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 { &[], ) .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 Result Result { 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 { &[], ) .await - .map_err(|e| ServerFnError::new(format!("查询失败: {}", e)))?; + .map_err(|e| { + tracing::error!("query failed: {}", e); + ServerFnError::new(format!("查询失败: {}", e)) + })?; let tags: Vec = rows .iter() @@ -806,7 +902,10 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result Result Result { 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 Result