context-logger 0.2.1

A small structured context propagation layer for the standard Rust log ecosystem.
use std::sync::mpsc::{self, Receiver, Sender};

use log::{Level, LevelFilter, Log, Metadata, Record, kv};
use serde_json::{Map, Value};

#[derive(Debug, Eq, PartialEq)]
pub struct RecordSnapshot {
    pub level: Level,
    pub target: String,
    pub message: String,
    pub fields: Map<String, Value>,
}

#[must_use]
pub fn init_channel_logger() -> (ChannelLogger, Receiver<RecordSnapshot>) {
    let (sender, receiver) = mpsc::channel();
    (ChannelLogger { sender }, receiver)
}

#[derive(Debug)]
pub struct ChannelLogger {
    sender: Sender<RecordSnapshot>,
}

impl Log for ChannelLogger {
    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
        metadata.level() <= LevelFilter::Trace
    }

    fn log(&self, record: &Record<'_>) {
        self.sender
            .send(RecordSnapshot::from_record(record))
            .expect("test logger receiver was dropped");
    }

    fn flush(&self) {}
}

impl RecordSnapshot {
    fn from_record(record: &Record<'_>) -> Self {
        let mut fields = Map::new();
        record
            .key_values()
            .visit(&mut JsonFields(&mut fields))
            .expect("test logger failed to visit record fields");

        Self {
            level: record.level(),
            target: record.target().to_owned(),
            message: record.args().to_string(),
            fields,
        }
    }
}

struct JsonFields<'a>(&'a mut Map<String, Value>);

impl<'kvs> kv::VisitSource<'kvs> for JsonFields<'_> {
    fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
        let value = serde_json::to_value(value).expect("log field is not JSON serializable");
        self.0.insert(key.to_string(), value);
        Ok(())
    }
}