log4rs/encode/
json.rs

1//! An encoder which writes a JSON object.
2//!
3//! Each log event will be written as a JSON object on its own line.
4//!
5//! Requires the `json_encoder` feature.
6//!
7//! # Contents
8//!
9//! An example object (note that real output will not be pretty-printed):
10//!
11//! ```json
12//! {
13//!     "time": "2016-03-20T14:22:20.644420340-08:00",
14//!     "message": "the log message",
15//!     "module_path": "foo::bar",
16//!     "file": "foo/bar/mod.rs",
17//!     "line": 100,
18//!     "level": "INFO",
19//!     "target": "foo::bar",
20//!     "thread": "main",
21//!     "thread_id": 123,
22//!     "mdc": {
23//!         "request_id": "123e4567-e89b-12d3-a456-426655440000"
24//!     }
25//! }
26//! ```
27//! If the `log_kv` feature is enabled, an additional `attributes` field will
28//! contain a map of the record's [log::kv][log_kv] structured logging
29//! attributes.
30//!
31//! [log_kv]: https://docs.rs/log/latest/log/kv/index.html
32
33use chrono::{
34    format::{DelayedFormat, Fixed, Item},
35    DateTime, Local,
36};
37use log::{Level, Record};
38use serde::ser::{self, Serialize, SerializeMap};
39use std::{fmt, option, thread};
40
41#[cfg(feature = "config_parsing")]
42use crate::config::{Deserialize, Deserializers};
43use crate::encode::{Encode, Write, NEWLINE};
44
45/// The JSON encoder's configuration
46#[cfg(feature = "config_parsing")]
47#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)]
48#[serde(deny_unknown_fields)]
49pub struct JsonEncoderConfig {
50    #[serde(skip_deserializing)]
51    _p: (),
52}
53
54/// An `Encode`r which writes a JSON object.
55#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
56pub struct JsonEncoder(());
57
58impl JsonEncoder {
59    /// Returns a new `JsonEncoder` with a default configuration.
60    pub fn new() -> Self {
61        Self::default()
62    }
63}
64
65impl JsonEncoder {
66    fn encode_inner(
67        &self,
68        w: &mut dyn Write,
69        time: DateTime<Local>,
70        record: &Record,
71    ) -> anyhow::Result<()> {
72        let thread = thread::current();
73        let message = Message {
74            time: time.format_with_items(Some(Item::Fixed(Fixed::RFC3339)).into_iter()),
75            level: record.level(),
76            message: record.args(),
77            module_path: record.module_path(),
78            file: record.file(),
79            line: record.line(),
80            target: record.target(),
81            thread: thread.name(),
82            thread_id: thread_id::get(),
83            mdc: Mdc,
84            #[cfg(feature = "log_kv")]
85            attributes: kv::Attributes(record.key_values()),
86        };
87        message.serialize(&mut serde_json::Serializer::new(&mut *w))?;
88        w.write_all(NEWLINE.as_bytes())?;
89        Ok(())
90    }
91}
92
93impl Encode for JsonEncoder {
94    fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()> {
95        self.encode_inner(w, Local::now(), record)
96    }
97}
98
99#[derive(serde::Serialize)]
100struct Message<'a> {
101    #[serde(serialize_with = "ser_display")]
102    time: DelayedFormat<option::IntoIter<Item<'a>>>,
103    level: Level,
104    #[serde(serialize_with = "ser_display")]
105    message: &'a fmt::Arguments<'a>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    module_path: Option<&'a str>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    file: Option<&'a str>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    line: Option<u32>,
112    target: &'a str,
113    thread: Option<&'a str>,
114    thread_id: usize,
115    mdc: Mdc,
116    #[cfg(feature = "log_kv")]
117    attributes: kv::Attributes<'a>,
118}
119
120fn ser_display<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
121where
122    T: fmt::Display,
123    S: ser::Serializer,
124{
125    s.collect_str(v)
126}
127
128struct Mdc;
129
130impl ser::Serialize for Mdc {
131    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
132    where
133        S: ser::Serializer,
134    {
135        let mut map = serializer.serialize_map(None)?;
136
137        let mut err = Ok(());
138        log_mdc::iter(|k, v| {
139            if let Ok(()) = err {
140                err = map.serialize_key(k).and_then(|()| map.serialize_value(v));
141            }
142        });
143        err?;
144
145        map.end()
146    }
147}
148
149/// A deserializer for the `JsonEncoder`.
150///
151/// # Configuration
152///
153/// ```yaml
154/// kind: json
155/// ```
156#[cfg(feature = "config_parsing")]
157#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
158pub struct JsonEncoderDeserializer;
159
160#[cfg(feature = "config_parsing")]
161impl Deserialize for JsonEncoderDeserializer {
162    type Trait = dyn Encode;
163
164    type Config = JsonEncoderConfig;
165
166    fn deserialize(
167        &self,
168        _: JsonEncoderConfig,
169        _: &Deserializers,
170    ) -> anyhow::Result<Box<dyn Encode>> {
171        Ok(Box::<JsonEncoder>::default())
172    }
173}
174#[cfg(feature = "log_kv")]
175mod kv {
176    use log::kv::VisitSource;
177    use serde::ser::{self, Error, SerializeMap};
178
179    pub(crate) struct Attributes<'a>(pub &'a dyn log::kv::Source);
180
181    pub(crate) struct SerializerVisitor<T: ser::SerializeMap>(pub T);
182
183    impl<'kvs, T: ser::SerializeMap> VisitSource<'kvs> for SerializerVisitor<T> {
184        fn visit_pair(
185            &mut self,
186            key: log::kv::Key<'kvs>,
187            value: log::kv::Value<'kvs>,
188        ) -> Result<(), log::kv::Error> {
189            self.0
190                .serialize_entry(key.as_str(), &value.to_string())
191                .map_err(|e| log::kv::Error::boxed(e.to_string()))?;
192            Ok(())
193        }
194    }
195
196    impl<'a> ser::Serialize for Attributes<'a> {
197        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198        where
199            S: serde::Serializer,
200        {
201            let map = serializer.serialize_map(Some(self.0.count()))?;
202            let mut visitor = SerializerVisitor(map);
203            self.0.visit(&mut visitor).map_err(S::Error::custom)?;
204            visitor.0.end()
205        }
206    }
207}
208
209#[cfg(test)]
210#[cfg(feature = "simple_writer")]
211mod test {
212    #[cfg(feature = "chrono")]
213    use chrono::{DateTime, Local};
214    use log::Level;
215
216    use super::*;
217    use crate::encode::writer::simple::SimpleWriter;
218
219    #[test]
220    fn default() {
221        let time = DateTime::parse_from_rfc3339("2016-03-20T14:22:20.644420340-08:00")
222            .unwrap()
223            .with_timezone(&Local);
224        let level = Level::Debug;
225        let target = "target";
226        let module_path = "module_path";
227        let file = "file";
228        let line = 100;
229        let message = "message";
230        let thread = "encode::json::test::default";
231        log_mdc::insert("foo", "bar");
232
233        let encoder = JsonEncoder::new();
234
235        let mut record_builder = Record::builder();
236        record_builder
237            .level(level)
238            .target(target)
239            .module_path(Some(module_path))
240            .file(Some(file))
241            .line(Some(line));
242
243        #[cfg(feature = "log_kv")]
244        record_builder.key_values(&[("log_foo", "log_bar")]);
245
246        let mut buf = vec![];
247        encoder
248            .encode_inner(
249                &mut SimpleWriter(&mut buf),
250                time,
251                &record_builder.args(format_args!("{}", message)).build(),
252            )
253            .unwrap();
254
255        #[cfg(feature = "log_kv")]
256        let expected_attributes = ",\"attributes\":{\"log_foo\":\"log_bar\"}";
257        #[cfg(not(feature = "log_kv"))]
258        let expected_attributes = "";
259
260        let expected = format!(
261            "{{\"time\":\"{}\",\"level\":\"{}\",\"message\":\"{}\",\"module_path\":\"{}\",\
262            \"file\":\"{}\",\"line\":{},\"target\":\"{}\",\
263            \"thread\":\"{}\",\"thread_id\":{},\"mdc\":{{\"foo\":\"bar\"}}{}}}",
264            time.to_rfc3339(),
265            level,
266            message,
267            module_path,
268            file,
269            line,
270            target,
271            thread,
272            thread_id::get(),
273            expected_attributes
274        );
275        assert_eq!(expected, String::from_utf8(buf).unwrap().trim());
276    }
277}