tdm_server_rust/utils/
error_log.rs1use crate::dev::error_log as dev_error_log;
19use std::fs::OpenOptions;
20use std::io::Write;
21use std::path::PathBuf;
22use std::sync::{OnceLock, RwLock};
23
24const MAX_BODY_LEN: usize = 512;
26
27struct ErrorLogRuntime {
29 error_log_path: PathBuf,
31}
32
33static RUNTIME: OnceLock<RwLock<ErrorLogRuntime>> = OnceLock::new();
34
35pub fn init(error_log_path: PathBuf) {
43 if RUNTIME.get().is_some() {
44 return;
45 }
46 ensure_log_file(&error_log_path);
47 let _ = RUNTIME.set(RwLock::new(ErrorLogRuntime { error_log_path }));
48}
49
50fn ensure_log_file(path: &PathBuf) {
52 if let Some(parent) = path.parent() {
53 let _ = std::fs::create_dir_all(parent);
54 }
55 let _ = OpenOptions::new()
56 .create(true)
57 .append(true)
58 .open(path);
59}
60
61fn runtime() -> Option<&'static RwLock<ErrorLogRuntime>> {
62 RUNTIME.get()
63}
64
65fn fallback_error_log_path() -> PathBuf {
67 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
68 .join("logs")
69 .join("error.log")
70}
71
72fn truncate(text: &str, max: usize) -> String {
74 if text.len() <= max {
75 return text.to_string();
76 }
77 format!("{}...(len={})", &text[..max], text.len())
78}
79
80fn append_line(path: &PathBuf, line: &str) {
82 if let Some(parent) = path.parent() {
83 let _ = std::fs::create_dir_all(parent);
84 }
85 if let Ok(mut file) = OpenOptions::new()
86 .create(true)
87 .append(true)
88 .open(path)
89 {
90 let _ = writeln!(file, "{line}");
91 }
92}
93
94pub fn log_http_error(
106 method: &str,
107 path: &str,
108 status: u16,
109 body: &str,
110 member_id: Option<i32>,
111) {
112 let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
113 let member = member_id
114 .map(|id| id.to_string())
115 .unwrap_or_else(|| "-".to_string());
116 let line = format!(
117 "[{ts}] HTTP {method} {path} status={status} member={member} body={}",
118 truncate(body, MAX_BODY_LEN)
119 );
120
121 if let Some(rt) = runtime() {
122 if let Ok(guard) = rt.read() {
123 append_line(&guard.error_log_path, &line);
124 }
125 } else {
126 append_line(&fallback_error_log_path(), &line);
127 }
128 dev_error_log::try_persist_http_error(method, path, status, body, member_id);
129 crate::telemetry::log_error_event(&format!(
130 "HTTP {method} {path} status={status}"
131 ));
132 tracing::warn!("{line}");
133}
134
135pub fn log_app_error(kind: &str, code: i32, msg: &str, detail: Option<&str>) {
146 let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
147 let extra = detail.unwrap_or("");
148 let line = format!(
149 "[{ts}] APP kind={kind} code={code} msg={} detail={}",
150 truncate(msg, MAX_BODY_LEN),
151 truncate(extra, MAX_BODY_LEN)
152 );
153
154 if let Some(rt) = runtime() {
155 if let Ok(guard) = rt.read() {
156 append_line(&guard.error_log_path, &line);
157 }
158 } else {
159 append_line(&fallback_error_log_path(), &line);
160 }
161 dev_error_log::try_persist_app_error(kind, code, msg, detail);
162 crate::telemetry::log_error_event(&format!("APP kind={kind} code={code} msg={msg}"));
163 tracing::warn!("{line}");
164}