Skip to main content

tdm_server_rust/dev/
error_log.rs

1//! dev 错误异步落库(含 trace_id)
2
3use crate::dev::error_repo::{self, DevErrorInsert};
4use crate::telemetry::skywalking;
5use sqlx::PgPool;
6use std::sync::OnceLock;
7
8static DEV_POOL: OnceLock<PgPool> = OnceLock::new();
9
10/// dev 启动时注册 DB 连接池
11pub fn init_pool(pool: PgPool) {
12    let _ = DEV_POOL.set(pool);
13}
14
15/// 读取当前 SW trace_id
16pub fn current_trace_id() -> Option<String> {
17    skywalking::current_trace_id()
18}
19
20/// 异步写入 HTTP 错误到 MySQL(dev only)
21pub fn try_persist_http_error(
22    method: &str,
23    path: &str,
24    status: u16,
25    body: &str,
26    member_id: Option<i32>,
27) {
28    let Some(pool) = DEV_POOL.get().cloned() else {
29        return;
30    };
31    persist_http_error(pool, method, path, status, body, member_id);
32}
33
34/// 异步写入 AppError 到 MySQL(dev only)
35pub fn try_persist_app_error(kind: &str, code: i32, msg: &str, detail: Option<&str>) {
36    let Some(pool) = DEV_POOL.get().cloned() else {
37        return;
38    };
39    persist_app_error(pool, kind, code, msg, detail);
40}
41
42/// 异步写入 HTTP 错误到 MySQL
43fn persist_http_error(
44    pool: PgPool,
45    method: &str,
46    path: &str,
47    status: u16,
48    body: &str,
49    member_id: Option<i32>,
50) {
51    let row = DevErrorInsert {
52        kind: "http_error",
53        method: Some(method.to_string()),
54        path: Some(path.to_string()),
55        status: Some(status as i16),
56        code: None,
57        msg: truncate(body, 512),
58        member_id,
59        otel_trace_id: current_trace_id(),
60    };
61    spawn_insert(pool, row);
62}
63
64/// 异步写入 AppError 到 MySQL
65fn persist_app_error(pool: PgPool, kind: &str, code: i32, msg: &str, detail: Option<&str>) {
66    let text = match detail.filter(|d| !d.is_empty()) {
67        Some(d) => format!("{msg} | {d}"),
68        None => msg.to_string(),
69    };
70    let row = DevErrorInsert {
71        kind: static_kind(kind),
72        method: None,
73        path: None,
74        status: None,
75        code: Some(code),
76        msg: truncate(&text, 512),
77        member_id: None,
78        otel_trace_id: current_trace_id(),
79    };
80    spawn_insert(pool, row);
81}
82
83fn static_kind(kind: &str) -> &'static str {
84    match kind {
85        "business" => "business",
86        "login_expired" => "login_expired",
87        "oss" => "oss",
88        "internal" => "internal",
89        "download_unauth" => "download_unauth",
90        "download_failed" => "download_failed",
91        _ => "app_error",
92    }
93}
94
95fn truncate(s: &str, max: usize) -> String {
96    if s.len() <= max {
97        s.to_string()
98    } else {
99        format!("{}...", &s[..max])
100    }
101}
102
103fn spawn_insert(pool: PgPool, row: DevErrorInsert) {
104    tokio::spawn(async move {
105        let _ = error_repo::insert(&pool, row).await;
106    });
107}