1use std::collections::VecDeque;
2use std::sync::Mutex;
3
4use tokio::sync::mpsc;
5use tracing::field::{Field, Visit};
6use tracing_subscriber::Layer;
7
8use crate::protocol::{AlertInfo, LogEntry};
9
10const CONTEXT_BUFFER_SIZE: usize = 200;
11
12#[derive(Debug)]
24pub struct LogMonitor {
25 tx: mpsc::Sender<AlertInfo>,
26 context_buffer: Mutex<VecDeque<LogEntry>>,
27}
28
29impl LogMonitor {
30 pub(crate) fn new(tx: mpsc::Sender<AlertInfo>) -> Self {
31 Self {
32 tx,
33 context_buffer: Mutex::new(VecDeque::with_capacity(CONTEXT_BUFFER_SIZE)),
34 }
35 }
36}
37
38impl<S: tracing::Subscriber> Layer<S> for LogMonitor {
39 fn on_event(
40 &self,
41 event: &tracing::Event<'_>,
42 _ctx: tracing_subscriber::layer::Context<'_, S>,
43 ) {
44 let meta = event.metadata();
45 let level = *meta.level();
46
47 let mut visitor = MessageVisitor::default();
48 event.record(&mut visitor);
49 let message = visitor.message.unwrap_or_default();
50
51 let timestamp_ms = std::time::SystemTime::now()
52 .duration_since(std::time::UNIX_EPOCH)
53 .unwrap_or_default()
54 .as_millis() as u64;
55
56 let entry = LogEntry {
58 level: level.to_string(),
59 target: meta.target().to_string(),
60 message: message.clone(),
61 timestamp_ms,
62 };
63
64 let mut buf = self
65 .context_buffer
66 .lock()
67 .unwrap_or_else(|e| e.into_inner());
68 if buf.len() >= CONTEXT_BUFFER_SIZE {
69 buf.pop_front();
70 }
71 buf.push_back(entry);
72
73 if level != tracing::Level::ERROR || !meta.target().starts_with("iroh") {
75 return;
76 }
77
78 let context: Vec<LogEntry> = buf.drain(..).collect();
79
80 let alert = AlertInfo {
81 target: meta.target().to_string(),
82 message,
83 file: meta.file().map(String::from),
84 line: meta.line(),
85 timestamp_ms,
86 iroh_version: crate::IROH_VERSION.to_string(),
87 iroh_n0des_version: crate::IROH_N0DES_VERSION.to_string(),
88 context,
89 };
90
91 let _ = self.tx.try_send(alert);
94 }
95}
96
97#[derive(Default)]
98struct MessageVisitor {
99 message: Option<String>,
100}
101
102impl Visit for MessageVisitor {
103 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
104 if field.name() == "message" {
105 self.message = Some(format!("{:?}", value));
106 }
107 }
108
109 fn record_str(&mut self, field: &Field, value: &str) {
110 if field.name() == "message" {
111 self.message = Some(value.to_string());
112 }
113 }
114}