dev_logger/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8//! Convenient env_logger for testing purpose.
9//!
10//! # Example
11//!
12//! ```
13//! // In lib.rs:
14//! #[cfg(test)]
15//! dev_logger::init!();
16//!
17//! // In test function:
18//! tracing::info!(name = "message");
19//!
20//! // Set RUST_LOG=info and run the test.
21//! ```
22
23use std::io;
24use std::sync::Arc;
25use std::sync::Mutex;
26
27pub use ctor::ctor;
28use tracing_subscriber::fmt::format::FmtSpan;
29use tracing_subscriber::fmt::MakeWriter;
30use tracing_subscriber::fmt::Subscriber;
31use tracing_subscriber::EnvFilter;
32
33/// Initialize tracing and env_logger for adhoc logging (ex. in a library test)
34/// purpose.
35pub fn init() {
36    let builder = Subscriber::builder()
37        .with_env_filter(EnvFilter::from_env("LOG"))
38        .with_ansi(false)
39        .with_target(false)
40        .without_time()
41        .with_span_events(FmtSpan::ACTIVE);
42
43    builder.init();
44}
45
46/// Trace the given function using the given filter (in EnvFilter format).
47/// Return strings representing the traced logs.
48pub fn traced(filter: &str, func: impl FnOnce()) -> Vec<String> {
49    #[derive(Clone, Default)]
50    struct Output(Arc<Mutex<Vec<String>>>);
51
52    impl MakeWriter<'_> for Output {
53        type Writer = Output;
54        fn make_writer(&self) -> Self::Writer {
55            self.clone()
56        }
57    }
58
59    impl io::Write for Output {
60        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
61            let mut lines = self.0.lock().unwrap();
62            let mut s = String::from_utf8_lossy(buf).trim().to_string();
63
64            // Buck unittest targets add "_unittest" suffix to crate names
65            // that will affect the "target". Workaround it by removing the
66            // suffix.
67            if cfg!(fbcode_build) {
68                s = s.replace("_unittest: ", ": ");
69                s = s.replace("_unittest::", "::");
70            }
71
72            lines.push(s);
73            Ok(buf.len())
74        }
75        fn flush(&mut self) -> io::Result<()> {
76            Ok(())
77        }
78    }
79
80    let out = Output::default();
81    let builder = Subscriber::builder()
82        .with_env_filter(EnvFilter::new(filter))
83        .with_ansi(false)
84        .without_time()
85        .with_writer(out.clone())
86        .with_span_events(FmtSpan::ACTIVE);
87    let dispatcher = builder.finish();
88    tracing::subscriber::with_default(dispatcher, func);
89
90    let lines = out.0.lock().unwrap();
91    lines.clone()
92}
93
94/// Call `init` on startup. This is useful for tests.
95#[macro_export]
96macro_rules! init {
97    () => {
98        #[dev_logger::ctor]
99        fn dev_logger_init_ctor() {
100            dev_logger::init();
101        }
102    };
103}
104
105#[test]
106fn test_traced() {
107    let lines = traced("info", || {
108        tracing::info_span!("bar", x = 1).in_scope(|| {
109            tracing::info!("foo");
110            tracing::debug!("foo2");
111        });
112    });
113    assert_eq!(
114        lines,
115        [
116            "INFO bar{x=1}: dev_logger: enter",
117            "INFO bar{x=1}: dev_logger: foo",
118            "INFO bar{x=1}: dev_logger: exit"
119        ]
120    );
121}