1use std::path::Path;
2use std::sync::OnceLock;
3
4use crate::audit_log_writer;
5use tracing_subscriber::{
6 EnvFilter, Layer, filter::filter_fn, fmt, layer::SubscriberExt, reload, util::SubscriberInitExt,
7};
8
9#[derive(Clone, Copy, Debug)]
15pub struct LocalRfc3339Timer;
16
17impl fmt::time::FormatTime for LocalRfc3339Timer {
18 fn format_time(&self, w: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
19 write!(
20 w,
21 "{}",
22 chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true)
23 )
24 }
25}
26
27static GLOBAL_RELOAD_HANDLE: OnceLock<reload::Handle<EnvFilter, tracing_subscriber::Registry>> =
38 OnceLock::new();
39
40fn map_remote_level_to_env_filter(level: &str) -> Option<&'static str> {
45 match level.to_lowercase().as_str() {
46 "no" => Some("off"),
47 "debug" => Some("debug"),
48 "info" => Some("info"),
49 "warning" => Some("warn"),
50 "error" => Some("error"),
51 "fatal" => Some("error"), _ => None,
53 }
54}
55
56pub fn set_runtime_log_level(level: &str) -> Result<String, String> {
71 let mapped = match map_remote_level_to_env_filter(level) {
72 Some(s) => s,
73 None => return Err("level invalid".to_string()),
74 };
75
76 let handle = match GLOBAL_RELOAD_HANDLE.get() {
77 Some(h) => h,
78 None => return Err("reload handle not initialized".to_string()),
79 };
80
81 let new_filter = EnvFilter::try_new(mapped)
82 .map_err(|e| format!("EnvFilter parse failed for '{mapped}': {e}"))?;
83
84 handle
85 .reload(new_filter)
86 .map_err(|e| format!("reload failed: {e}"))?;
87
88 Ok(mapped.to_string())
89}
90
91pub fn is_runtime_reload_available() -> bool {
94 GLOBAL_RELOAD_HANDLE.get().is_some()
95}
96
97pub fn init_logging_with_level(level: &str) {
104 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
105 let (filter_layer, reload_handle) = reload::Layer::new(filter);
106
107 let fmt_layer = fmt::layer()
108 .with_timer(LocalRfc3339Timer)
109 .with_target(true)
110 .with_thread_ids(true)
111 .with_file(true)
112 .with_line_number(true)
113 .with_writer(std::io::stderr);
114
115 let registry = tracing_subscriber::registry()
116 .with(filter_layer)
117 .with(fmt_layer);
118 if registry.try_init().is_ok() {
119 if GLOBAL_RELOAD_HANDLE.set(reload_handle).is_err() {
121 eprintln!(
122 "futu-opend: runtime log reload handle already installed; keeping existing handle"
123 );
124 }
125 }
126}
127
128pub fn init_logging() {
131 init_logging_with_level("info");
132}
133
134pub fn init_json_logging_with_level(level: &str) {
138 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
139 let (filter_layer, reload_handle) = reload::Layer::new(filter);
140
141 let fmt_layer = fmt::layer()
142 .json()
143 .with_timer(LocalRfc3339Timer)
144 .with_target(true)
145 .with_thread_ids(true);
146
147 let registry = tracing_subscriber::registry()
148 .with(filter_layer)
149 .with(fmt_layer);
150 if registry.try_init().is_ok() && GLOBAL_RELOAD_HANDLE.set(reload_handle).is_err() {
151 eprintln!(
152 "futu-opend: runtime log reload handle already installed; keeping existing handle"
153 );
154 }
155}
156
157pub fn init_json_logging() {
159 init_json_logging_with_level("info");
160}
161
162pub fn init_logging_with_audit(
170 level: &str,
171 audit_path: Option<&Path>,
172) -> std::io::Result<Option<tracing_appender::non_blocking::WorkerGuard>> {
173 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
174 let (filter_layer, reload_handle) = reload::Layer::new(filter);
176
177 let fmt_layer = fmt::layer()
178 .with_timer(LocalRfc3339Timer)
179 .with_target(true)
180 .with_thread_ids(true)
181 .with_file(true)
182 .with_line_number(true)
183 .with_writer(std::io::stderr);
184
185 let registry = tracing_subscriber::registry()
186 .with(filter_layer)
187 .with(fmt_layer);
188
189 let _init_ok = if let Some(path) = audit_path {
190 let (writer, guard) = audit_log_writer::open_writer(path)?;
192 let audit_layer = fmt::layer()
193 .json()
194 .flatten_event(true)
195 .with_current_span(false)
196 .with_span_list(false)
197 .with_target(true)
198 .with_writer(writer)
199 .with_filter(filter_fn(|meta| meta.target() == "futu_audit"));
200 let init_ok = registry.with(audit_layer).try_init().is_ok();
201 if init_ok && GLOBAL_RELOAD_HANDLE.set(reload_handle).is_err() {
202 eprintln!(
203 "futu-opend: runtime log reload handle already installed; keeping existing handle"
204 );
205 }
206 return Ok(if init_ok { Some(guard) } else { None });
207 } else {
208 let ok = registry.try_init().is_ok();
209 if ok && GLOBAL_RELOAD_HANDLE.set(reload_handle).is_err() {
210 eprintln!(
211 "futu-opend: runtime log reload handle already installed; keeping existing handle"
212 );
213 }
214 ok
215 };
216 Ok(None)
217}
218
219#[cfg(test)]
220mod tests;