wde_logger/
editor_layer.rs1use 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}