bflog 0.3.1

Very tiny logging framework made for microservices.
Documentation
use super::output;
use super::{Level, LevelFilter, LogSink};
use bytes::{BufMut, Bytes, BytesMut};
use log;
use serde::Serialize;
use std::io;
use std::io::Write;
use std::ops::Drop;
use std::sync::{Arc, Mutex, Weak};

pub use log::SetLoggerError as MakeGlobalError;

/// Sends log entries to a `LogSink`.
pub struct Logger {
  /// Source of the log entries from this logger.
  ///
  /// This could be the module name, the application name, or any other string
  /// to categorize log entries on.
  pub src: String,
  /// Maximum level of entries sent to the `LogSink`.
  ///
  /// Set to `LevelFilter::Off` to not send any entries.
  pub level: LevelFilter,
  /// Sink to send entries to.
  pub sink: Weak<LogSink>,
  /// Buffer to use when writing an entry before it is sent.
  buffer: Buffer,
}

impl Logger {
  /// Creates a new `Logger` for the given `LogSink`.
  ///
  /// The logger's `level` will initially be set to the default level of the
  /// sink. The level can be changed to override the default.
  pub fn new(sink: &Arc<LogSink>) -> Logger {
    let level = sink.default_level;

    Logger {
      src: String::default(),
      sink: Arc::downgrade(&sink),
      level,
      buffer: Buffer(BytesMut::new()),
    }
  }

  /// Creates a new `Logger` with the same `sink` and `level` but with a new
  /// `src`.
  pub fn with_src(&self, src: impl Into<String>) -> Self {
    Logger {
      src: src.into(),
      sink: self.sink.clone(),
      level: self.level,
      buffer: Buffer(BytesMut::new()),
    }
  }

  /// Sets this logger as the logging implementation for the standard `log`
  /// crate.
  pub fn make_global(self) -> Result<(), MakeGlobalError> {
    log::set_max_level(self.level);
    log::set_boxed_logger(Box::new(Adapter(Mutex::new(self))))
  }

  /// Logs a message, with optional context, and sends it to the `LogSink`.
  pub fn log(&mut self, level: Level, msg: &str) -> ContextWriter {
    let mut writer = ContextWriter {
      logger: None,
      pretty: false,
    };

    if level <= self.level {
      if let Some(sink) = self.sink.upgrade() {
        output::begin_entry(&mut self.buffer, sink.format, level, &self.src, msg);

        writer.logger = Some(self);
      }
    }

    writer
  }

  /// Sends the bytes in the buffer to the `LogSink`.
  fn send(&mut self) {
    if let Some(sink) = self.sink.upgrade() {
      sink.send(self.buffer.take_bytes());
    }
  }

  /// Logs a `Trace` level message with optional context.
  pub fn trace(&mut self, msg: &str) -> ContextWriter {
    self.log(Level::Trace, msg)
  }

  /// Logs a `Debug` level message with optional context.
  pub fn debug(&mut self, msg: &str) -> ContextWriter {
    self.log(Level::Debug, msg)
  }

  /// Logs an `Info` level message with optional context.
  pub fn info(&mut self, msg: &str) -> ContextWriter {
    self.log(Level::Info, msg)
  }

  /// Logs a `Warn` level message with optional context.
  pub fn warn(&mut self, msg: &str) -> ContextWriter {
    self.log(Level::Warn, msg)
  }

  /// Logs an `Error` level message with optional context.
  pub fn error(&mut self, msg: &str) -> ContextWriter {
    self.log(Level::Error, msg)
  }
}

// Support cloning loggers to copy settings.
impl Clone for Logger {
  fn clone(&self) -> Self {
    Logger {
      src: self.src.clone(),
      sink: self.sink.clone(),
      level: self.level,
      buffer: Buffer(BytesMut::new()),
    }
  }
}

/// Wrapper around `BytesMut` that automatically reserves enough
/// capacity for writes to succeed.
struct Buffer(BytesMut);

impl Buffer {
  /// Takes the bytes written so far.
  fn take_bytes(&mut self) -> Bytes {
    self.0.take().freeze()
  }
}

// Implements `Write` to automatically grow the buffer.
impl Write for Buffer {
  fn write(&mut self, src: &[u8]) -> io::Result<usize> {
    let len = src.len();

    self.0.reserve(len);
    self.0.put_slice(src);

    Ok(len)
  }

  fn flush(&mut self) -> io::Result<()> {
    Ok(())
  }
}

/// Temporary structure that can write additional context to a log entry.
pub struct ContextWriter<'a> {
  /// Whether the `pretty` method has been called.
  pretty: bool,
  /// The logger to write to. The value is taken in `drop` to finish the
  /// entry.
  logger: Option<&'a mut Logger>,
}

impl<'a> ContextWriter<'a> {
  /// Sets the context writer to “pretty” mode, which outputs each context
  /// item on a separate line with pretty formatting.
  pub fn pretty(&mut self) -> &mut Self {
    self.pretty = true;
    self
  }

  /// Adds a context item with a given `key` and `value` to the log entry.
  ///
  /// This method is identical to `write` but is better named when the method
  /// is repeatedly chained from a logger method.
  pub fn with<V: Serialize>(&mut self, key: &str, value: &V) -> &mut Self {
    self.write(key, value)
  }

  /// Writes a context item with a given `key` and `value` to the log entry.
  ///
  /// This method is identical to `with` but is better named when the writer is
  /// stored in a variable and used procedurally.
  pub fn write<V: Serialize>(&mut self, key: &str, value: &V) -> &mut Self {
    if let Some(ref mut logger) = self.logger {
      if let Some(sink) = logger.sink.upgrade() {
        if self.pretty {
          output::context_pretty(&mut logger.buffer, sink.format, key, value);
        } else {
          output::context(&mut logger.buffer, sink.format, key, value);
        }
      }
    }

    self
  }

  /// Completes the log entry.
  ///
  /// This method does nothing, but serves as an easy way to drop a writer
  /// stored in a variable without the need for a block.
  pub fn finish(self) {
    // See `Drop::drop` below.
  }
}

// Implements `Drop` to finish the log entry.
impl<'a> Drop for ContextWriter<'a> {
  fn drop(&mut self) {
    if let Some(ref mut logger) = self.logger {
      if let Some(sink) = logger.sink.upgrade() {
        output::end_entry(&mut logger.buffer, sink.format);
        logger.send();
      }
    }
  }
}

/// Wrapper around a `Logger` that adapts it for use with the standard `log`
/// crate macros.
struct Adapter(Mutex<Logger>);

// Implement `log::Log` to lock the mutex and a simple log entry to the sink.
impl log::Log for Adapter {
  fn enabled(&self, metadata: &log::Metadata) -> bool {
    let logger = self.0.lock().expect("could not lock logger");

    metadata.level() <= logger.level
  }

  fn log(&self, record: &log::Record) {
    let mut logger = self.0.lock().expect("could not lock logger");

    if record.level() <= logger.level {
      if let Some(sink) = logger.sink.upgrade() {
        output::simple_entry(
          &mut logger.buffer,
          sink.format,
          record.level(),
          record.target(),
          record.args(),
        );

        logger.send();
      }
    }
  }

  fn flush(&self) {}
}