logforth_layout_json/
lib.rs1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
18
19use std::fmt::Arguments;
20
21use jiff::Timestamp;
22use jiff::TimestampDisplayWithOffset;
23use jiff::tz::TimeZone;
24use logforth_core::Diagnostic;
25use logforth_core::Error;
26use logforth_core::kv::Key;
27use logforth_core::kv::Value;
28use logforth_core::kv::Visitor;
29use logforth_core::layout::Layout;
30use logforth_core::record::Record;
31use serde::Serialize;
32use serde_json::Map;
33
34#[derive(Default, Debug, Clone)]
54pub struct JsonLayout {
55 tz: Option<TimeZone>,
56}
57
58impl JsonLayout {
59 pub fn timezone(mut self, tz: TimeZone) -> Self {
70 self.tz = Some(tz);
71 self
72 }
73}
74
75struct KvCollector<'a> {
76 kvs: &'a mut Map<String, serde_json::Value>,
77}
78
79impl Visitor for KvCollector<'_> {
80 fn visit(&mut self, key: Key, value: Value) -> Result<(), Error> {
81 let key = key.into_string();
82 match serde_json::to_value(&value) {
83 Ok(value) => self.kvs.insert(key, value),
84 Err(_) => self.kvs.insert(key, value.to_string().into()),
85 };
86 Ok(())
87 }
88}
89
90#[derive(Debug, Clone, Serialize)]
91struct RecordLine<'a> {
92 #[serde(serialize_with = "serialize_timestamp")]
93 timestamp: TimestampDisplayWithOffset,
94 level: &'a str,
95 target: &'a str,
96 file: &'a str,
97 line: u32,
98 #[serde(serialize_with = "serialize_args")]
99 message: &'a Arguments<'a>,
100 #[serde(skip_serializing_if = "Map::is_empty")]
101 kvs: Map<String, serde_json::Value>,
102 #[serde(skip_serializing_if = "Map::is_empty")]
103 diags: Map<String, serde_json::Value>,
104}
105
106fn serialize_timestamp<S>(
107 timestamp: &TimestampDisplayWithOffset,
108 serializer: S,
109) -> Result<S::Ok, S::Error>
110where
111 S: serde::Serializer,
112{
113 serializer.collect_str(&format_args!("{timestamp:.6}"))
114}
115
116fn serialize_args<S>(args: &Arguments, serializer: S) -> Result<S::Ok, S::Error>
117where
118 S: serde::Serializer,
119{
120 serializer.collect_str(args)
121}
122
123impl Layout for JsonLayout {
124 fn format(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<Vec<u8>, Error> {
125 let diagnostics = diags;
126
127 let ts = Timestamp::try_from(record.time()).unwrap();
130 let tz = self.tz.clone().unwrap_or_else(TimeZone::system);
131 let offset = tz.to_offset(ts);
132 let timestamp = ts.display_with_offset(offset);
133
134 let mut kvs = Map::new();
135 let mut kvs_visitor = KvCollector { kvs: &mut kvs };
136 record.key_values().visit(&mut kvs_visitor)?;
137
138 let mut diags = Map::new();
139 let mut diags_visitor = KvCollector { kvs: &mut diags };
140 for d in diagnostics {
141 d.visit(&mut diags_visitor)?;
142 }
143
144 let record_line = RecordLine {
145 timestamp,
146 level: record.level().as_str(),
147 target: record.target(),
148 file: record.file().unwrap_or_default(),
149 line: record.line().unwrap_or_default(),
150 message: record.args(),
151 kvs,
152 diags,
153 };
154
155 Ok(serde_json::to_vec(&record_line).unwrap())
157 }
158}