tari_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
28use chrono::{
29    format::{DelayedFormat, Fixed, Item},
30    DateTime, Local,
31};
32use log::{Level, Record};
33use serde::ser::{self, Serialize, SerializeMap};
34use std::{fmt, option, thread};
35
36#[cfg(feature = "config_parsing")]
37use crate::config::{Deserialize, Deserializers};
38use crate::encode::{Encode, Write, NEWLINE};
39
40/// The JSON encoder's configuration
41#[cfg(feature = "config_parsing")]
42#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)]
43#[serde(deny_unknown_fields)]
44pub struct JsonEncoderConfig {
45    #[serde(skip_deserializing)]
46    _p: (),
47}
48
49/// An `Encode`r which writes a JSON object.
50#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
51pub struct JsonEncoder(());
52
53impl JsonEncoder {
54    /// Returns a new `JsonEncoder` with a default configuration.
55    pub fn new() -> Self {
56        Self::default()
57    }
58}
59
60impl JsonEncoder {
61    fn encode_inner(
62        &self,
63        w: &mut dyn Write,
64        time: DateTime<Local>,
65        record: &Record,
66    ) -> anyhow::Result<()> {
67        let thread = thread::current();
68        let message = Message {
69            time: time.format_with_items(Some(Item::Fixed(Fixed::RFC3339)).into_iter()),
70            message: record.args(),
71            level: record.level(),
72            module_path: record.module_path(),
73            file: record.file(),
74            line: record.line(),
75            target: record.target(),
76            thread: thread.name(),
77            thread_id: thread_id::get(),
78            mdc: Mdc,
79        };
80        message.serialize(&mut serde_json::Serializer::new(&mut *w))?;
81        w.write_all(NEWLINE.as_bytes())?;
82        Ok(())
83    }
84}
85
86impl Encode for JsonEncoder {
87    fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()> {
88        self.encode_inner(w, Local::now(), record)
89    }
90}
91
92#[derive(serde::Serialize)]
93struct Message<'a> {
94    #[serde(serialize_with = "ser_display")]
95    time: DelayedFormat<option::IntoIter<Item<'a>>>,
96    #[serde(serialize_with = "ser_display")]
97    message: &'a fmt::Arguments<'a>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    module_path: Option<&'a str>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    file: Option<&'a str>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    line: Option<u32>,
104    level: Level,
105    target: &'a str,
106    thread: Option<&'a str>,
107    thread_id: usize,
108    mdc: Mdc,
109}
110
111fn ser_display<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
112where
113    T: fmt::Display,
114    S: ser::Serializer,
115{
116    s.collect_str(v)
117}
118
119struct Mdc;
120
121impl ser::Serialize for Mdc {
122    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123    where
124        S: ser::Serializer,
125    {
126        let mut map = serializer.serialize_map(None)?;
127
128        let mut err = Ok(());
129        log_mdc::iter(|k, v| {
130            if let Ok(()) = err {
131                err = map.serialize_key(k).and_then(|()| map.serialize_value(v));
132            }
133        });
134        err?;
135
136        map.end()
137    }
138}
139
140/// A deserializer for the `JsonEncoder`.
141///
142/// # Configuration
143///
144/// ```yaml
145/// kind: json
146/// ```
147#[cfg(feature = "config_parsing")]
148#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
149pub struct JsonEncoderDeserializer;
150
151#[cfg(feature = "config_parsing")]
152impl Deserialize for JsonEncoderDeserializer {
153    type Trait = dyn Encode;
154
155    type Config = JsonEncoderConfig;
156
157    fn deserialize(
158        &self,
159        _: JsonEncoderConfig,
160        _: &Deserializers,
161    ) -> anyhow::Result<Box<dyn Encode>> {
162        Ok(Box::new(JsonEncoder::default()))
163    }
164}
165
166#[cfg(test)]
167#[cfg(feature = "simple_writer")]
168mod test {
169    #[cfg(feature = "chrono")]
170    use chrono::{DateTime, Local};
171    use log::Level;
172
173    use super::*;
174    use crate::encode::writer::simple::SimpleWriter;
175
176    #[test]
177    fn default() {
178        let time = DateTime::parse_from_rfc3339("2016-03-20T14:22:20.644420340-08:00")
179            .unwrap()
180            .with_timezone(&Local);
181        let level = Level::Debug;
182        let target = "target";
183        let module_path = "module_path";
184        let file = "file";
185        let line = 100;
186        let message = "message";
187        let thread = "encode::json::test::default";
188        log_mdc::insert("foo", "bar");
189
190        let encoder = JsonEncoder::new();
191
192        let mut buf = vec![];
193        encoder
194            .encode_inner(
195                &mut SimpleWriter(&mut buf),
196                time,
197                &Record::builder()
198                    .level(level)
199                    .target(target)
200                    .module_path(Some(module_path))
201                    .file(Some(file))
202                    .line(Some(line))
203                    .args(format_args!("{}", message))
204                    .build(),
205            )
206            .unwrap();
207
208        let expected = format!(
209            "{{\"time\":\"{}\",\"message\":\"{}\",\"module_path\":\"{}\",\
210             \"file\":\"{}\",\"line\":{},\"level\":\"{}\",\"target\":\"{}\",\
211             \"thread\":\"{}\",\"thread_id\":{},\"mdc\":{{\"foo\":\"bar\"}}}}",
212            time.to_rfc3339(),
213            message,
214            module_path,
215            file,
216            line,
217            level,
218            target,
219            thread,
220            thread_id::get(),
221        );
222        assert_eq!(expected, String::from_utf8(buf).unwrap().trim());
223    }
224}