bflog 0.3.1

Very tiny logging framework made for microservices.
Documentation
use super::Format;
use super::Level;
use chrono::{Datelike, Timelike};
use serde::Serialize;
use serde_json;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::io::Write;

// Colors for the parts of a log entry.
//
// See `fn color(Format)` for format colors.
const TIME_COLOR: Color = Color::BrightBlack;
const SRC_COLOR: Color = Color::White;
const MSG_COLOR: Color = Color::BrightWhite;

const KEY_COLOR: Color = Color::Cyan;
const VALUE_COLOR: Color = Color::BrightCyan;

/// One of the possible terminal colors.
#[allow(dead_code)]
enum Color {
  Black,
  Red,
  Green,
  Yellow,
  Blue,
  Magenta,
  Cyan,
  White,
  BrightBlack,
  BrightRed,
  BrightGreen,
  BrightYellow,
  BrightBlue,
  BrightMagenta,
  BrightCyan,
  BrightWhite,
  Reset,
}

// Display the color by writing an escape sequence.
impl Display for Color {
  fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
    formatter.write_str(match self {
      Color::Black => "\x1b[0m\x1b[30m",
      Color::Red => "\x1b[0m\x1b[31m",
      Color::Green => "\x1b[0m\x1b[32m",
      Color::Yellow => "\x1b[0m\x1b[33m",
      Color::Blue => "\x1b[0m\x1b[34m",
      Color::Magenta => "\x1b[0m\x1b[35m",
      Color::Cyan => "\x1b[0m\x1b[36m",
      Color::White => "\x1b[0m\x1b[37m",
      Color::BrightBlack => "\x1b[30;1m",
      Color::BrightRed => "\x1b[31;1m",
      Color::BrightGreen => "\x1b[32;1m",
      Color::BrightYellow => "\x1b[33;1m",
      Color::BrightBlue => "\x1b[34;1m",
      Color::BrightMagenta => "\x1b[35;1m",
      Color::BrightCyan => "\x1b[36;1m",
      Color::BrightWhite => "\x1b[37;1m",
      Color::Reset => "\x1b[0m",
    })
  }
}

struct SrcName<'a>(&'a str);

// Format the source for plain/color/modern output.
impl<'a> Display for SrcName<'a> {
  fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
    if self.0.len() > 0 {
      write!(formatter, " [{}]", self.0)?;
    }

    Ok(())
  }
}

// Format the source for JSON output.
impl<'a> Debug for SrcName<'a> {
  fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
    write!(formatter, "{:?}", self.0)
  }
}

/// Outputs the beginning of a long entry.
pub fn begin_entry<W: Write>(mut writer: W, format: Format, level: Level, src: &str, msg: &str) {
  let time = chrono::Utc::now();
  let src = SrcName(src);

  match format {
    Format::Plain => {
      write!(
        writer,
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z {}{} {}",
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        level,
        src,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Color => {
      write!(
        writer,
        "{}{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{}{}{} {}{}",
        TIME_COLOR,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        color(level),
        level,
        SRC_COLOR,
        src,
        MSG_COLOR,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Modern => {
      write!(
        writer,
        "{}{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{}{}{} {}{}",
        TIME_COLOR,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        color(level),
        icon(level),
        SRC_COLOR,
        src,
        MSG_COLOR,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Json => {
      write!(
        writer,
        r#"{{"time":"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z","level":"{}","src":{:?},"msg":"#,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        level,
        src,
      )
      .expect("could not write entry begin");

      serde_json::to_writer(&mut writer, msg).expect("could not write entry msg");
    }
  }
}

/// Outputs one key/value pair of context information.
pub fn context<V: Serialize>(mut writer: impl Write, format: Format, key: &str, value: &V) {
  match format {
    Format::Plain => {
      write!(writer, " {}:", key);
      serde_json::to_writer(writer, value).expect("error serializing context");
    }

    Format::Color | Format::Modern => {
      write!(writer, " {}{}:{}", KEY_COLOR, key, VALUE_COLOR);
      serde_json::to_writer(writer, value).expect("error serializing context");
    }

    Format::Json => {
      write!(writer, ",{:?}:", key);
      serde_json::to_writer(writer, value).expect("error serializing context");
    }
  }
}

/// Outputs one key/value pair of context information in pretty (long) format.
pub fn context_pretty<V: Serialize>(mut writer: impl Write, format: Format, key: &str, value: &V) {
  match format {
    Format::Plain => {
      write!(writer, "\n{}: ", key);
      serde_json::to_writer_pretty(writer, value).expect("error serializing context");
    }

    Format::Color | Format::Modern => {
      write!(writer, "\n{}{}:{} ", KEY_COLOR, key, Color::Reset);
      serde_json::to_writer_pretty(writer, value).expect("error serializing context");
    }

    Format::Json => {
      write!(writer, ",{:?}:", key);
      serde_json::to_writer(writer, value).expect("error serializing context");
    }
  }
}

/// Outputs the end of an entry.
pub fn end_entry<W: Write>(mut writer: W, format: Format) {
  match format {
    Format::Plain => writeln!(writer),
    Format::Color | Format::Modern => writeln!(writer, "{}", Color::Reset),
    Format::Json => writeln!(writer, "}}"),
  }
  .expect("could not write end msg");
}

/// Gets the appropriate `Color` for the given `Level`.
fn color(level: Level) -> Color {
  match level {
    Level::Trace => Color::White,
    Level::Debug => Color::BrightMagenta,
    Level::Info => Color::BrightBlue,
    Level::Warn => Color::BrightYellow,
    Level::Error => Color::BrightRed,
  }
}

/// Gets the appropriate unicode icon for the given `Level`.
fn icon(level: Level) -> &'static str {
  match level {
    Level::Trace => "",
    Level::Debug => "",
    Level::Info => "",
    Level::Warn => "",
    Level::Error => "",
  }
}

/// Outputs a complete log entry with no context writer.
pub fn simple_entry<W: Write>(
  mut writer: W,
  format: Format,
  level: Level,
  src: &str,
  msg: &fmt::Arguments,
) {
  let time = chrono::Utc::now();
  let src = SrcName(src);

  match format {
    Format::Plain => {
      write!(
        writer,
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z {}{} {}",
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        level,
        src,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Color => {
      write!(
        writer,
        "{}{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{}{}{} {}{}",
        TIME_COLOR,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        color(level),
        level,
        SRC_COLOR,
        src,
        MSG_COLOR,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Modern => {
      write!(
        writer,
        "{}{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{}{}{} {}{}",
        TIME_COLOR,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        color(level),
        icon(level),
        SRC_COLOR,
        src,
        MSG_COLOR,
        msg,
      )
      .expect("could not write entry begin");
    }

    Format::Json => {
      write!(
        writer,
        r#"{{"time":"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z","level":"{}","src":{:?},"msg":"#,
        time.year(),
        time.month(),
        time.day(),
        time.hour(),
        time.minute(),
        time.second(),
        level,
        src,
      )
      .expect("could not write entry begin");

      serde_json::to_writer(&mut writer, msg).expect("could not write entry msg");
    }
  };

  end_entry(writer, format);
}