use crate::storage::{LogEvent, LogStorage, SpanInfo};
use chrono::Utc;
use std::collections::HashMap;
use std::fmt;
use tracing::field::{Field, Visit};
use tracing::{Level, Subscriber};
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;
struct FieldVisitor {
fields: HashMap<String, String>,
}
impl FieldVisitor {
fn new() -> Self {
Self {
fields: HashMap::new(),
}
}
}
impl Visit for FieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.fields
.insert(field.name().to_string(), format!("{:?}", value));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
}
pub struct LogCaptureLayer {
storage: LogStorage,
}
impl LogCaptureLayer {
pub fn new(storage: LogStorage) -> Self {
Self { storage }
}
fn extract_message(event: &tracing::Event) -> String {
let mut visitor = FieldVisitor::new();
event.record(&mut visitor);
if let Some(message) = visitor.fields.get("message") {
return message.clone();
}
visitor
.fields
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.collect::<Vec<_>>()
.join(", ")
}
fn level_to_string(level: &Level) -> String {
match *level {
Level::TRACE => "TRACE",
Level::DEBUG => "DEBUG",
Level::INFO => "INFO",
Level::WARN => "WARN",
Level::ERROR => "ERROR",
}
.to_string()
}
fn extract_span_info<S>(event: &tracing::Event<'_>, ctx: &Context<'_, S>) -> Option<SpanInfo>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let span = ctx.event_span(event)?;
let ext = span.extensions();
let name = span.name().to_string();
let mut fields = HashMap::new();
if let Some(field_visitor) = ext.get::<FieldVisitor>() {
fields = field_visitor.fields.clone();
}
Some(SpanInfo { name, fields })
}
}
const FILTERED_TARGETS: &[&str] = &[
"log", "tracing_web_console", "tungstenite", "tokio_tungstenite", ];
impl<S> Layer<S> for LogCaptureLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
let metadata = event.metadata();
let target = metadata.target();
let mut visitor = FieldVisitor::new();
event.record(&mut visitor);
let actual_target = visitor
.fields
.get("log.target")
.cloned()
.unwrap_or_else(|| target.to_string());
for filtered in FILTERED_TARGETS {
if actual_target == *filtered || actual_target.starts_with(&format!("{}::", filtered)) {
return;
}
}
let message = Self::extract_message(event);
visitor.fields.remove("message");
visitor.fields.remove("log.target");
visitor.fields.remove("log.module_path");
visitor.fields.remove("log.file");
visitor.fields.remove("log.line");
let log_event = LogEvent {
timestamp: Utc::now(),
level: Self::level_to_string(metadata.level()),
target: actual_target,
message,
fields: visitor.fields,
span: Self::extract_span_info(event, &ctx),
file: metadata.file().map(|s| s.to_string()),
line: metadata.line(),
};
self.storage.push(log_event);
}
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
id: &tracing::span::Id,
ctx: Context<'_, S>,
) {
let span = ctx.span(id).expect("Span not found");
let mut visitor = FieldVisitor::new();
attrs.record(&mut visitor);
let mut extensions = span.extensions_mut();
extensions.insert(visitor);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_conversion() {
assert_eq!(LogCaptureLayer::level_to_string(&Level::TRACE), "TRACE");
assert_eq!(LogCaptureLayer::level_to_string(&Level::DEBUG), "DEBUG");
assert_eq!(LogCaptureLayer::level_to_string(&Level::INFO), "INFO");
assert_eq!(LogCaptureLayer::level_to_string(&Level::WARN), "WARN");
assert_eq!(LogCaptureLayer::level_to_string(&Level::ERROR), "ERROR");
}
#[test]
fn test_field_visitor() {
let visitor = FieldVisitor::new();
assert_eq!(visitor.fields.len(), 0);
}
#[test]
fn test_log_capture_layer_creation() {
let storage = LogStorage::new();
let _layer = LogCaptureLayer::new(storage.clone());
let filter = crate::storage::LogFilter::default();
let (_events, count) = storage.get_filtered(&filter, None, None);
assert_eq!(count, 0);
}
}