1use crate::formatters::FormatterTrait;
2use crate::record::Record;
3use chrono::Local;
4use serde_json::json;
5use std::fmt;
6use std::sync::Arc;
7
8pub type FormatFn = Arc<dyn Fn(&Record) -> String + Send + Sync>;
10
11#[derive(Clone)]
13pub struct JsonFormatter {
14 include_timestamp: bool,
16 include_level: bool,
18 include_module: bool,
20 include_location: bool,
22 pattern: String,
24 format_fn: Option<FormatFn>,
26}
27
28impl fmt::Debug for JsonFormatter {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.debug_struct("JsonFormatter")
31 .field("include_timestamp", &self.include_timestamp)
32 .field("include_level", &self.include_level)
33 .field("include_module", &self.include_module)
34 .field("include_location", &self.include_location)
35 .field("pattern", &self.pattern)
36 .field("format_fn", &"<format_fn>")
37 .finish()
38 }
39}
40
41impl Default for JsonFormatter {
42 fn default() -> Self {
43 Self {
44 include_timestamp: true,
45 include_level: true,
46 include_module: true,
47 include_location: true,
48 pattern: "{timestamp} {level} {module} {location} {message}".to_string(),
49 format_fn: None,
50 }
51 }
52}
53
54impl JsonFormatter {
55 pub fn new() -> Self {
56 Self::default()
57 }
58}
59
60impl FormatterTrait for JsonFormatter {
61 fn format(&self, record: &Record) -> String {
62 if let Some(format_fn) = &self.format_fn {
63 return format_fn(record);
64 }
65
66 if self.pattern != "{timestamp} {level} {module} {location} {message}" {
68 let mut result = self.pattern.clone();
69
70 if self.include_level {
72 result = result.replace("{level}", &record.level().to_string());
73 } else {
74 result = result.replace("{level}", "");
75 }
76
77 if self.include_module {
78 result = result.replace("{module}", record.module());
79 } else {
80 result = result.replace("{module}", "");
81 }
82
83 if self.include_location {
84 result = result.replace(
85 "{location}",
86 &format!("{}:{}", record.file(), record.line()),
87 );
88 } else {
89 result = result.replace("{location}", "");
90 }
91
92 if self.include_timestamp {
93 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
94 result = result.replace("{timestamp}", ×tamp.to_string());
95 } else {
96 result = result.replace("{timestamp}", "");
97 }
98
99 result = result.replace("{message}", record.message());
100
101 if result.ends_with('\n') {
103 result.to_string()
104 } else {
105 format!("{}\n", result)
106 }
107 } else {
108 let mut json = serde_json::Map::new();
110
111 if self.include_timestamp {
112 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
113 json.insert("timestamp".to_string(), json!(timestamp.to_string()));
114 }
115
116 if self.include_level {
117 json.insert("level".to_string(), json!(record.level().to_string()));
118 }
119
120 if self.include_module {
121 json.insert("module".to_string(), json!(record.module()));
122 }
123
124 if self.include_location {
125 json.insert(
126 "location".to_string(),
127 json!(format!("{}:{}", record.file(), record.line())),
128 );
129 }
130
131 json.insert("message".to_string(), json!(record.message()));
132
133 for (key, value) in record.metadata() {
135 json.insert(key.to_string(), json!(value));
136 }
137
138 for (key, value) in record.context() {
140 json.insert(key.to_string(), value.clone());
141 }
142
143 format!(
144 "{}\n",
145 serde_json::to_string(&json).unwrap_or_else(|_| "{}".to_string())
146 )
147 }
148 }
149
150 fn with_colors(self, _use_colors: bool) -> Self {
151 self
152 }
153
154 fn with_timestamp(mut self, include_timestamp: bool) -> Self {
155 self.include_timestamp = include_timestamp;
156 self
157 }
158
159 fn with_level(mut self, include_level: bool) -> Self {
160 self.include_level = include_level;
161 self
162 }
163
164 fn with_module(mut self, include_module: bool) -> Self {
165 self.include_module = include_module;
166 self
167 }
168
169 fn with_location(mut self, include_location: bool) -> Self {
170 self.include_location = include_location;
171 self
172 }
173
174 fn with_pattern(mut self, pattern: impl Into<String>) -> Self {
175 self.pattern = pattern.into();
176 self
177 }
178
179 fn with_format<F>(mut self, format_fn: F) -> Self
180 where
181 F: Fn(&Record) -> String + Send + Sync + 'static,
182 {
183 self.format_fn = Some(Arc::new(format_fn));
184 self
185 }
186
187 fn box_clone(&self) -> Box<dyn FormatterTrait + Send + Sync> {
188 Box::new(Self {
189 include_timestamp: self.include_timestamp,
190 include_level: self.include_level,
191 include_module: self.include_module,
192 include_location: self.include_location,
193 pattern: self.pattern.clone(),
194 format_fn: self.format_fn.clone(),
195 })
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::level::LogLevel;
203
204 #[test]
205 fn test_json_formatter_default() {
206 let formatter = JsonFormatter::default();
207 let record = Record::new(
208 LogLevel::Info,
209 "Test message",
210 Some("test".to_string()),
211 Some("test.rs".to_string()),
212 Some(42),
213 );
214
215 let formatted = formatter.format(&record);
216 assert!(formatted.contains("Test message"));
217 assert!(formatted.contains("INFO"));
218 assert!(formatted.contains("test"));
219 assert!(formatted.contains("test.rs:42"));
220 }
221
222 #[test]
223 fn test_json_formatter_no_timestamp() {
224 let formatter = JsonFormatter::default().with_timestamp(false);
225 let record = Record::new(
226 LogLevel::Info,
227 "Test message",
228 Some("test".to_string()),
229 Some("test.rs".to_string()),
230 Some(42),
231 );
232
233 let formatted = formatter.format(&record);
234 assert!(formatted.contains("Test message"));
235 assert!(formatted.contains("INFO"));
236 assert!(formatted.contains("test"));
237 assert!(formatted.contains("test.rs:42"));
238 assert!(!formatted.contains("timestamp"));
239 }
240
241 #[test]
242 fn test_json_formatter_no_level() {
243 let formatter = JsonFormatter::default().with_level(false);
244 let record = Record::new(
245 LogLevel::Info,
246 "Test message",
247 Some("test".to_string()),
248 Some("test.rs".to_string()),
249 Some(42),
250 );
251
252 let formatted = formatter.format(&record);
253 assert!(formatted.contains("Test message"));
254 assert!(!formatted.contains("INFO"));
255 assert!(formatted.contains("test"));
256 assert!(formatted.contains("test.rs:42"));
257 }
258
259 #[test]
260 fn test_json_formatter_no_module() {
261 let formatter = JsonFormatter::default().with_module(false);
262 let record = Record::new(
263 LogLevel::Info,
264 "Test message",
265 Some("test".to_string()),
266 Some("main.rs".to_string()),
267 Some(42),
268 );
269
270 let formatted = formatter.format(&record);
271 assert!(formatted.contains("Test message"));
272 assert!(formatted.contains("INFO"));
273 assert!(!formatted.contains("test"));
274 assert!(formatted.contains("main.rs:42"));
275 }
276
277 #[test]
278 fn test_json_formatter_no_location() {
279 let formatter = JsonFormatter::default().with_location(false);
280 let record = Record::new(
281 LogLevel::Info,
282 "Test message",
283 Some("test".to_string()),
284 Some("test.rs".to_string()),
285 Some(42),
286 );
287
288 let formatted = formatter.format(&record);
289 assert!(formatted.contains("Test message"));
290 assert!(formatted.contains("INFO"));
291 assert!(formatted.contains("test"));
292 assert!(!formatted.contains("test.rs:42"));
293 }
294
295 #[test]
296 fn test_json_formatter_custom_format() {
297 let formatter =
298 JsonFormatter::default().with_format(|record| format!("CUSTOM: {}", record.message()));
299 let record = Record::new(
300 LogLevel::Info,
301 "Test message",
302 Some("test".to_string()),
303 Some("test.rs".to_string()),
304 Some(42),
305 );
306
307 let formatted = formatter.format(&record);
308 assert_eq!(formatted, "CUSTOM: Test message");
309 }
310}