Rust: Slog and multiple drains

3-minute read

Slog is a structured logging library for Rustlang.
Structured logging is a concept that puts events over messages, events are logged with associated key-value data, not plain string messages. This helps in:

  • processing log files for analytics
  • searching and debugging (example: search log statements for particular user or request)

Logging

Lets create a simple logger:

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

fn main() {
    let drain = slog::Discard;

    let root_logger = slog::Logger::root(drain, o!());

    info!(root_logger, "Application started";
        "started_at" => format!("{}", chrono::Utc::now()));
}

Important terms:

  • Drain is a trait which is responsible for deciding the output/destination of the logs. Any custom log handling logic should be implemented as a Drain.
  • Context are key-value pairs that gets added into every log statement. Let’s say if you want to add the git commit version to each log statement you can add it here.
  • Logger are objects used to execute logging statements.
    For creating root logger, we need drain and context. Read complete documentation here.

Here, a root_logger object is created with drain Discard (which discards everything) and blank context (o!()).


Lets look at another example:

#[macro_use]
extern crate slog;
extern crate slog_term;
extern crate chrono;

use slog::Drain;

fn main() {
    let decorator = slog_term::TermDecorator::new().build();
    let drain = slog_term::FullFormat::new(decorator).build().fuse();

    let root_logger = slog::Logger::root(drain, o!());

    info!(root_logger, "Application started";
        "started_at" => format!("{}", chrono::Utc::now()));
}

Here, slog_term::FullFormat drain is used for terminal output with TermDecorator (Decorator is an implementing strategy of output formating in terms of IO, colors, etc.)


Since drain is a trait to customize logging to any output, it allows slog to become extensible, composable, and flexible. There are many feature slog libraries based on this trait:

You can always write your own implementation using this trait.

Multiple Drains

Drains can also be interlinked and extended according to our needs. Following is an example where there are two outputs:

  • to stdout, all log statements are sent
  • to stderr, log statements with log level greater than or equal to slog::Level::Warning are sent
#[macro_use]
extern crate slog;
extern crate slog_term;
extern crate chrono;

use slog::Drain;

fn main() {
    let drain = slog_async::Async::new(
        slog::Duplicate::new(
            slog::Filter::new(
                slog_term::FullFormat::new(
                    slog_term::PlainSyncDecorator::new(std::io::stderr(),)).build(),
                |record: &slog::Record| record.level().is_at_least(slog::Level::Warning),
            ),
            slog_term::FullFormat::new(slog_term::PlainSyncDecorator::new(std::io::stdout())).build(),
        ).fuse()
    ).build().fuse();

    let root_logger = slog::Logger::root(drain, o!());

    info!(root_logger, "Application started";
        "started_at" => format!("{}", chrono::Utc::now()));
}

Here, Async, Duplicate, Filter, FullFormat drains are used.

Log Macros and Log Levels

To log a statement/record, we use log macros. In the above examples, info! is used.

Log Levels are log categories based on urgency. For example, we have debug, info, error, etc. for self-explaining urgencies. We have different log macros for different log levels.

Extra

You can find many more examples, here.