std_logger/
lib.rs

1//! A crate that holds a logging implementation that logs to standard error and
2//! standard out. It uses standard error for all regular messages and standard
3//! out for requests. To initialise the logger use [`Config`].
4//!
5//! This crate provides only a logging implementation. To do actual logging use
6//! the [`log`] crate and it's various macros.
7//!
8//!
9//! # Supported Formats
10//!
11//! This crate supports the three following formats:
12//!
13//! * Logfmt, following <https://www.brandur.org/logfmt>, use
14//!   [`Config::logfmt`].
15//! * JSON, use [`Config::json`].
16//! * Google Cloud Platform structured logging using JSON, following
17//!   <https://cloud.google.com/logging/docs/structured-logging>, [`Config::gcloud`].
18//!
19//! ### Logfmt
20//!
21//! For regular messages, printed to standard error, Logfmt looks like the
22//! following:
23//!
24//! ```text
25//! ts="YYYY-MM-DDTHH:MM:SS.MICROSZ" lvl="$LOG_LEVEL" msg="$message" target="$target" module="$module"
26//!
27//! For example:
28//!
29//! ts="2018-03-24T13:48:28.820588Z" lvl="ERROR" msg="my error message" target="my_module" module="my_module"
30//! ```
31//!
32//! Requests or access messages, logged using the [`REQUEST_TARGET`] target or
33//! the [`request`] macro, are printed to standard out using following format:
34//!
35//! ```text
36//! ts="YYYY-MM-DDTHH:MM:SS.MICROSZ" lvl="INFO" msg="$message" target="request" module="$module"
37//!
38//! For example:
39//!
40//! ts="2018-03-24T13:30:28.820588Z" lvl="INFO" msg="my request message" target="request" module="my_module"
41//! ```
42//!
43//! Note: the timestamp is not printed when the *timestamp* feature is not
44//! enabled, this feature is enabled by default, see [Timestamp feature] below.
45//!
46//! ### JSON (NDJSON)
47//!
48//! Formatting using JSON uses one object per limit, which looks like the
49//! following:
50//!
51//! ```text
52//! {"timestamp":"YYYY-MM-DDTHH:MM:SS.MICROSZ","level":"$LOG_LEVEL","message":"$message","target":"$module","module":"$module"}
53//!
54//! For example:
55//!
56//! {"timestamp":"2018-03-24T13:48:28.820588Z","level":"ERROR","message":"my error message","target":"my_module","module":"my_module"}
57//! ```
58//!
59//! Note: the timestamp is not printed when the *timestamp* feature is not
60//! enabled, this feature is enabled by default, see [Timestamp feature] below.
61//!
62//! ### Gcloud (GCP JSON)
63//!
64//! Google Cloud Platform structured logging using JSON, or just gcloud for
65//! short, uses JSON for messages. Regular messages are printed to standard
66//! error and look like the following:
67//!
68//! ```text
69//! {"timestamp":"YYYY-MM-DDTHH:MM:SS.MICROSZ","severity":"$LOG_LEVEL","message":"$message","target":"$module","module":"$module"}
70//!
71//! For example:
72//!
73//! {"timestamp":"2018-03-24T13:48:28.820588Z","severity":"ERROR","message":"my error message","target":"my_module","module":"my_module"}
74//! ```
75//!
76//! Note: the timestamp is not printed when the *timestamp* feature is not
77//! enabled, this feature is enabled by default, see [Timestamp feature] below.
78//!
79//! The format for requests looks the same, but are logged to standard out.
80//! There are some things to note that are special for the gcloud format:
81//!
82//!  * Panics are logged using the `CRITICAL` severity instead of `ERROR`.
83//!  * When debug logging is enabled `sourceLocation` is added with the file and
84//!    line information to all logs, if the source information is provided.
85//!
86//!
87//! # Setting severity
88//!
89//! You can use various environment variables to change the severity (log level)
90//! of the messages to actually log and which to ignore.
91//!
92//! `LOG` and `LOG_LEVEL` can be used to set the severity to a specific value,
93//! see the [`log`]'s package [`LevelFilter`] type for available values.
94//!
95//! ```bash
96//! ## In your shell of your choice:
97//!
98//! ## Set the log severity to only print log messages with info severity or
99//! ## higher, trace and debug messages won't be printed anymore.
100//! $ LOG=info ./my_binary
101//!
102//! ## Set the log severity to only print log messages with warning severity or
103//! ## higher, informational (or lower severity) messages won't be printed
104//! ## anymore.
105//! $ LOG=warn ./my_binary
106//! ```
107//!
108//! Alternatively setting the `TRACE` variable (e.g. `TRACE=1`) sets the
109//! severity to the trace, meaning it will log everything. Setting `DEBUG` will
110//! set the severity to debug.
111//!
112//! ```bash
113//! ## In your shell of your choice:
114//!
115//! ## Enables trace logging.
116//! $ TRACE=1 ./my_binary
117//!
118//! ## Enables debug logging.
119//! $ DEBUG=1 ./my_binary
120//! ```
121//!
122//! If none of these environment variables are found it will default to an
123//! information severity.
124//!
125//! In addition to these runtime filters the [log] crate provides [compile time
126//! filters] which allows you to filter log messages at compile time.
127//!
128//! [log]: mod@log
129//! [compile time filters]: mod@log#compile-time-filters
130//!
131//! # Logging requests
132//!
133//! To log requests a special target is provided: [`REQUEST_TARGET`] and a
134//! special macro: [`request`]. This will cause the message to be logged to
135//! standard out, rather then standard error. This allows for separate
136//! processing of error messages and request logs.
137//!
138//! ```
139//! use std_logger::request;
140//!
141//! # fn main() {
142//! request!("Got a request!");
143//! # }
144//! ```
145//!
146//!
147//! # Limiting logging targets
148//!
149//! Sometimes it's useful to only log messages related to a specific target, for
150//! example when debugging a single function you might want only see messages
151//! from the module the function is in. This can be achieved by using the
152//! `LOG_TARGET` environment variable.
153//!
154//! ```bash
155//! ## In your shell of choose:
156//!
157//! ## Only log messages from your crate.
158//! $ LOG_TARGET=my_crate ./my_binary
159//!
160//! ## Only log messages from the `my_module` module in your crate.
161//! $ LOG_TARGET=my_crate::my_module ./my_binary
162//!
163//! ## Multiple log targets are also supported by separating the values by a comma.
164//! $ LOG_TARGET=my_crate::my_module,my_crate::my_other_module ./my_binary
165//!
166//! ## Very useful in combination with trace severity to get all messages you
167//! ## want, but filter out the messages for crates you're not interested in.
168//! $ LOG_LEVEL=trace LOG_TARGET=my_crate::my_module ./my_binary
169//! ```
170//!
171//! Note that [requests] and panics (with [target="panic"]) are always logged.
172//!
173//! [requests]: index.html#logging-requests
174//! [target="panic"]: PANIC_TARGET
175//!
176//!
177//! # Crate features
178//!
179//! This crate has three features:
180//! * *timestamp*, enabled by default.
181//! * *log-panic*, enabled by default.
182//! * *nightly*, disabled by default.
183//!
184//!
185//! ## Timestamp feature
186//!
187//! The *timestamp* feature adds a timestamp in front of every message. It uses
188//! the format defined in [`RFC3339`] with 6 digit microsecond precision, e.g.
189//! `2018-03-24T13:48:48.063934Z`. The timestamp is **always** logged in UTC.
190//!
191//! ### Notes
192//!
193//! This feature uses [`SystemTime`] as time source, which **is not monotonic**.
194//! This means that a log message created after an *earlier* log message can
195//! have a timestamp **before** the earlier created log message.
196//!
197//! [`SystemTime`]: std::time::SystemTime
198//!
199//!
200//! ## Log-panic feature
201//!
202//! The *log-panic* feature will log all panics using the `error` severity,
203//! rather then using the default panic handler. It will log the panic message
204//! as well as the location and a backtrace, see the log output below for an
205//! example (this example doesn't include a timestamp).
206//!
207//! ```log
208//! lvl="ERROR" msg="thread 'main' panicked at 'oops', examples/panic.rs:24" target="panic" module="" backtrace="
209//! stack backtrace:
210//!    0:        0x106ba8f74 - backtrace::backtrace::trace<closure>
211//!                         at backtrace-0.3.2/src/backtrace/mod.rs:42
212//!    1:        0x106ba49af - backtrace::capture::Backtrace::new::h54d7cfa8f40c5b43
213//!                         at backtrace-0.3.2/src/capture.rs:64
214//!    2:        0x106b9f4e6 - log_panics::init::{{closure}}
215//!                         at log-panics-1.2.0/src/lib.rs:52
216//!    3:        0x106bc6951 - std::panicking::rust_panic_with_hook::h6c19f9ba35264287
217//!                         at src/libstd/panicking.rs:612
218//!    4:        0x106b93146 - std::panicking::begin_panic<&str>
219//!                         at src/libstd/panicking.rs:572
220//!    5:        0x106b93bf1 - panic::main
221//!                         at examples/panic.rs:24
222//!    6:        0x106bc751c - __rust_maybe_catch_panic
223//!                         at src/libpanic_unwind/lib.rs:98
224//!    7:        0x106bc6c08 - std::rt::lang_start::h6f338c4ae2d58bbe
225//!                         at src/libstd/rt.rs:61
226//!    8:        0x106b93c29 - main
227//! "
228//! ```
229//!
230//! If the *timestamp* feature is enable the first line of the message will be
231//! prefixed with a timestamp as described in the [Timestamp feature].
232//!
233//!
234//! ## Nightly feature
235//!
236//! Enabling this feature enables the crate to use unstable (i.e. nightly-only)
237//! features from the compiler and standard library.
238//!
239//! Currently this enables nothing.
240//!
241//!
242//! # Examples
243//!
244//! ```
245//! # use std::time::Duration;
246//! #
247//! use log::info;
248//! use std_logger::request;
249//!
250//! fn main() {
251//!     // First thing we need to do is initialise the logger before anything
252//!     // else.
253//!     std_logger::Config::logfmt().init();
254//!
255//!     // Now we can start logging!
256//!     info!("Our application started!");
257//!
258//!     // Do useful stuff, like starting a HTTP server.
259//! #   log_handler(Request { url: "/some_page".to_owned(), status: 200,
260//! #       response_time: Duration::from_millis(100) });
261//! }
262//!
263//! # struct Request {
264//! #   url: String,
265//! #   status: u16,
266//! #   response_time: Duration,
267//! # }
268//! #
269//! /// This our example request handler, just pretend it gets called with a
270//! /// request.
271//! fn log_handler(req: Request) {
272//!     // This will be logged to standard out, rather then standard error.
273//!     request!(
274//!         url = req.url,
275//!         status = req.status,
276//!         response_time:? = req.response_time;
277//!         "HTTP request"
278//!     );
279//! }
280//! ```
281//!
282//! [`REQUEST_TARGET`]: constant.REQUEST_TARGET.html
283//! [`log`]: https://crates.io/crates/log
284//! [`RFC3339`]: https://tools.ietf.org/html/rfc3339
285//! [Timestamp feature]: #timestamp-feature
286
287#![warn(missing_debug_implementations, missing_docs, unused_results)]
288
289use std::cell::RefCell;
290use std::io::{self, IoSlice, Write};
291use std::marker::PhantomData;
292
293use log::{kv, LevelFilter, Log, Metadata, Record};
294
295mod format;
296use format::{Buffer, Format, BUFS_SIZE};
297
298mod config;
299pub use config::Config;
300
301#[cfg(feature = "timestamp")]
302mod timestamp;
303
304#[cfg(test)]
305mod tests;
306
307/// Target for logging requests.
308///
309/// The [`request`] macro provides a convenient way to log requests.
310///
311/// See the [crate level documentation] for more.
312///
313/// [crate level documentation]: index.html#logging-requests
314pub const REQUEST_TARGET: &str = "request";
315
316/// Target for logging panics.
317pub const PANIC_TARGET: &str = "panic";
318
319/// Logs a request.
320///
321/// This uses [info] level severity and the [`REQUEST_TARGET`] target to log a
322/// request. See the [crate level documentation] for more.
323///
324/// [info]: log::Level::Info
325/// [crate level documentation]: index.html#logging-requests
326#[macro_export]
327macro_rules! request {
328    ($( $arg: tt )*) => (
329        $crate::_log::log!(target: $crate::REQUEST_TARGET, $crate::_log::Level::Info, $($arg)*);
330    )
331}
332
333// Not part of the API. Only here for use in the `request!` macro.
334#[doc(hidden)]
335pub use log as _log;
336
337/// Our `Log` implementation.
338struct Logger<F, Kvs> {
339    /// The filter used to determine what messages to log.
340    filter: LevelFilter,
341    /// `add_loc` argument to `Format::format`.
342    add_loc: bool,
343    /// What logging targets to log.
344    targets: Targets,
345    /// Key-values supplied for all logs.
346    kvs: Kvs,
347    format: PhantomData<F>,
348}
349
350#[derive(Debug, Eq, PartialEq)]
351enum Targets {
352    /// Log all targets.
353    All,
354    /// Only log certain targets.
355    Only(Box<[Box<str>]>),
356}
357
358impl Targets {
359    /// Returns `true` if the `target` should be logged.
360    fn should_log(&self, target: &str) -> bool {
361        if target == REQUEST_TARGET || target == PANIC_TARGET {
362            // Always log requests and panics.
363            return true;
364        }
365        match self {
366            Targets::All => true, // All targets should be logged.
367            Targets::Only(targets) => {
368                // Log all targets that start with an allowed target. This way
369                // we can just use `LOG_TARGET=my_crate`, rather then
370                // `LOG_TARGET=my_crate::module1,my_crate::module2` etc.
371                targets
372                    .iter()
373                    .any(|log_target| target.starts_with(&**log_target))
374            }
375        }
376    }
377}
378
379impl<F, Kvs> Log for Logger<F, Kvs>
380where
381    F: Format + Sync + Send,
382    Kvs: kv::Source + Sync + Send,
383{
384    fn enabled(&self, metadata: &Metadata) -> bool {
385        self.filter >= metadata.level() && self.targets.should_log(metadata.target())
386    }
387
388    fn log(&self, record: &Record) {
389        if self.enabled(record.metadata()) {
390            log::<F, Kvs>(record, &self.kvs, self.add_loc);
391        }
392    }
393
394    fn flush(&self) {
395        // Can't flush standard error/out.
396    }
397}
398
399/// The actual logging of a record.
400#[allow(clippy::single_match_else)]
401fn log<F: Format, Kvs: kv::Source>(record: &Record, kvs: &Kvs, add_loc: bool) {
402    // Thread local buffer for logging. This way we only lock standard out/error
403    // for a single writev call and don't create half written logs.
404    thread_local! {
405        static BUF: RefCell<Buffer> = RefCell::new(Buffer::new());
406    }
407
408    BUF.with(|buf| {
409        let mut bufs = [IoSlice::new(&[]); BUFS_SIZE];
410        match buf.try_borrow_mut() {
411            Ok(mut buf) => {
412                // NOTE: keep in sync with the `Err` branch below.
413                let bufs = F::format(&mut bufs, &mut buf, record, kvs, add_loc);
414                match record.target() {
415                    REQUEST_TARGET => write_once(stdout(), bufs),
416                    _ => write_once(stderr(), bufs),
417                }
418                .unwrap_or_else(log_failure);
419            }
420            Err(_) => {
421                // NOTE: We only get to this branch if we're panicking while
422                // calling `F::format`, e.g. when a `fmt::Display` impl in the
423                // `record` panics, and the `log-panic` feature is enabled which
424                // calls `error!` and in turn this function again, while still
425                // borrowing `BUF`.
426                let mut buf = Buffer::new();
427                // NOTE: keep in sync with the `Ok` branch above.
428                let bufs = F::format(&mut bufs, &mut buf, record, kvs, add_loc);
429                match record.target() {
430                    REQUEST_TARGET => write_once(stdout(), bufs),
431                    _ => write_once(stderr(), bufs),
432                }
433                .unwrap_or_else(log_failure);
434            }
435        }
436    });
437}
438
439/// Write the entire `buf`fer into the `output` or return an error.
440#[inline]
441fn write_once<W>(mut output: W, bufs: &[IoSlice]) -> io::Result<()>
442where
443    W: Write,
444{
445    output.write_vectored(bufs).and_then(|written| {
446        let total_len = bufs.iter().map(|b| b.len()).sum();
447        if written == total_len {
448            Ok(())
449        } else {
450            // Not completely correct when going by the name alone, but it's the
451            // closest we can get to a descriptive error.
452            Err(io::Error::new(
453                io::ErrorKind::WriteZero,
454                "failed to write entire log message",
455            ))
456        }
457    })
458}
459
460/// The function that gets called when we're unable to print a message.
461#[inline(never)]
462#[cold]
463#[allow(clippy::needless_pass_by_value)]
464fn log_failure(err: io::Error) {
465    // We've just failed to log, no point in failing to log the fact that we
466    // have failed to log... So we remove our panic hook and use the default
467    // instead.
468    #[cfg(feature = "log-panic")]
469    drop(std::panic::take_hook());
470
471    panic!("unexpected error logging message: {err}")
472}
473
474// Functions to get standard out/error, which are stubbed in testing. Even
475// though the return type of the functions are different we only need them both
476// to implement `io::Write`.
477
478#[cfg(test)]
479use self::test_instruments::{stderr, stdout, LOG_OUTPUT};
480#[cfg(not(test))]
481use std::io::{stderr, stdout};
482
483// The testing variant of the functions.
484
485#[cfg(test)]
486mod test_instruments {
487    use std::io::{self, IoSlice, Write};
488    use std::mem::take;
489    use std::sync::Mutex;
490
491    /// Global log output.
492    pub(crate) static LOG_OUTPUT: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
493
494    /// Simple wrapper around a `Vec<u8>` which adds itself to `LOG_OUTPUT` when
495    /// dropped.
496    pub(crate) struct LogOutput {
497        inner: Vec<u8>,
498    }
499
500    impl Write for LogOutput {
501        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
502            self.inner.write(buf)
503        }
504
505        fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
506            self.inner.write_vectored(bufs)
507        }
508
509        fn flush(&mut self) -> io::Result<()> {
510            unreachable!()
511        }
512    }
513
514    impl Drop for LogOutput {
515        fn drop(&mut self) {
516            let buf = take(&mut self.inner);
517            LOG_OUTPUT.lock().unwrap().push(buf);
518        }
519    }
520
521    pub(crate) fn stdout() -> LogOutput {
522        LogOutput { inner: Vec::new() }
523    }
524
525    pub(crate) fn stderr() -> LogOutput {
526        LogOutput { inner: Vec::new() }
527    }
528}