//! This module provides an implementation of `slog::Drain` that optionally writes to a channel if //! there are subscribers to a HTTP SSE stream. use serde_json::json; use serde_json::Value; use std::sync::Arc; use tokio::sync::broadcast::Sender; use tracing::field::{Field, Visit}; use tracing::{Event, Subscriber}; use tracing_subscriber::layer::{Context, Layer}; /// Default log level for SSE Events. // NOTE: Made this a constant. Debug level seems to be pretty intense. Can make this // configurable later if needed. const LOG_LEVEL: tracing::Level = tracing::Level::INFO; /// The components required in the HTTP API task to receive logged events. #[derive(Clone)] pub struct SSELoggingComponents { /// The channel to receive events from. pub sender: Arc>>, } impl SSELoggingComponents { pub fn new(channel_size: usize) -> Self { let (sender, _receiver) = tokio::sync::broadcast::channel(channel_size); SSELoggingComponents { sender: Arc::new(sender), } } } impl Layer for SSELoggingComponents { fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { if *event.metadata().level() > LOG_LEVEL { return; } let mut visitor = TracingEventVisitor::new(); event.record(&mut visitor); let mut log_entry = visitor.finish(event.metadata()); if let Some(error_type) = log_entry .get("fields") .and_then(|fields| fields.get("error_type")) .and_then(|val| val.as_str()) { if error_type.eq_ignore_ascii_case("crit") { log_entry["level"] = json!("CRIT"); if let Some(Value::Object(ref mut map)) = log_entry.get_mut("fields") { map.remove("error_type"); } } } let _ = self.sender.send(Arc::new(log_entry)); } } struct TracingEventVisitor { fields: serde_json::Map, } impl TracingEventVisitor { fn new() -> Self { TracingEventVisitor { fields: serde_json::Map::new(), } } fn finish(self, metadata: &tracing::Metadata<'_>) -> Value { let mut log_entry = serde_json::Map::new(); log_entry.insert( "time".to_string(), json!(chrono::Local::now() .format("%b %d %H:%M:%S%.3f") .to_string()), ); log_entry.insert("level".to_string(), json!(metadata.level().to_string())); log_entry.insert("target".to_string(), json!(metadata.target())); log_entry.insert("fields".to_string(), Value::Object(self.fields)); Value::Object(log_entry) } } impl Visit for TracingEventVisitor { fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { self.fields .insert(field.name().to_string(), json!(format!("{:?}", value))); } fn record_str(&mut self, field: &Field, value: &str) { self.fields.insert(field.name().to_string(), json!(value)); } fn record_i64(&mut self, field: &Field, value: i64) { self.fields.insert(field.name().to_string(), json!(value)); } fn record_u64(&mut self, field: &Field, value: u64) { self.fields.insert(field.name().to_string(), json!(value)); } fn record_bool(&mut self, field: &Field, value: bool) { self.fields.insert(field.name().to_string(), json!(value)); } }