daboross/fern

JSON

Raniz85 opened this issue · 6 comments

I'm trying to implement JSON logging using fern, but I'm having issues with messages containing newlines.

See this example program:

fn main() {
    let mut dispatch = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{{ \"message\": \"{}\" }}",
                message
            ))
        })
        .chain(std::io::stdout())
        .apply().unwrap();
    log::info!("This is a single line and works fine");
    log::info!("This is mulitple lines\n and doesn't produce valid JSON");
}

And the output:

{ "message": "This is a single line and works fine" }
{ "message": "This is mulitple lines
 and doesn't produce valid JSON" }

Is there any good way of escaping the newline(s) in the message?

If you're OK with it, how about using an existing json crate? It'll have robust handling of this kind of issue.

For instance, using serde_json, I think something like the following should work:

fn main() {
    let mut dispatch = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}",
                serde_json::to_string(&serde_json::json!(
                    {
                        "message": message,
                    }
                )).expect("formatting `serde_json::Value` with string keys never fails")
            ))
        })
        .chain(std::io::stdout())
        .apply().unwrap();
    log::info!("This is a single line and works fine");
    log::info!("This is mulitple lines\n and doesn't produce valid JSON");
}

This has the disadvantage of making multiple allocations per message, but it'll be quite robust.

If serde_json is too heavy weight, then tinyjson might be better? It has a much lower compile-time cost, and the runtime will probably be slightly faster as well.

fn main() {
    let mut dispatch = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{{\"message\":{}}}",
                tinyjson::JsonValue::String(message.to_string()).stringify()
                .expect("no inf floats in a string")
            ))
        })
        .chain(std::io::stdout())
        .apply().unwrap();
    log::info!("This is a single line and works fine");
    log::info!("This is mulitple lines\n and doesn't produce valid JSON");
}

Let me know what your constraints are, and we might be able to find a better solution?

Using serde_json hadn't occured to me, but that works really well.

Thanks!

Maybe mention this in the docs? :)

I'd originally thought that structured logging would be coming to log a lot sooner and we'd be able to add builtin json/other serde logging based on that. But I think you might be right - adding documentation for doing this with regular logging would be a good idea.

Key/Value logging seems to have arrived in the log crate now.

https://docs.rs/log/0.4.8/log/struct.Record.html#method.key_values

Oh! I must have not been watching the right issues/etc. I'll have to go back and look at when that happened, and see if there's anything we want to change in fern based on that.

Thanks!

Looks like it's still unstable right now, but other logging crates have already started integrating into it.

I've opened #63 to do some initial brainstorming for how fern should support this. I probably won't have time to actually do the planning nor implementation soon, but this will track it for the future.