Skip to content

softprops/json-env-logger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

json env logger

A simple structured JSON logger for Rust


install

Add the following to your Cargo.toml file

[dependencies]
json_env_logger = "0.1"

usage

tl;dr

When you run

$ RUST_LOG=trace cargo -q run \
  --example default \
  --features iso-timestamps

You will get

{"level":"TRACE","ts":"2020-07-06T03:41:57.831Z","msg":"I am a trace","task_id":567,"thread_id":"12"}
{"level":"DEBUG","ts":"2020-07-06T03:41:57.832Z","msg":"I am a debug","foo":1}
{"level":"INFO","ts":"2020-07-06T03:41:57.832Z","msg":"I am an info"}
{"level":"WARN","ts":"2020-07-06T03:41:57.832Z","msg":"I am a warning"}
{"level":"ERROR","ts":"2020-07-06T03:41:57.832Z","msg":"I am an error"}

basic use

Like env_logger, call init before you start your logging engines.

fn main() {
    json_env_logger::init();

    log::info!("👋")
}

Run your program with RUST_LOG=info your/bin

adding more structure

The log crate is working its way towards adding first class interfaces for structured fields in its macros. Rather then reinvent that functionality, this crate embraces it. json_env_logger will serialize these key-value pairs when present. Sadly the log crate doesn't expose these macro interfaces quite yet. ...But kv-log-macro does!

[dependencies]
json_env_logger = "0.1"
kv-log-macro = "1.0"
// the macros exported in this crate are
// log compatible and will one day be merged 
// so save your future self the toil of find and replace
use kv_log_macro as log;

fn main() {
    json_env_logger::init();

    log::info!("👋", { size: "medium", age: 42 })
}

⭐ These structured fields are currently limited for now to values of type u64, i64, f64, bool, char or str

Run your program with RUST_LOG=info your/bin

iso-timestamps

By default json_env_logger uses unix epoch time for timestamps. You might prefer ISO-8601 timestamps instead. You can swap implementations with by enabling the iso-timestamps feature

[dependencies]
json_env_logger = { version = "0.1", features = ["iso-timestamps"] }
fn main() {
    json_env_logger::init();

    log::info!("👋")
}

Run your program with RUST_LOG=info your/bin

panic visibility

When panics are unavoidable, you can register a panic hook that serializes the panic to json before logging it with error!

fn main() {
    json_env_logger::init();
    json_env_logger::panic_hook();

    panic!("whoooops!")
}

Run your program with RUST_LOG=info your/bin

⭐ You can also serialize backtraces by enabling the backtrace cargo feature

faq

Why do I need structured logging?

Maybe you don't. ...But maybe you do! You might if you run applications in production in an environment whose log aggregation does useful things for you if you emit json logs such as

  • structured field based filters, an alternative to artisanal regex queries
  • aggregation statistics
  • alert automation
  • anomaly detection
  • basically anything a computer can do for you when it's logs are structured in a machine readable format

 
What use case does json_env_logger target?

Most folks on the Rust logging market start out with log. They soon find they need configurable logging so they move to env_logger. Sometimes they want env_logger but pretty logging for host local application so they move to pretty_env_logger of if you like emoji-logger.

In other cases you want to run applications in a cloud service that rewards you for emitting logs in JSON format. That's use case this targets, those coming from env_logger but would like to leverage build in JSON log parsing and discovery options their cloud provided offers for free.

 
Does one of these already exist?

A few actually. Like many crates in the Rust ecosystem, they are all good. Picking a dependency is a dance of picking your tradeoffs given an application's goals and needs.

There's slog, an entire ecosystem of logging for Rust. It's strength is that its highly configurable. It's drawback is that it's highly configurable interface can get in the way of simple cases where you just want to emit structured logs in json without a lot of ceremony.

Here's an example directly from its docs

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

use slog::Drain;
use std::sync::Mutex;

fn main() {
    let root = slog::Logger::root(
        Mutex::new(slog_json::Json::default(std::io::stderr())).map(slog::Fuse),
        o!("version" => env!("CARGO_PKG_VERSION"))
    );
}

vs

fn main() {
    json_env_logger::init();
}

slog's encouraged programming model is to pass loggers instances around as arguments. This is a good practice and allows for simple context propagation, but comes at the expense of being a much different programming model that others using the standard log crate have written code against so you may find yourself having to rewrite more code that just your program's initialization.

There's also femme which is one part pretty printer, one part JSON logger, and one part WASM JavaScript object logger. It's strength is that is indeed pretty! It's not just pretty logger and yet also not just a JSON logger. It's an assortment of things making it broadly focused rather than narrowly focused on JSON log formatting. If you only use one of those things you might be packing more than you need.

If you are migrating from env_logger's environment variable driving configuration options you are a bit out of luck. You will be finding yourself recompiling and rebuilding your application to change log levels.

 
So what are the tradeoffs of json_env_logger then?

Glad you asked. It depends on env_logger which has some opinionated defaults, some of which you might not like. For example, it logs to stderr by default. You might play for team stdout. The good news is that json_env_logger exposes its interfaces for overriding those opinions.

Some features available in env_logger json_env_logger doesn't use and those bring in extra transitive dependencies. We're aware. Luckily they are all behind env_logger feature flags and json_env_logger turns them all off! The only transient dependency is then just log which you already have if your doing any sort of logging:)

 
I have more questions

That's technically not a question but ok. Ask away by opening a GitHub issue. Thanks!

 

Doug Tangren (softprops) 2020

About

A structured JSON logger for Rust.

Resources

License

Stars

Watchers

Forks

Sponsor this project

Languages