grafbabe 0.1.0

Simple openmetrics stats collector
use super::config::{Config, LogFormat, LogTarget};
use super::time::Time;
use env_logger::fmt::{Formatter, Target, WriteStyle};
use log::{LevelFilter, kv};
use std::time::SystemTime;
use std::{io, io::Write};

pub fn init_logger(config: &Config) {
    let target = match config.log_target {
        LogTarget::None => return,
        LogTarget::Stdout => Target::Stdout,
        LogTarget::Stderr => Target::Stderr,
    };
    let log_format = config.log_format;

    let mut builder = env_logger::builder();
    let logger = builder
        .target(target)
        .filter_level(config.log_level.to_level_filter())
        .filter_module("ureq", LevelFilter::Off)
        .filter_module("rustls", LevelFilter::Off)
        .filter_module("mio", LevelFilter::Off)
        .format(move |formatter, record| {
            let now = SystemTime::now();
            let t = Time::from(now);
            let level = record.level();
            let target = record.target();
            let args = record.args();
            #[cfg(feature = "color")]
            let level_style = formatter.default_level_style(level);
            #[cfg(feature = "color")]
            write!(
                formatter,
                "{t} [{level_style}{level:<5}{level_style:#} {target}] {args}"
            )?;
            #[cfg(not(feature = "color"))]
            write!(formatter, "{t} [{level:<5} {target}] {args}")?;

            record
                .key_values()
                .visit(&mut Visitor {
                    format: log_format,
                    formatter,
                })
                .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
            write!(formatter, "\n")
        });

    match config.log_format {
        LogFormat::Pretty => logger.write_style(WriteStyle::Auto).init(),
        LogFormat::Plain => logger.write_style(WriteStyle::Never).init(),
    }
}

static NEEDS_ESCAPE_PRETTY: &'static [char] = &[' ', '"', '\\'];
static NEEDS_ESCAPE_PLAIN: &'static [char] = &[' ', '"', '\\', '\n'];

struct Visitor<'a> {
    format: LogFormat,
    formatter: &'a mut Formatter,
}

impl<'kvs> kv::VisitSource<'kvs> for Visitor<'_> {
    fn visit_pair(&mut self, key: kv::Key<'_>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
        match self.format {
            LogFormat::Plain => write_plain(self.formatter, key, value)?,
            LogFormat::Pretty => write_pretty(self.formatter, key, value)?,
        }
        Ok(())
    }
}

fn write_plain(w: &mut Formatter, key: kv::Key<'_>, value: kv::Value) -> std::io::Result<()> {
    write!(w, " {key}=")?;
    let value_str = value.to_string();
    if value_str.chars().any(|c| NEEDS_ESCAPE_PLAIN.contains(&c)) {
        let escaped_value = value_str.escape_debug();
        write!(w, "\"{escaped_value}\"")
    } else {
        write!(w, "{value}")
    }
}

fn write_pretty(w: &mut Formatter, key: kv::Key<'_>, value: kv::Value) -> std::io::Result<()> {
    write!(w, "\n\t{key}: ")?;
    let value_str = value.to_string();
    if value_str.chars().any(|c| NEEDS_ESCAPE_PRETTY.contains(&c)) {
        let escaped_value = value_str.escape_debug();
        write!(w, "\"{escaped_value}\"")
    } else {
        write!(w, "{value}")
    }
}