10 releases

0.3.7 Jul 13, 2024
0.3.6 Jul 12, 2024
0.3.5 May 18, 2024
0.3.4 Mar 13, 2024
0.1.0 Jan 24, 2024

#365 in Rust patterns

Download history 166/week @ 2024-07-27 96/week @ 2024-08-03 155/week @ 2024-08-10 128/week @ 2024-08-17 109/week @ 2024-08-24 118/week @ 2024-08-31 151/week @ 2024-09-07 171/week @ 2024-09-14 195/week @ 2024-09-21 126/week @ 2024-09-28 118/week @ 2024-10-05 138/week @ 2024-10-12 122/week @ 2024-10-19 93/week @ 2024-10-26 136/week @ 2024-11-02 110/week @ 2024-11-09

476 downloads per month
Used in fibra

MIT license

44KB
548 lines

Logkit

Super fast, structured, scalable logging library for Rust

Crates.io MIT licensed Documentation Build Status Build Status Build Status

Hello World

#[macro_use] extern crate logkit;

fn main() {
    let mut logger = logkit::Logger::new(Some(&logkit::StdoutTarget));
    logger.mount(logkit::TimePlugin::from_millis());
    logger.mount(logkit::LevelPlugin);
    logger.mount(logkit::SourcePlugin::new());
    logkit::set_default_logger(logger);

    trace!("hello, this is a trace log");
    debug!("hello, this is a debug log");
    info!(version = "0.1.0", commit = "3291cc60"; "this is a log with two string fields");
    warn!(address = "127.0.0.1", port = 3000; "this is a log with a string and a numeric field");
    error!("this is a log with a 'println' style string {}:{}", "127.0.0.1", 3000.0);
}

Output sample:

{"time":"2024-06-30T14:43:33.236+08:00","level":"trace","msg":"hello, this is a trace log","src":"examples/hello_world.rs:10"}
{"time":"2024-06-30T14:43:33.236+08:00","level":"debug","msg":"hello, this is a debug log","src":"examples/hello_world.rs:11"}
{"time":"2024-06-30T14:43:33.236+08:00","level":"info","msg":"this is a log with two string fields","version":"0.1.0","commit":"3291cc60","src":"examples/hello_world.rs:12"}
{"time":"2024-06-30T14:43:33.236+08:00","level":"warn","msg":"this is a log with a string and a numeric field","address":"127.0.0.1","port":3000,"src":"examples/hello_world.rs:13"}
{"time":"2024-06-30T14:43:33.236+08:00","level":"error","msg":"this is a log with a 'println' style string 127.0.0.1:3000","src":"examples/hello_world.rs:14"}

Basic Syntax

Five convenient macros are available for use: trace, debug, info, warn, and error. These support the following log formats, and you can define custom macros if necessary.

#[macro_use] extern crate logkit;

trace!(); // outputs just a linebreak
trace!("plain message");
trace!("println-like message {} {}!", "Hello", "World");
trace!(name = "Alice", age = 20); // outputs only fields, no message
trace!(name = "Alice", age = 20; "separate fields and messages with semicolon");
trace!(name = "Alice", age = 20; "println-like message {} {}! with fields", "Hello", "World");

Default Logger

For convenience, we have defined a default logger that outputs messages to stderr.

#[macro_use] extern crate logkit;

assert_eq!(logkit::default_logger().level(), logkit::LEVEL_TRACE);
trace!("hello, this is a trace log");
debug!("hello, this is a debug log");

Custom Logger

fn main() {
    let mut logger = logkit::Logger::new(None);
    logger.mount(logkit::LevelPlugin); // you can add your own plugin
    logger.route(logkit::StderrTarget); // and add your custom target

    // replace the default logger
    logkit::set_default_logger(logger);
    // or use it directly like built-in macros
}

Custom Level

There are five built-in log levels: TRACE, DEBUG, INFO, WARN and ERROR. You can define your own levels, as the type is simply an alias for i32, not an enum.

pub const LEVEL_CUSTOM : logkit::Level = 10; // use any number distinct from the built-ins

