1use chrono::Local;
2use std::collections::VecDeque;
3use std::sync::{Arc, Mutex, OnceLock};
4use tracing::Level;
5use tracing_subscriber::fmt::MakeWriter;
6
7const MAX_LOG_ENTRIES: usize = 1000;
9
10#[derive(Debug, Clone)]
12pub struct LogEntry {
13 pub timestamp: String,
14 pub level: String,
15 pub target: String,
16 pub message: String,
17}
18
19impl LogEntry {
20 pub fn new(level: Level, target: &str, message: String) -> Self {
21 Self {
22 timestamp: Local::now().format("%H:%M:%S.%3f").to_string(),
23 level: level.to_string().to_uppercase(),
24 target: target.to_string(),
25 message,
26 }
27 }
28
29 pub fn format_for_display(&self) -> String {
31 format!(
32 "[{}] {} [{}] {}",
33 self.timestamp, self.level, self.target, self.message
34 )
35 }
36}
37
38#[derive(Clone)]
40pub struct LogRingBuffer {
41 entries: Arc<Mutex<VecDeque<LogEntry>>>,
42}
43
44impl LogRingBuffer {
45 pub fn new() -> Self {
46 Self {
47 entries: Arc::new(Mutex::new(VecDeque::with_capacity(MAX_LOG_ENTRIES))),
48 }
49 }
50
51 pub fn push(&self, entry: LogEntry) {
52 let mut entries = self.entries.lock().unwrap();
53 if entries.len() >= MAX_LOG_ENTRIES {
54 entries.pop_front();
55 }
56 entries.push_back(entry);
57 }
58
59 pub fn get_recent(&self, count: usize) -> Vec<LogEntry> {
60 let entries = self.entries.lock().unwrap();
61 entries.iter().rev().take(count).rev().cloned().collect()
62 }
63
64 pub fn clear(&self) {
65 let mut entries = self.entries.lock().unwrap();
66 entries.clear();
67 }
68
69 pub fn len(&self) -> usize {
70 let entries = self.entries.lock().unwrap();
71 entries.len()
72 }
73}
74
75pub struct RingBufferWriter {
77 buffer: LogRingBuffer,
78}
79
80impl RingBufferWriter {
81 pub fn new(buffer: LogRingBuffer) -> Self {
82 Self { buffer }
83 }
84}
85
86impl std::io::Write for RingBufferWriter {
87 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
88 if let Ok(message) = std::str::from_utf8(buf) {
90 let message = message.trim();
91 if !message.is_empty() {
92 let (level, rest) = if message.starts_with("TRACE ") {
95 (Level::TRACE, &message[6..])
96 } else if message.starts_with("DEBUG ") {
97 (Level::DEBUG, &message[6..])
98 } else if message.starts_with("INFO ") {
99 (Level::INFO, &message[5..])
100 } else if message.starts_with("WARN ") {
101 (Level::WARN, &message[5..])
102 } else if message.starts_with("ERROR ") {
103 (Level::ERROR, &message[6..])
104 } else {
105 self.buffer
107 .push(LogEntry::new(Level::INFO, "general", message.to_string()));
108 return Ok(buf.len());
109 };
110
111 let (target, msg) = if let Some(colon_pos) = rest.find(':') {
113 let potential_target = &rest[..colon_pos];
114 if !potential_target.contains(' ') {
116 (potential_target, rest[colon_pos + 1..].trim())
117 } else {
118 ("general", rest)
119 }
120 } else {
121 ("general", rest)
122 };
123
124 self.buffer
125 .push(LogEntry::new(level, target, msg.to_string()));
126 }
127 }
128 Ok(buf.len())
129 }
130
131 fn flush(&mut self) -> std::io::Result<()> {
132 Ok(())
133 }
134}
135
136impl<'a> MakeWriter<'a> for RingBufferWriter {
137 type Writer = Self;
138
139 fn make_writer(&'a self) -> Self::Writer {
140 self.clone()
141 }
142}
143
144impl Clone for RingBufferWriter {
145 fn clone(&self) -> Self {
146 Self {
147 buffer: self.buffer.clone(),
148 }
149 }
150}
151
152pub struct DualWriter {
154 buffer: LogRingBuffer,
155 dual_logger: &'static crate::dual_logging::DualLogger,
156}
157
158impl DualWriter {
159 pub fn new(
160 buffer: LogRingBuffer,
161 dual_logger: &'static crate::dual_logging::DualLogger,
162 ) -> Self {
163 Self {
164 buffer,
165 dual_logger,
166 }
167 }
168}
169
170impl std::io::Write for DualWriter {
171 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
172 if let Ok(message) = std::str::from_utf8(buf) {
174 let message = message.trim();
175 if !message.is_empty() {
176 let (level, rest) = if message.starts_with("TRACE ") {
179 (Level::TRACE, &message[6..])
180 } else if message.starts_with("DEBUG ") {
181 (Level::DEBUG, &message[6..])
182 } else if message.starts_with("INFO ") {
183 (Level::INFO, &message[5..])
184 } else if message.starts_with("WARN ") {
185 (Level::WARN, &message[5..])
186 } else if message.starts_with("ERROR ") {
187 (Level::ERROR, &message[6..])
188 } else {
189 if message.starts_with("2025-") || message.starts_with("2024-") {
191 return Ok(buf.len());
192 }
193 let entry = LogEntry::new(Level::INFO, "general", message.to_string());
195 self.buffer.push(entry.clone());
196 self.dual_logger.log("INFO", "general", message);
197 return Ok(buf.len());
198 };
199
200 let (target, msg) = if let Some(colon_pos) = rest.find(':') {
202 let potential_target = &rest[..colon_pos];
203 if !potential_target.contains(' ') {
205 (potential_target, rest[colon_pos + 1..].trim())
206 } else {
207 ("general", rest)
208 }
209 } else {
210 ("general", rest)
211 };
212
213 self.buffer
215 .push(LogEntry::new(level, target, msg.to_string()));
216
217 let level_str = match level {
219 Level::TRACE => "TRACE",
220 Level::DEBUG => "DEBUG",
221 Level::INFO => "INFO",
222 Level::WARN => "WARN",
223 Level::ERROR => "ERROR",
224 };
225 self.dual_logger.log(level_str, target, msg);
226 }
227 }
228 Ok(buf.len())
229 }
230
231 fn flush(&mut self) -> std::io::Result<()> {
232 self.dual_logger.flush();
233 Ok(())
234 }
235}
236
237impl<'a> MakeWriter<'a> for DualWriter {
238 type Writer = Self;
239
240 fn make_writer(&'a self) -> Self::Writer {
241 Self {
242 buffer: self.buffer.clone(),
243 dual_logger: self.dual_logger,
244 }
245 }
246}
247
248impl Clone for DualWriter {
249 fn clone(&self) -> Self {
250 Self {
251 buffer: self.buffer.clone(),
252 dual_logger: self.dual_logger,
253 }
254 }
255}
256
257static LOG_BUFFER: OnceLock<LogRingBuffer> = OnceLock::new();
259
260pub fn init_log_buffer() -> LogRingBuffer {
262 let buffer = LogRingBuffer::new();
263 LOG_BUFFER.set(buffer.clone()).ok();
264 buffer
265}
266
267pub fn get_log_buffer() -> Option<LogRingBuffer> {
269 LOG_BUFFER.get().cloned()
270}
271
272pub fn init_tracing_with_dual_logging() {
274 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
275
276 let dual_logger = crate::dual_logging::init_dual_logger();
278
279 let buffer = init_log_buffer();
281
282 let dual_writer = DualWriter::new(buffer.clone(), dual_logger);
284
285 let fmt_layer = fmt::layer()
287 .with_writer(dual_writer)
288 .with_target(true)
289 .with_level(true)
290 .with_ansi(false)
291 .without_time() .compact();
293
294 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("trace"));
296
297 tracing_subscriber::registry()
298 .with(filter)
299 .with(fmt_layer)
300 .init();
301
302 tracing::info!(target: "EnhancedTuiApp", "Logging system initialized with dual output");
304}
305
306pub fn init_tracing() -> LogRingBuffer {
308 init_tracing_with_dual_logging();
309 get_log_buffer().unwrap_or_else(|| LogRingBuffer::new())
310}
311
312#[macro_export]
314macro_rules! trace_operation {
315 ($op:expr) => {
316 tracing::debug!(target: "operation", "{}", $op);
317 };
318}
319
320#[macro_export]
321macro_rules! trace_query {
322 ($query:expr) => {
323 tracing::info!(target: "query", "Executing: {}", $query);
324 };
325}
326
327#[macro_export]
328macro_rules! trace_buffer_switch {
329 ($from:expr, $to:expr) => {
330 tracing::debug!(target: "buffer", "Switching from buffer {} to {}", $from, $to);
331 };
332}
333
334#[macro_export]
335macro_rules! trace_key {
336 ($key:expr) => {
337 tracing::trace!(target: "input", "Key: {:?}", $key);
338 };
339}