1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
//! [Bunyan](https://www.npmjs.com/package/bunyan) formatting for `slog-rs`
//!
//! ```
//! #[macro_use]
//! extern crate slog;
//! extern crate slog_bunyan;
//!
//! use slog::*;
//!
//! fn main() {
//!     let root = Logger::new_root(o!("build-id" => "8dfljdf"));
//!
//!     root.set_drain(
//!             drain::stream(
//!                 std::io::stderr(),
//!                 slog_bunyan::new()
//!                 )
//!             );
//! }
//! ```
#![warn(missing_docs)]

#[macro_use]
extern crate slog;
extern crate nix;
extern crate chrono;
extern crate slog_json;

use slog_json::Json;
use slog::logger::RecordInfo;
use slog::Level;

fn get_hostname() -> String {

    let mut buf = vec!(0u8; 256);
    match nix::unistd::gethostname(&mut buf) {
        Ok(()) => {
            // TODO: BUG: use locale to known encoding?
            String::from_utf8_lossy(buf.split(|&b| b == 0).next().unwrap_or(&buf)).to_string()
        }
        Err(_) => "n/a".to_string(),
    }
}

fn level_to_string(level: Level) -> i8 {
    match level {
        Level::Critical => 60,
        Level::Error => 50,
        Level::Warning => 40,
        Level::Info => 30,
        Level::Debug => 20,
        Level::Trace => 10,
    }
}

/// Create bunyan formatter
pub fn new() -> Json {
    let mut b = Json::build();
    b.set_newlines(true)
     .add_key_values(o!(
                "pid" => nix::unistd::getpid() as usize,
                "host" => get_hostname(),
                "time" => |rinfo : &RecordInfo| {
                    rinfo.ts.to_rfc3339()
                },
                "level" => |rinfo : &RecordInfo| {
                    level_to_string(rinfo.level)
                },
                // TODO: slog loggers don't have names...
                "name" => "slog-rs",
                "v" => 0usize,
                "msg" => |rinfo : &RecordInfo| {
                    rinfo.msg.clone()
                }
            ));
    b.build()
}

#[cfg(test)]
mod test {
    use super::*;
    use super::get_hostname;
    use chrono::{TimeZone, UTC};
    use nix;
    use slog::logger::RecordInfo;
    use slog::Level;
    use slog::format::Format;

    #[test]
    fn trivial() {
        let formatter = new();

        let dt = UTC.ymd(2014, 7, 8).and_hms(9, 10, 11);

        let info = RecordInfo {
            ts: dt,
            level: Level::Info,
            msg: "message".to_string(),
        };

        let mut v = vec!();
        formatter.format(&mut v, &info, &[], &[]).unwrap();

        assert_eq!(String::from_utf8_lossy(&v),
                   "{\"pid\":".to_string() + &nix::unistd::getpid().to_string() + ",\"host\":\"" +
                   &get_hostname() +
                   "\",\"time\":\"2014-07-08T09:10:11+00:00\",\"level\":30,\"name\":\"slog-rs\",\
                    \"v\":0,\"msg\":\"message\"}\n");
    }
}