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::MySqlPool;
6use std::sync::OnceLock;
7
8static DEV_POOL: OnceLock<MySqlPool> = OnceLock::new();
9
10/// dev 启动时注册 DB 连接池
11pub fn init_pool(pool: MySqlPool) {
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: MySqlPool,
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(
66    pool: MySqlPool,
67    kind: &str,
68    code: i32,
69    msg: &str,
70    detail: Option<&str>,
71) {
72    let text = match detail.filter(|d| !d.is_empty()) {
73        Some(d) => format!("{msg} | {d}"),
74        None => msg.to_string(),
75    };
76    let row = DevErrorInsert {
77        kind: static_kind(kind),
78        method: None,
79        path: None,
80        status: None,
81        code: Some(code),
82        msg: truncate(&text, 512),
83        member_id: None,
84        otel_trace_id: current_trace_id(),
85    };
86    spawn_insert(pool, row);
87}
88
89fn static_kind(kind: &str) -> &'static str {
90    match kind {
91        "business" => "business",
92        "login_expired" => "login_expired",
93        "oss" => "oss",
94        "internal" => "internal",
95        "download_unauth" => "download_unauth",
96        "download_failed" => "download_failed",
97        _ => "app_error",
98    }
99}
100
101fn truncate(s: &str, max: usize) -> String {
102    if s.len() <= max {
103        s.to_string()
104    } else {
105        format!("{}...", &s[..max])
106    }
107}
108
109fn spawn_insert(pool: MySqlPool, row: DevErrorInsert) {
110    tokio::spawn(async move {
111        let _ = error_repo::insert(&pool, row).await;
112    });
113}