#[macro_export]
macro_rules! custom {
    ($($arg:tt)*) => {{
        logkit::record!(logkit::default_logger(), LEVEL_CUSTOM, $($arg)*)
    }};
}

custom!("this is a custom log level");

Custom Encoding

We support all scalar types and many std collections, if you want to encode your own type into json, you can implement the Encode trait.

pub struct CustomStruct {
    pub key1: i32,
    pub key2: bool,
    pub key3: String,
}

impl logkit::Encode for CustomStruct {
    #[inline]
    fn encode(&self, buf: &mut Vec<u8>) {
        // format your struct into buf
        unimplemented!()
    }
}

Logging Plugin

Plugins, also known as middleware, add hooks for pre and post steps. When a logger spawns a record, the pre method is called before any fields are added to it. When the record is ready to flush, the post method is invoked before outputting to targets. You can add any fields to the record. If you decide not to continue handling the record, simply return false in pre or post. The record will not be processed further if false is returned.

#[macro_use] extern crate logkit;

// custom plugin to add 'pid' to record
pub struct PidPlugin { pub pid: u32 }

impl logkit::Plugin for PidPlugin {
    #[inline]
    fn post(&self, record: &mut logkit::Record) -> bool {
        record.append("pid", &self.pid);
        true
    }
}

fn main() {
    let mut logger = logkit::Logger::new(Some(&logkit::StderrTarget));
    logger.mount(PidPlugin { pid: std::process::id() });
    logkit::set_default_logger(logger);

    info!("you will see this log with a process id");
}
#[macro_use] extern crate logkit;

// custom plugin to filter all levels below 'info'
pub struct LimitPlugin;

impl logkit::Plugin for LimitPlugin {
    #[inline]
    fn pre(&self, record: &mut logkit::Record) -> bool {
        record.level() >= logkit::LEVEL_INFO
    }
}

fn main() {
    let mut logger = logkit::Logger::new(Some(&logkit::StderrTarget));
    logger.mount(LimitPlugin);
    logkit::set_default_logger(logger);

    debug!("this log is ignored");
    info!("you can see this log");
}

Output Target

Upon completion, a record is routed to various targets, which define the methods of outputting content. A record can be directed to multiple targets, and each target is simply required to implement the Target trait.

#[macro_use] extern crate logkit;

pub struct CustomTarget;

impl logkit::Target for CustomTarget {
    #[inline]
    fn write(&self, buf: &[u8]) {
        use std::io::Write;
        let _ = std::io::stdout().write_all(buf);
    }
}

fn main() {
    let mut logger = logkit::Logger::new(Some(&logkit::StderrTarget));
    logger.route(CustomTarget);
    logkit::set_default_logger(logger);

    info!("record will be output to both stderr and stdout now");
}

Benchmark

  • MacBook Air, Apple M2 24G, Sonoma 14.2.1
Name Time
empty_log [22.526 ns 22.541 ns 22.560 ns]
level_off [1.6941 ns 1.6989 ns 1.7050 ns]
msg_only [63.166 ns 63.172 ns 63.177 ns]
msg_format [63.238 ns 63.373 ns 63.548 ns]
fields_only [96.944 ns 96.974 ns 97.005 ns]
fields_msg [147.03 ns 147.26 ns 147.56 ns]
fields_msg_format [146.44 ns 146.51 ns 146.58 ns]
fields_ten_fields [395.31 ns 395.35 ns 395.40 ns]
  • AWS c5.2xlarge, 8C 16G, Ubuntu 22.04
Name Time
empty_log [50.761 ns 50.764 ns 50.768 ns]
level_off [4.1800 ns 4.1804 ns 4.1810 ns]
msg_only [121.12 ns 121.14 ns 121.16 ns]
msg_format [121.18 ns 121.20 ns 121.23 ns]
fields_only [177.70 ns 177.74 ns 177.77 ns]
fields_msg [264.25 ns 264.33 ns 264.45 ns]
fields_msg_format [261.80 ns 261.89 ns 261.98 ns]
fields_ten_fields [654.11 ns 654.31 ns 654.51 ns]

Dependencies

~9–16MB
~209K SLoC