Skip to main content

ephem_debugger_rs/
capture.rs

1//! Tracing layer that captures log events into the [`LogStore`].
2//!
3//! # Usage
4//!
5//! ```rust,ignore
6//! use tracing_subscriber::prelude::*;
7//! use ephem_debugger::capture::CaptureLayer;
8//!
9//! tracing_subscriber::registry()
10//!     .with(CaptureLayer::new(store.clone()))
11//!     .with(tracing_subscriber::fmt::layer())
12//!     .init();
13//! ```
14
15use std::sync::Arc;
16
17use tracing::field::{Field, Visit};
18use tracing::Event;
19use tracing_subscriber::layer::Context;
20use tracing_subscriber::Layer;
21
22use crate::store::LogStore;
23
24/// A [`tracing_subscriber::Layer`] that captures events into the debugger
25/// [`LogStore`] as console entries.
26pub struct CaptureLayer {
27    store: Arc<LogStore>,
28}
29
30impl CaptureLayer {
31    /// Create a new capture layer writing to the given store.
32    pub fn new(store: Arc<LogStore>) -> Self {
33        Self { store }
34    }
35}
36
37impl<S> Layer<S> for CaptureLayer
38where
39    S: tracing::Subscriber,
40{
41    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
42        let mut visitor = FieldVisitor::default();
43        event.record(&mut visitor);
44
45        let level = match *event.metadata().level() {
46            tracing::Level::ERROR => "error",
47            tracing::Level::WARN => "warn",
48            tracing::Level::INFO => "info",
49            tracing::Level::DEBUG => "debug",
50            tracing::Level::TRACE => "debug",
51        };
52
53        let mut args: Vec<serde_json::Value> = Vec::new();
54        if let Some(msg) = visitor.message {
55            args.push(serde_json::Value::String(msg));
56        }
57        for (key, value) in visitor.fields {
58            args.push(serde_json::json!({ key: value }));
59        }
60
61        self.store.push_console(level, args, "server");
62    }
63}
64
65/// Visitor that extracts the message and structured fields from a tracing event.
66#[derive(Default)]
67struct FieldVisitor {
68    message: Option<String>,
69    fields: Vec<(String, String)>,
70}
71
72impl Visit for FieldVisitor {
73    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
74        let val = format!("{value:?}");
75        if field.name() == "message" {
76            self.message = Some(val);
77        } else {
78            self.fields.push((field.name().to_string(), val));
79        }
80    }
81
82    fn record_str(&mut self, field: &Field, value: &str) {
83        if field.name() == "message" {
84            self.message = Some(value.to_string());
85        } else {
86            self.fields
87                .push((field.name().to_string(), value.to_string()));
88        }
89    }
90
91    fn record_i64(&mut self, field: &Field, value: i64) {
92        self.fields
93            .push((field.name().to_string(), value.to_string()));
94    }
95
96    fn record_u64(&mut self, field: &Field, value: u64) {
97        self.fields
98            .push((field.name().to_string(), value.to_string()));
99    }
100
101    fn record_bool(&mut self, field: &Field, value: bool) {
102        self.fields
103            .push((field.name().to_string(), value.to_string()));
104    }
105
106    fn record_f64(&mut self, field: &Field, value: f64) {
107        self.fields
108            .push((field.name().to_string(), value.to_string()));
109    }
110}