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