1#![doc(html_root_url = "https://docs.rs/structured-logger/latest")]
84#![allow(clippy::needless_doctest_main)]
85
86use json::new_writer;
87use log::{kv::*, Level, LevelFilter, Metadata, Record, SetLoggerError};
88use std::{
89 collections::BTreeMap,
90 env, io,
91 time::{SystemTime, UNIX_EPOCH},
92};
93
94pub trait Writer {
104 fn write_log(&self, value: &BTreeMap<Key, Value>) -> Result<(), io::Error>;
106}
107
108pub mod async_json;
109pub mod json;
110
111pub struct Builder {
113 filter: LevelFilter,
114 default_writer: Box<dyn Writer>,
115 writers: Vec<(Target, Box<dyn Writer>)>,
116}
117
118impl Default for Builder {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124impl Builder {
125 pub fn new() -> Self {
130 Builder {
131 filter: get_env_level(),
132 default_writer: new_writer(io::stderr()),
133 writers: Vec::new(),
134 }
135 }
136
137 pub fn with_level(level: &str) -> Self {
141 Builder {
142 filter: level.parse().unwrap_or(LevelFilter::Info),
143 default_writer: new_writer(io::stderr()),
144 writers: Vec::new(),
145 }
146 }
147
148 pub fn with_default_writer(self, writer: Box<dyn Writer>) -> Self {
150 Builder {
151 filter: self.filter,
152 default_writer: writer,
153 writers: self.writers,
154 }
155 }
156
157 pub fn with_target_writer(self, targets: &str, writer: Box<dyn Writer>) -> Self {
168 let mut cfg = Builder {
169 filter: self.filter,
170 default_writer: self.default_writer,
171 writers: self.writers,
172 };
173
174 cfg.writers.push((Target::from(targets), writer));
175 cfg
176 }
177
178 pub fn build(self) -> impl log::Log {
184 Logger {
185 filter: self.filter,
186 default_writer: self.default_writer,
187 writers: self
188 .writers
189 .into_iter()
190 .map(|(t, w)| (InnerTarget::from(t), w))
191 .collect(),
192 }
193 }
194
195 pub fn init(self) {
206 self.try_init()
207 .unwrap_or_else(|err| panic!("failed to initialize the logger: {}", err));
208 }
209
210 pub fn try_init(self) -> Result<(), SetLoggerError> {
218 let filter = self.filter;
219 let logger = Box::new(self.build());
220
221 log::set_boxed_logger(logger)?;
222 log::set_max_level(filter);
223
224 #[cfg(feature = "log-panic")]
225 std::panic::set_hook(Box::new(log_panic));
226 Ok(())
227 }
228}
229
230pub fn init() {
232 Builder::new().init();
233}
234
235#[inline]
237pub fn unix_ms() -> u64 {
238 let ts = SystemTime::now()
239 .duration_since(UNIX_EPOCH)
240 .expect("system time before Unix epoch");
241 ts.as_millis() as u64
242}
243
244pub fn get_env_level() -> LevelFilter {
247 for var in &["LOG", "LOG_LEVEL", "RUST_LOG"] {
248 if let Ok(level) = env::var(var) {
249 if let Ok(level) = level.parse() {
250 return level;
251 }
252 }
253 }
254
255 if env::var("TRACE").is_ok() {
256 LevelFilter::Trace
257 } else if env::var("DEBUG").is_ok() {
258 LevelFilter::Debug
259 } else {
260 LevelFilter::Info
261 }
262}
263
264struct Logger {
265 filter: LevelFilter,
266 default_writer: Box<dyn Writer>,
267 writers: Box<[(InnerTarget, Box<dyn Writer>)]>,
268}
269
270impl Logger {
271 fn get_writer(&self, target: &str) -> &dyn Writer {
272 for t in self.writers.iter() {
273 if t.0.test(target) {
274 return t.1.as_ref();
275 }
276 }
277
278 self.default_writer.as_ref()
279 }
280
281 fn try_log(&self, record: &Record) -> Result<(), io::Error> {
282 let kvs = record.key_values();
283 let mut visitor = KeyValueVisitor(BTreeMap::new());
284 let _ = kvs.visit(&mut visitor);
285
286 visitor
287 .0
288 .insert(Key::from("target"), Value::from(record.target()));
289
290 let args = record.args();
291 let msg: String;
292 if let Some(msg) = args.as_str() {
293 visitor.0.insert(Key::from("message"), Value::from(msg));
294 } else {
295 msg = args.to_string();
296 visitor.0.insert(Key::from("message"), Value::from(&msg));
297 }
298
299 let level = record.level();
300 visitor
301 .0
302 .insert(Key::from("level"), Value::from(level.as_str()));
303
304 if level <= Level::Warn {
305 if let Some(val) = record.module_path() {
306 visitor.0.insert(Key::from("module"), Value::from(val));
307 }
308 if let Some(val) = record.file() {
309 visitor.0.insert(Key::from("file"), Value::from(val));
310 }
311 if let Some(val) = record.line() {
312 visitor.0.insert(Key::from("line"), Value::from(val));
313 }
314 }
315
316 visitor
317 .0
318 .insert(Key::from("timestamp"), Value::from(unix_ms()));
319 self.get_writer(record.target()).write_log(&visitor.0)?;
320 Ok(())
321 }
322}
323
324unsafe impl Sync for Logger {}
325unsafe impl Send for Logger {}
326
327impl log::Log for Logger {
328 fn enabled(&self, metadata: &Metadata) -> bool {
329 self.filter >= metadata.level()
330 }
331
332 fn log(&self, record: &Record) {
333 if self.enabled(record.metadata()) {
334 if let Err(err) = self.try_log(record) {
335 log_failure(format!("Logger failed to log: {}", err).as_str());
337 }
338 }
339 }
340
341 fn flush(&self) {}
342}
343
344struct Target {
345 all: bool,
346 prefix: Vec<String>,
347 items: Vec<String>,
348}
349
350impl Target {
351 fn from(targets: &str) -> Self {
352 let mut target = Target {
353 all: false,
354 prefix: Vec::new(),
355 items: Vec::new(),
356 };
357 for t in targets.split(',') {
358 let t = t.trim();
359 if t == "*" {
360 target.all = true;
361 break;
362 } else if t.ends_with('*') {
363 target.prefix.push(t.trim_end_matches('*').to_string());
364 } else {
365 target.items.push(t.to_string());
366 }
367 }
368 target
369 }
370}
371
372struct InnerTarget {
373 all: bool,
374 prefix: Box<[Box<str>]>,
375 items: Box<[Box<str>]>,
376}
377
378impl InnerTarget {
379 fn from(t: Target) -> Self {
380 InnerTarget {
381 all: t.all,
382 prefix: t.prefix.into_iter().map(|s| s.into_boxed_str()).collect(),
383 items: t.items.into_iter().map(|s| s.into_boxed_str()).collect(),
384 }
385 }
386
387 fn test(&self, target: &str) -> bool {
388 if self.all {
389 return true;
390 }
391 if self.items.iter().any(|i| i.as_ref() == target) {
392 return true;
393 }
394 if self.prefix.iter().any(|p| target.starts_with(p.as_ref())) {
395 return true;
396 }
397 false
398 }
399}
400
401struct KeyValueVisitor<'kvs>(BTreeMap<Key<'kvs>, Value<'kvs>>);
402
403impl<'kvs> VisitSource<'kvs> for KeyValueVisitor<'kvs> {
404 #[inline]
405 fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> {
406 self.0.insert(key, value);
407 Ok(())
408 }
409}
410
411pub fn log_failure(msg: &str) {
414 match serde_json::to_string(msg) {
415 Ok(msg) => {
416 eprintln!(
417 "{{\"level\":\"ERROR\",\"message\":{},\"target\":\"structured_logger\",\"timestamp\":{}}}",
418 &msg,
419 unix_ms()
420 );
421 }
422 Err(err) => {
423 panic!("log_failure serialize error: {}", err)
425 }
426 }
427}
428
429#[cfg(feature = "log-panic")]
431fn log_panic(info: &std::panic::PanicHookInfo<'_>) {
432 use std::backtrace::Backtrace;
433 use std::thread;
434
435 let mut record = log::Record::builder();
436 let thread = thread::current();
437 let thread_name = thread.name().unwrap_or("unnamed");
438 let backtrace = Backtrace::force_capture();
439
440 let key_values = [
441 ("backtrace", Value::from_debug(&backtrace)),
442 ("thread_name", Value::from(thread_name)),
443 ];
444 let key_values = key_values.as_slice();
445
446 let _ = record
447 .level(log::Level::Error)
448 .target("panic")
449 .key_values(&key_values);
450
451 if let Some(location) = info.location() {
452 let _ = record
453 .file(Some(location.file()))
454 .line(Some(location.line()));
455 };
456
457 log::logger().log(
458 &record
459 .args(format_args!("thread '{thread_name}' {info}"))
460 .build(),
461 );
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use gag::BufferRedirect;
468 use serde_json::{de, value};
469 use std::io::Read;
470
471 #[test]
472 fn unix_ms_works() {
473 let now = unix_ms();
474 assert!(now > 1670123456789_u64);
475 }
476
477 #[test]
478 fn get_env_level_works() {
479 assert_eq!(Level::Info, get_env_level());
480
481 env::set_var("LOG", "error");
482 assert_eq!(Level::Error, get_env_level());
483 env::remove_var("LOG");
484
485 env::set_var("LOG_LEVEL", "Debug");
486 assert_eq!(Level::Debug, get_env_level());
487 env::remove_var("LOG_LEVEL");
488
489 env::set_var("RUST_LOG", "WARN");
490 assert_eq!(Level::Warn, get_env_level());
491 env::remove_var("RUST_LOG");
492
493 env::set_var("TRACE", "");
494 assert_eq!(Level::Trace, get_env_level());
495 env::remove_var("TRACE");
496
497 env::set_var("DEBUG", "");
498 assert_eq!(Level::Debug, get_env_level());
499 env::remove_var("DEBUG");
500 }
501
502 #[test]
503 fn target_works() {
504 let target = InnerTarget::from(Target::from("*"));
505 assert!(target.test(""));
506 assert!(target.test("api"));
507 assert!(target.test("hello"));
508
509 let target = InnerTarget::from(Target::from("api*, file,db"));
510 assert!(!target.test(""));
511 assert!(!target.test("apx"));
512 assert!(!target.test("err"));
513 assert!(!target.test("dbx"));
514 assert!(target.test("api"));
515 assert!(target.test("apiinfo"));
516 assert!(target.test("apierr"));
517 assert!(target.test("file"));
518 assert!(target.test("db"));
519
520 let target = InnerTarget::from(Target::from("api*, file, *"));
521 assert!(target.test(""));
522 assert!(target.test("apx"));
523 assert!(target.test("err"));
524 assert!(target.test("api"));
525 assert!(target.test("apiinfo"));
526 assert!(target.test("apierr"));
527 assert!(target.test("error"));
528 }
529
530 #[test]
531 fn log_failure_works() {
532 let cases: Vec<&str> = vec!["", "\"", "hello", "\"hello >", "hello\n", "hello\r"];
533 for case in cases {
534 let buf = BufferRedirect::stderr().unwrap();
535 log_failure(case);
536 let mut msg: String = String::new();
537 buf.into_inner().read_to_string(&mut msg).unwrap();
538 let msg = msg.as_str();
539 assert_eq!('\n', msg.chars().last().unwrap());
541
542 let res = de::from_str::<BTreeMap<String, value::Value>>(msg);
543 assert!(res.is_ok());
544 let res = res.unwrap();
545 assert_eq!("ERROR", res.get("level").unwrap());
546 assert_eq!(case, res.get("message").unwrap());
547 assert_eq!("structured_logger", res.get("target").unwrap());
548 assert!(unix_ms() - 999 <= res.get("timestamp").unwrap().as_u64().unwrap());
549 }
550 }
551}