Skip to main content

wde_logger/
editor_layer.rs

1use std::{
2    collections::{BTreeMap, VecDeque},
3    sync::{Mutex, OnceLock},
4    time::SystemTime
5};
6
7use tracing::{
8    Level,
9    field::{Field, Visit}
10};
11use tracing_subscriber::{Layer, layer::Context};
12
13const EDITOR_LOG_CAPACITY: usize = 4_000;
14
15#[derive(Clone, Debug)]
16pub struct EditorLogEntry {
17    pub timestamp: SystemTime,
18    pub level: Level,
19    pub target: String,
20    pub message: String
21}
22
23static EDITOR_LOG_BUFFER: OnceLock<Mutex<VecDeque<EditorLogEntry>>> = OnceLock::new();
24
25fn editor_log_buffer() -> &'static Mutex<VecDeque<EditorLogEntry>> {
26    EDITOR_LOG_BUFFER.get_or_init(|| Mutex::new(VecDeque::with_capacity(EDITOR_LOG_CAPACITY)))
27}
28fn push_editor_log(entry: EditorLogEntry) {
29    let mut logs = editor_log_buffer()
30        .lock()
31        .expect("editor log buffer should not be poisoned");
32    if logs.len() >= EDITOR_LOG_CAPACITY {
33        logs.pop_front();
34    }
35    logs.push_back(entry);
36}
37pub fn editor_logs_snapshot() -> Vec<EditorLogEntry> {
38    editor_log_buffer()
39        .lock()
40        .expect("editor log buffer should not be poisoned")
41        .iter()
42        .cloned()
43        .collect()
44}
45pub fn editor_logs_clear() {
46    editor_log_buffer()
47        .lock()
48        .expect("editor log buffer should not be poisoned")
49        .clear();
50}
51
52#[derive(Default)]
53pub(crate) struct EditorLogVisitor {
54    message: Option<String>,
55    fields: BTreeMap<String, String>
56}
57impl Visit for EditorLogVisitor {
58    fn record_debug(&mut self, field: &Field, value: &dyn core::fmt::Debug) {
59        self.record(field, format!("{value:?}"));
60    }
61
62    fn record_str(&mut self, field: &Field, value: &str) {
63        self.record(field, value.to_string());
64    }
65
66    fn record_i64(&mut self, field: &Field, value: i64) {
67        self.record(field, value.to_string());
68    }
69
70    fn record_u64(&mut self, field: &Field, value: u64) {
71        self.record(field, value.to_string());
72    }
73
74    fn record_bool(&mut self, field: &Field, value: bool) {
75        self.record(field, value.to_string());
76    }
77
78    fn record_f64(&mut self, field: &Field, value: f64) {
79        self.record(field, value.to_string());
80    }
81}
82impl EditorLogVisitor {
83    fn record(&mut self, field: &Field, value: String) {
84        if field.name() == "message" {
85            self.message = Some(value.trim_matches('"').to_string());
86        } else {
87            self.fields.insert(field.name().to_string(), value);
88        }
89    }
90
91    fn build_message(self) -> String {
92        let mut message = self.message.unwrap_or_default();
93        if !self.fields.is_empty() {
94            let trailing = self
95                .fields
96                .into_iter()
97                .map(|(key, value)| format!("{key}={value}"))
98                .collect::<Vec<_>>()
99                .join(" ");
100            if message.is_empty() {
101                message = trailing;
102            } else {
103                message.push(' ');
104                message.push_str(&trailing);
105            }
106        }
107        if message.is_empty() {
108            "<empty log event>".to_string()
109        } else {
110            message
111        }
112    }
113}
114
115pub(crate) struct EditorLogLayer;
116impl<S> Layer<S> for EditorLogLayer
117where
118    S: tracing::Subscriber
119{
120    fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
121        let metadata = event.metadata();
122        let mut visitor = EditorLogVisitor::default();
123        event.record(&mut visitor);
124
125        push_editor_log(EditorLogEntry {
126            timestamp: SystemTime::now(),
127            level: *metadata.level(),
128            target: metadata.target().to_string(),
129            message: visitor.build_message()
130        });
131    }
132}