Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logging structs and vectors of structs via JSON subscriber (and Valuable) ... is broken? #3148

Open
s-m-e opened this issue Nov 20, 2024 · 1 comment

Comments

@s-m-e
Copy link

s-m-e commented Nov 20, 2024

Bug Report

Version

├── tracing v0.1.40
│   ├── tracing-attributes v0.1.27 (proc-macro)
│   └── tracing-core v0.1.32
├── tracing-subscriber v0.3.18
│   ├── tracing-core v0.1.32 (*)
│   ├── tracing-log v0.2.0
│   │   └── tracing-core v0.1.32 (*)
│   └── tracing-serde v0.1.3
│       └── tracing-core v0.1.32 (*)

Platform

Linux e2 5.15.0-124-generic #134-Ubuntu SMP Fri Sep 27 20:20:17 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

rustc 1.81.0 (eeb90cda1 2024-09-04)

Crates

[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
valuable = { version = "0.1.0", features = ["derive"] }

Description

I tried to make sense of this and this in the context of logging JSON, which I could not really make work. Minimal example:

use std::fmt;

use tracing::info;
use tracing_subscriber;
use valuable::Valuable;

#[derive(Clone, Debug, Valuable)]
struct Foo {
    bar: String,
}

impl fmt::Display for Foo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<Foo bar={}>", self.bar)
    }
}

fn vec2str<T: fmt::Display>(data: &Vec<T>) -> String {
    let mut buffer = String::new();
    for item in data {
        buffer.push_str(&format!("{}", item));
    };
    buffer
}

fn main() {
    tracing_subscriber::fmt()
        .json()
        .with_max_level(tracing::Level::DEBUG)
        .with_current_span(false)
        .init();

    // WORKS
    //   Actual output: {"timestamp":"2024-11-20T15:00:24.868450Z","level":"INFO","fields":{"msg":"Hello world!"},"target":"rusttest"}
    info!(msg = "Hello world!");

    let stuff = Foo {
        bar: "abc".to_string(),
    };
    let otherstuff: Vec<Foo> = vec![
        Foo {bar: "def".to_string()},
        Foo {bar: "ghi".to_string()},
    ];

    println!("{}", stuff);  // just debugging
    println!("{}", vec2str(&otherstuff));  // just debugging

    // DOES NOT WORK CORRECTLY:
    //   Expected: {"timestamp":"2024-11-20T15:00:24.868450Z","level":"INFO","fields":{"msg":{"bar": "abc"}},"target":"rusttest"}
    //   In reality: {"timestamp":"2024-11-20T15:00:24.868450Z","level":"INFO","fields":{"msg":"Foo { bar: \"abc\" }"},"target":"rusttest"}
    info!(msg = stuff.as_value());

    // DOES NOT WORK CORRECTLY:
    //   Expected: {"timestamp":"2024-11-20T15:00:24.868450Z","level":"INFO","fields":{"msg":[{"bar": "def"}, {"bar": "ghi"}]},"target":"rusttest"}
    //   In reality: {"timestamp":"2024-11-20T15:00:24.868450Z","level":"INFO","fields":{"msg":"[Foo { bar: \"def\" }, Foo { bar: \"ghi\" }]"},"target":"rusttest"}
   info!(msg = otherstuff.as_value());

}

The JSON output is not really valid JSON.

Am I assuming correctly that, after reading #1570 and this comment on it, what I am attempting to do is not fully supported (yet)? Or am I missing something?

@s-m-e
Copy link
Author

s-m-e commented Nov 21, 2024

Just in case someone else runs into this, workaround (for myself) for the time being:

use std::io::Error;

use serde::Serialize;
use serde_json::to_string;
use chrono;

#[derive(Serialize)]
struct Log<T> {
    timestamp: String,
    level: String,
    fields: T,
    target: String,
}

fn log<T: Serialize>(level: String, data: T) -> Result<(), Error> {
    let log: Log<T> = Log {
        timestamp: chrono::offset::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true),
        level: level,
        fields: data,
        target: "tool::log".to_string(),
    };
    println!("{}", to_string(&log)?); // JSON
    Ok(())
}

fn info_<T: Serialize>(data: T) -> Result<(), Error> {
    log("INFO".to_string(), data)?;
    Ok(())
}

#[derive(Serialize)]
struct Msg1<'a> {
    msg: &'a Foo,
}

#[derive(Serialize)]
struct Msg2<'a> {
    msg: &'a Vec<Foo>,
}

... which allows me to do ...

info_(Msg1 { msg: &stuff })?;
info_(Msg2 { msg: &otherstuff })?;

... resulting in the desired output and otherwise, well, generating output that fits nicely into what tracing is doing otherwise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant