1use std::sync::Arc;
7
8use chrono::{DateTime, Utc};
9use parking_lot::Mutex;
10use serde::Serialize;
11use tracing::{Event, Subscriber};
12use tracing_subscriber::layer::{Context, Layer};
13use tracing_subscriber::registry::LookupSpan;
14
15const MAX_ENTRIES: usize = 5_000;
16
17#[derive(Debug, Clone, Serialize)]
18pub struct LogEntry {
19 pub timestamp: DateTime<Utc>,
20 pub level: String,
21 pub target: String,
22 pub message: String,
23 pub fields: serde_json::Value,
24}
25
26pub struct LogBuffer {
27 entries: Mutex<std::collections::VecDeque<LogEntry>>,
28}
29
30impl LogBuffer {
31 pub fn new() -> Arc<Self> {
32 Arc::new(Self {
33 entries: Mutex::new(std::collections::VecDeque::with_capacity(MAX_ENTRIES)),
34 })
35 }
36
37 pub fn push(&self, entry: LogEntry) {
38 let mut g = self.entries.lock();
39 if g.len() == MAX_ENTRIES {
40 g.pop_front();
41 }
42 g.push_back(entry);
43 }
44
45 pub fn tail(&self, n: usize) -> Vec<LogEntry> {
46 let g = self.entries.lock();
47 let start = g.len().saturating_sub(n);
48 g.iter().skip(start).cloned().collect()
49 }
50
51 pub fn last_error(&self) -> Option<LogEntry> {
52 let g = self.entries.lock();
53 g.iter()
54 .rev()
55 .find(|e| e.level.eq_ignore_ascii_case("ERROR"))
56 .cloned()
57 }
58
59 pub fn count(&self) -> usize {
60 self.entries.lock().len()
61 }
62}
63
64pub struct CaptureLayer {
65 buffer: Arc<LogBuffer>,
66}
67
68impl CaptureLayer {
69 pub fn new(buffer: Arc<LogBuffer>) -> Self {
70 Self { buffer }
71 }
72}
73
74impl<S> Layer<S> for CaptureLayer
75where
76 S: Subscriber + for<'a> LookupSpan<'a>,
77{
78 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
79 let mut visitor = FieldVisitor::default();
80 event.record(&mut visitor);
81
82 let metadata = event.metadata();
83 let entry = LogEntry {
84 timestamp: Utc::now(),
85 level: metadata.level().to_string(),
86 target: metadata.target().to_string(),
87 message: visitor.message.unwrap_or_default(),
88 fields: serde_json::Value::Object(visitor.fields),
89 };
90 self.buffer.push(entry);
91 }
92}
93
94#[derive(Default)]
95struct FieldVisitor {
96 message: Option<String>,
97 fields: serde_json::Map<String, serde_json::Value>,
98}
99
100impl tracing::field::Visit for FieldVisitor {
101 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
102 let value = format!("{value:?}");
103 if field.name() == "message" {
104 self.message = Some(value);
105 } else {
106 self.fields
107 .insert(field.name().to_string(), serde_json::Value::String(value));
108 }
109 }
110
111 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
112 if field.name() == "message" {
113 self.message = Some(value.to_string());
114 } else {
115 self.fields.insert(
116 field.name().to_string(),
117 serde_json::Value::String(value.to_string()),
118 );
119 }
120 }
121
122 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
123 self.fields
124 .insert(field.name().to_string(), serde_json::json!(value));
125 }
126
127 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
128 self.fields
129 .insert(field.name().to_string(), serde_json::json!(value));
130 }
131
132 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
133 self.fields
134 .insert(field.name().to_string(), serde_json::json!(value));
135 }
136}
137
138pub fn install() -> Arc<LogBuffer> {
141 use tracing_subscriber::prelude::*;
142 let buffer = LogBuffer::new();
143 let layer = CaptureLayer::new(buffer.clone());
144 let _ = tracing_subscriber::registry().with(layer).try_init();
146 buffer
147}