rust_loguru/formatters/
json.rs

1use crate::formatters::{FormatFn, FormatterTrait};
2use crate::record::Record;
3use serde_json;
4use serde_json::json;
5use std::fmt;
6use std::sync::Arc;
7
8/// A JSON formatter that formats log records as JSON
9#[derive(Clone)]
10pub struct JsonFormatter {
11    /// Whether to use colors in the output
12    use_colors: bool,
13    /// Whether to include timestamps in the output
14    include_timestamp: bool,
15    /// Whether to include log levels in the output
16    include_level: bool,
17    /// Whether to include module names in the output
18    include_module: bool,
19    /// Whether to include file locations in the output
20    include_location: bool,
21    /// A custom format function
22    format_fn: Option<FormatFn>,
23}
24
25impl fmt::Debug for JsonFormatter {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        f.debug_struct("JsonFormatter")
28            .field("use_colors", &self.use_colors)
29            .field("include_timestamp", &self.include_timestamp)
30            .field("include_level", &self.include_level)
31            .field("include_module", &self.include_module)
32            .field("include_location", &self.include_location)
33            .field("format_fn", &"<format_fn>")
34            .finish()
35    }
36}
37
38impl Default for JsonFormatter {
39    fn default() -> Self {
40        Self {
41            use_colors: true,
42            include_timestamp: true,
43            include_level: true,
44            include_module: true,
45            include_location: true,
46            format_fn: None,
47        }
48    }
49}
50
51impl JsonFormatter {
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    pub fn with_colors(self, _use_colors: bool) -> Self {
57        self
58    }
59
60    pub fn with_timestamp(mut self, include_timestamp: bool) -> Self {
61        self.include_timestamp = include_timestamp;
62        self
63    }
64
65    pub fn with_level(mut self, include_level: bool) -> Self {
66        self.include_level = include_level;
67        self
68    }
69
70    pub fn with_module(mut self, include_module: bool) -> Self {
71        self.include_module = include_module;
72        self
73    }
74
75    pub fn with_location(mut self, include_location: bool) -> Self {
76        self.include_location = include_location;
77        self
78    }
79
80    pub fn with_format<F>(mut self, format_fn: F) -> Self
81    where
82        F: Fn(&Record) -> String + Send + Sync + 'static,
83    {
84        self.format_fn = Some(Arc::new(format_fn));
85        self
86    }
87}
88
89impl FormatterTrait for JsonFormatter {
90    fn fmt(&self, record: &Record) -> String {
91        if let Some(format_fn) = &self.format_fn {
92            return format_fn(record);
93        }
94
95        let mut json = json!({
96            "message": record.message(),
97        });
98
99        if self.include_timestamp {
100            json["timestamp"] = json!(record.timestamp().to_rfc3339());
101        }
102        if self.include_level {
103            json["level"] = json!(record.level().to_string());
104        }
105        if self.include_module {
106            json["module"] = json!(record.module());
107        }
108        if self.include_location {
109            json["location"] = json!(record.location().to_string());
110        }
111
112        json.to_string()
113    }
114
115    fn with_colors(&mut self, use_colors: bool) {
116        self.use_colors = use_colors;
117    }
118
119    fn with_timestamp(&mut self, include_timestamp: bool) {
120        self.include_timestamp = include_timestamp;
121    }
122
123    fn with_level(&mut self, include_level: bool) {
124        self.include_level = include_level;
125    }
126
127    fn with_module(&mut self, include_module: bool) {
128        self.include_module = include_module;
129    }
130
131    fn with_location(&mut self, include_location: bool) {
132        self.include_location = include_location;
133    }
134
135    fn with_pattern(&mut self, _pattern: String) {
136        // JSON formatter doesn't use patterns
137    }
138
139    fn with_format(&mut self, format_fn: FormatFn) {
140        self.format_fn = Some(format_fn);
141    }
142
143    fn box_clone(&self) -> Box<dyn FormatterTrait + Send + Sync> {
144        Box::new(self.clone())
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::level::LogLevel;
152
153    #[test]
154    fn test_json_formatter_default() {
155        let formatter = JsonFormatter::default();
156        let record = Record::new(
157            LogLevel::Info,
158            "Test message",
159            Some("test".to_string()),
160            Some("test.rs".to_string()),
161            Some(42),
162        );
163
164        let formatted = formatter.fmt(&record);
165        assert!(formatted.contains("Test message"));
166        assert!(formatted.contains("INFO"));
167        assert!(formatted.contains("test"));
168        assert!(formatted.contains("test.rs:42"));
169    }
170
171    #[test]
172    fn test_json_formatter_no_timestamp() {
173        let formatter = JsonFormatter::default().with_timestamp(false);
174        let record = Record::new(
175            LogLevel::Info,
176            "Test message",
177            Some("test".to_string()),
178            Some("test.rs".to_string()),
179            Some(42),
180        );
181
182        let formatted = formatter.fmt(&record);
183        assert!(formatted.contains("Test message"));
184        assert!(formatted.contains("INFO"));
185        assert!(formatted.contains("test"));
186        assert!(formatted.contains("test.rs:42"));
187        assert!(!formatted.contains("timestamp"));
188    }
189
190    #[test]
191    fn test_json_formatter_no_level() {
192        let formatter = JsonFormatter::default().with_level(false);
193        let record = Record::new(
194            LogLevel::Info,
195            "Test message",
196            Some("test".to_string()),
197            Some("test.rs".to_string()),
198            Some(42),
199        );
200
201        let formatted = formatter.fmt(&record);
202        assert!(formatted.contains("Test message"));
203        assert!(!formatted.contains("INFO"));
204        assert!(formatted.contains("test"));
205        assert!(formatted.contains("test.rs:42"));
206    }
207
208    #[test]
209    fn test_json_formatter_no_module() {
210        let formatter = JsonFormatter::default().with_module(false);
211        let record = Record::new(
212            LogLevel::Info,
213            "Test message",
214            Some("test".to_string()),
215            Some("main.rs".to_string()),
216            Some(42),
217        );
218
219        let formatted = formatter.fmt(&record);
220        assert!(formatted.contains("Test message"));
221        assert!(formatted.contains("INFO"));
222        assert!(!formatted.contains("test"));
223        assert!(formatted.contains("main.rs:42"));
224    }
225
226    #[test]
227    fn test_json_formatter_no_location() {
228        let formatter = JsonFormatter::default().with_location(false);
229        let record = Record::new(
230            LogLevel::Info,
231            "Test message",
232            Some("test".to_string()),
233            Some("test.rs".to_string()),
234            Some(42),
235        );
236
237        let formatted = formatter.fmt(&record);
238        assert!(formatted.contains("Test message"));
239        assert!(formatted.contains("INFO"));
240        assert!(formatted.contains("test"));
241    }
242
243    #[test]
244    fn test_json_formatter_custom_format() {
245        let formatter =
246            JsonFormatter::default().with_format(|record| format!("CUSTOM: {}", record.message()));
247        let record = Record::new(
248            LogLevel::Info,
249            "Test message",
250            Some("test".to_string()),
251            Some("test.rs".to_string()),
252            Some(42),
253        );
254
255        let formatted = formatter.fmt(&record);
256        assert_eq!(formatted, "CUSTOM: Test message");
257    }
258}