use once_cell::sync::OnceCell;
use serde::Serialize;
use std::io::Write;
use tokio::sync::broadcast;
use tracing::{debug, error, info, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
#[derive(Clone, Debug, Serialize)]
pub struct LogEvent {
pub level: String,
pub target: String,
pub message: String,
pub file: Option<String>,
pub line: Option<u32>,
pub time: String,
}
static LOG_TX: OnceCell<broadcast::Sender<LogEvent>> = OnceCell::new();
pub fn subscribe_logs() -> Option<broadcast::Receiver<LogEvent>> {
LOG_TX.get().map(|tx| tx.subscribe())
}
struct BroadcastLayer {
tx: broadcast::Sender<LogEvent>,
}
impl<S> Layer<S> for BroadcastLayer
where
S: tracing::Subscriber,
{
fn on_event(&self, event: &tracing::Event, _ctx: tracing_subscriber::layer::Context<'_, S>) {
use tracing::field::{Field, Visit};
struct MsgVisitor {
msg: String,
}
impl Visit for MsgVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.msg = format!("{:?}", value);
}
}
fn record_str(&mut self, field: &Field, value: &str) {
if field.name() == "message" {
self.msg = value.to_string();
}
}
}
let mut visitor = MsgVisitor { msg: String::new() };
event.record(&mut visitor);
let meta = event.metadata();
let ev = LogEvent {
level: meta.level().to_string(),
target: meta.target().to_string(),
message: visitor.msg,
file: meta.file().map(|s| s.to_string()),
line: meta.line(),
time: chrono::Utc::now().to_rfc3339(),
};
let _ = self.tx.send(ev);
}
}
#[derive(Clone)]
pub struct Logger {
namespace: String,
}
impl Logger {
pub fn new(namespace: impl Into<String>) -> Self {
Self {
namespace: namespace.into(),
}
}
pub fn info(&self, message: &str) {
info!("[{}] {}", self.namespace, message);
}
pub fn debug(&self, message: &str) {
debug!("[{}] {}", self.namespace, message);
}
pub fn warn(&self, message: &str) {
warn!("[{}] {}", self.namespace, message);
}
pub fn error(&self, message: &str) {
error!("[{}] {}", self.namespace, message);
}
pub fn success(&self, message: &str) {
info!("[{}] ✓ {}", self.namespace, message);
}
}
pub fn init_logging() {
let level = std::env::var("ZOEY_LOG_LEVEL").unwrap_or_else(|_| "trace".to_string());
let env_filter =
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| level.into());
let tx = LOG_TX
.get_or_init(|| {
let (tx, _rx) = broadcast::channel(1024);
tx
})
.clone();
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.with(tracing_subscriber::fmt::layer().with_writer(LogBufferWriter::default()))
.with(BroadcastLayer { tx })
.init();
}
#[derive(Default, Clone)]
struct LogBufferWriter;
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for LogBufferWriter {
type Writer = LogBuf;
fn make_writer(&'a self) -> Self::Writer {
LogBuf::default()
}
}
#[derive(Default)]
struct LogBuf(Vec<u8>);
impl Write for LogBuf {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logger_creation() {
let logger = Logger::new("test");
assert_eq!(logger.namespace, "test");
}
#[test]
fn test_logger_methods() {
let logger = Logger::new("test");
logger.info("info message");
logger.debug("debug message");
logger.warn("warn message");
logger.error("error message");
logger.success("success message");
}
}