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