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().create(true).append(true).open(path);
56}
57
58fn runtime() -> Option<&'static RwLock<ErrorLogRuntime>> {
59 RUNTIME.get()
60}
61
62fn fallback_error_log_path() -> PathBuf {
64 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
65 .join("logs")
66 .join("error.log")
67}
68
69fn truncate(text: &str, max: usize) -> String {
71 if text.len() <= max {
72 return text.to_string();
73 }
74 format!("{}...(len={})", &text[..max], text.len())
75}
76
77fn append_line(path: &PathBuf, line: &str) {
79 if let Some(parent) = path.parent() {
80 let _ = std::fs::create_dir_all(parent);
81 }
82 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
83 let _ = writeln!(file, "{line}");
84 }
85}
86
87pub fn log_http_error(method: &str, path: &str, status: u16, body: &str, member_id: Option<i32>) {
99 let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
100 let member = member_id
101 .map(|id| id.to_string())
102 .unwrap_or_else(|| "-".to_string());
103 let line = format!(
104 "[{ts}] HTTP {method} {path} status={status} member={member} body={}",
105 truncate(body, MAX_BODY_LEN)
106 );
107
108 if let Some(rt) = runtime() {
109 if let Ok(guard) = rt.read() {
110 append_line(&guard.error_log_path, &line);
111 }
112 } else {
113 append_line(&fallback_error_log_path(), &line);
114 }
115 dev_error_log::try_persist_http_error(method, path, status, body, member_id);
116 crate::telemetry::log_error_event(&format!("HTTP {method} {path} status={status}"));
117 tracing::warn!("{line}");
118}
119
120pub fn log_app_error(kind: &str, code: i32, msg: &str, detail: Option<&str>) {
131 let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
132 let extra = detail.unwrap_or("");
133 let line = format!(
134 "[{ts}] APP kind={kind} code={code} msg={} detail={}",
135 truncate(msg, MAX_BODY_LEN),
136 truncate(extra, MAX_BODY_LEN)
137 );
138
139 if let Some(rt) = runtime() {
140 if let Ok(guard) = rt.read() {
141 append_line(&guard.error_log_path, &line);
142 }
143 } else {
144 append_line(&fallback_error_log_path(), &line);
145 }
146 dev_error_log::try_persist_app_error(kind, code, msg, detail);
147 crate::telemetry::log_error_event(&format!("APP kind={kind} code={code} msg={msg}"));
148 tracing::warn!("{line}");
149}