From 4fc1926660f84901a9266ddcd3d0227b0441264d Mon Sep 17 00:00:00 2001 From: Asuna Date: Sun, 24 Nov 2024 18:53:22 +0800 Subject: [PATCH] `JsonFormatter` outputs key-values to a new `kv` field --- spdlog/Cargo.toml | 2 +- spdlog/src/formatter/full_formatter.rs | 20 ++++---- spdlog/src/formatter/json_formatter.rs | 64 ++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/spdlog/Cargo.toml b/spdlog/Cargo.toml index 4af1ac1b..09dff181 100644 --- a/spdlog/Cargo.toml +++ b/spdlog/Cargo.toml @@ -37,7 +37,7 @@ native = [] libsystemd = ["libsystemd-sys"] multi-thread = ["crossbeam"] runtime-pattern = ["spdlog-internal"] -serde_json = ["serde", "dep:serde_json"] +serde_json = ["serde", "dep:serde_json", "value-bag/serde1"] [dependencies] arc-swap = "1.5.1" diff --git a/spdlog/src/formatter/full_formatter.rs b/spdlog/src/formatter/full_formatter.rs index 97dc37a3..125d81b8 100644 --- a/spdlog/src/formatter/full_formatter.rs +++ b/spdlog/src/formatter/full_formatter.rs @@ -62,14 +62,18 @@ impl FullFormatter { } } - fmt_with_time(ctx, record, |mut time: TimeDate| { - dest.write_str("[")?; - dest.write_str(time.full_second_str())?; - dest.write_str(".")?; - write!(dest, "{:03}", time.millisecond())?; - dest.write_str("] [")?; - Ok(()) - })?; + fmt_with_time( + ctx, + record, + |mut time: TimeDate| -> Result<(), fmt::Error> { + dest.write_str("[")?; + dest.write_str(time.full_second_str())?; + dest.write_str(".")?; + write!(dest, "{:03}", time.millisecond())?; + dest.write_str("] [")?; + Ok(()) + }, + )?; if let Some(logger_name) = record.logger_name() { dest.write_str(logger_name)?; diff --git a/spdlog/src/formatter/json_formatter.rs b/spdlog/src/formatter/json_formatter.rs index 82238f07..9994573b 100644 --- a/spdlog/src/formatter/json_formatter.rs +++ b/spdlog/src/formatter/json_formatter.rs @@ -5,7 +5,10 @@ use std::{ }; use cfg_if::cfg_if; -use serde::{ser::SerializeStruct, Serialize}; +use serde::{ + ser::{SerializeMap, SerializeStruct}, + Serialize, Serializer, +}; use crate::{ formatter::{Formatter, FormatterContext}, @@ -21,7 +24,7 @@ struct JsonRecord<'a>(&'a Record<'a>); impl Serialize for JsonRecord<'_> { fn serialize(&self, serializer: S) -> Result where - S: serde::Serializer, + S: Serializer, { let fields_len = 4 + opt_to_num(self.0.logger_name()) + opt_to_num(self.0.source_location()); @@ -40,6 +43,11 @@ impl Serialize for JsonRecord<'_> { .expect("invalid timestamp"), )?; record.serialize_field("payload", self.0.payload())?; + + if !self.0.key_values().is_empty() { + record.serialize_field("kv", &JsonKV(&self.0))?; + } + if let Some(logger_name) = self.0.logger_name() { record.serialize_field("logger", logger_name)?; } @@ -52,6 +60,22 @@ impl Serialize for JsonRecord<'_> { } } +struct JsonKV<'a>(&'a Record<'a>); + +impl<'a> Serialize for JsonKV<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let kv = self.0.key_values(); + let mut map = serializer.serialize_map(Some(kv.len()))?; + for (key, value) in kv { + map.serialize_entry(key.as_str(), &value)?; + } + map.end() + } +} + impl<'a> From<&'a Record<'a>> for JsonRecord<'a> { fn from(value: &'a Record<'a>) -> Self { JsonRecord(value) @@ -126,6 +150,13 @@ impl From for crate::Error { /// {"level":"error","timestamp":1722817541459,"payload":"something went wrong","logger":"app-component","tid":3478045} /// ``` /// +/// - If key-values are present: +/// +/// ```json +/// {"level":"info","timestamp":1722817541459,"payload":"hello, world!","kv":{"k1":123,"k2":"cool"},"tid":3478045} +/// {"level":"error","timestamp":1722817541459,"payload":"something went wrong","kv":{"k1":123,"k2":"cool"},"tid":3478045} +/// ``` +/// /// - If crate feature `source-location` is enabled: /// /// ```json @@ -193,7 +224,7 @@ mod tests { use chrono::prelude::*; use super::*; - use crate::{Level, SourceLocation, __EOL}; + use crate::{kv, Level, SourceLocation, __EOL}; #[test] fn should_format_json() { @@ -269,4 +300,31 @@ mod tests { ) ); } + + #[test] + fn should_format_json_with_kv() { + let mut dest = StringBuf::new(); + let formatter = JsonFormatter::new(); + let kvs = [ + (kv::Key::__from_static_str("k1"), kv::Value::from(114)), + (kv::Key::__from_static_str("k2"), kv::Value::from("514")), + ]; + let record = Record::new(Level::Info, "payload", None, None, &kvs); + let mut ctx = FormatterContext::new(); + formatter.format(&record, &mut dest, &mut ctx).unwrap(); + + let local_time: DateTime = record.time().into(); + + assert_eq!(ctx.style_range(), None); + assert_eq!( + dest.to_string(), + format!( + r#"{{"level":"info","timestamp":{},"payload":"{}","kv":{{"k1":114,"k2":"514"}},"tid":{}}}{}"#, + local_time.timestamp_millis(), + "payload", + record.tid(), + __EOL + ) + ); + } }