Skip to main content

st/
in_memory_logger.rs

1//! In-Memory Logger for Smart Tree
2//!
3//! A custom tracing layer that captures log events and stores them in a
4//! thread-safe, capped in-memory buffer. This allows the daemon to expose
5//! recent logs via an API endpoint.
6
7use serde::Serialize;
8use std::collections::VecDeque;
9use std::sync::{Arc, Mutex};
10use tracing::Subscriber;
11use tracing_subscriber::Layer;
12
13const MAX_LOG_ENTRIES: usize = 1000;
14
15#[derive(Debug, Clone, Serialize)]
16pub struct LogEntry {
17    pub timestamp: String,
18    pub level: String,
19    pub message: String,
20}
21
22#[derive(Clone, Debug)]
23pub struct InMemoryLogStore {
24    pub entries: Arc<Mutex<VecDeque<LogEntry>>>,
25}
26
27impl Default for InMemoryLogStore {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl InMemoryLogStore {
34    pub fn new() -> Self {
35        Self {
36            entries: Arc::new(Mutex::new(VecDeque::with_capacity(MAX_LOG_ENTRIES))),
37        }
38    }
39}
40
41pub struct InMemoryLoggerLayer {
42    store: InMemoryLogStore,
43}
44
45impl InMemoryLoggerLayer {
46    pub fn new(store: InMemoryLogStore) -> Self {
47        Self { store }
48    }
49}
50
51// A visitor to format the log message.
52struct LogVisitor {
53    message: String,
54}
55
56impl tracing::field::Visit for LogVisitor {
57    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
58        if field.name() == "message" {
59            self.message = format!("{:?}", value);
60        }
61    }
62}
63
64impl<S> Layer<S> for InMemoryLoggerLayer
65where
66    S: Subscriber,
67{
68    fn on_event(
69        &self,
70        event: &tracing::Event<'_>,
71        _ctx: tracing_subscriber::layer::Context<'_, S>,
72    ) {
73        let mut visitor = LogVisitor {
74            message: String::new(),
75        };
76        event.record(&mut visitor);
77
78        let metadata = event.metadata();
79        let entry = LogEntry {
80            timestamp: chrono::Utc::now().to_rfc3339(),
81            level: metadata.level().to_string(),
82            message: visitor.message,
83        };
84
85        if let Ok(mut entries) = self.store.entries.lock() {
86            if entries.len() >= MAX_LOG_ENTRIES {
87                entries.pop_front();
88            }
89            entries.push_back(entry);
90        }
91    }
92}