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//! * *serde1*, disabled by default.
184//!
185//!
186//! ## Timestamp feature
187//!
188//! The *timestamp* feature adds a timestamp in front of every message. It uses
189//! the format defined in [`RFC3339`] with 6 digit microsecond precision, e.g.
190//! `2018-03-24T13:48:48.063934Z`. The timestamp is **always** logged in UTC.
191//!
192//! ### Notes
193//!
194//! This feature uses [`SystemTime`] as time source, which **is not monotonic**.
195//! This means that a log message created after an *earlier* log message can
196//! have a timestamp **before** the earlier created log message.
197//!
198//! [`SystemTime`]: std::time::SystemTime
199//!
200//!
201//! ## Log-panic feature
202//!
203//! The *log-panic* feature will log all panics using the `error` severity,
204//! rather then using the default panic handler. It will log the panic message
205//! as well as the location and a backtrace, see the log output below for an
206//! example (this example doesn't include a timestamp).
207//!
208//! ```log
209//! lvl="ERROR" msg="thread 'main' panicked at 'oops', examples/panic.rs:24" target="panic" module="" backtrace="
210//! stack backtrace:
211//!    0:        0x106ba8f74 - backtrace::backtrace::trace<closure>
212//!                         at backtrace-0.3.2/src/backtrace/mod.rs:42
213//!    1:        0x106ba49af - backtrace::capture::Backtrace::new::h54d7cfa8f40c5b43
214//!                         at backtrace-0.3.2/src/capture.rs:64
215//!    2:        0x106b9f4e6 - log_panics::init::{{closure}}
216//!                         at log-panics-1.2.0/src/lib.rs:52
217//!    3:        0x106bc6951 - std::panicking::rust_panic_with_hook::h6c19f9ba35264287
218//!                         at src/libstd/panicking.rs:612
219//!    4:        0x106b93146 - std::panicking::begin_panic<&str>
220//!                         at src/libstd/panicking.rs:572
221//!    5:        0x106b93bf1 - panic::main
222//!                         at examples/panic.rs:24
223//!    6:        0x106bc751c - __rust_maybe_catch_panic
224//!                         at src/libpanic_unwind/lib.rs:98
225//!    7:        0x106bc6c08 - std::rt::lang_start::h6f338c4ae2d58bbe
226//!                         at src/libstd/rt.rs:61
227//!    8:        0x106b93c29 - main
228//! "
229//! ```
230//!
231//! If the *timestamp* feature is enable the first line of the message will be
232//! prefixed with a timestamp as described in the [Timestamp feature].
233//!
234//!
235//! ## Nightly feature
236//!
237//! Enabling this feature enables the crate to use unstable (i.e. nightly-only)
238//! features from the compiler and standard library.
239//!
240//! Currently this enables nothing.
241//!
242//! ## Serde1 feature
243//!
244//! Enables the use of [serde] version 1. Mainly this allows the value of
245//! key-value pairs to be structured.
246//!
247//! [serde]: https://crates.io/crates/serde
248//!
249//! # Examples
250//!
251//! ```
252//! # use std::time::Duration;
253//! #
254//! use log::info;
255//! use std_logger::request;
256//!
257//! fn main() {
258//!     // First thing we need to do is initialise the logger before anything
259//!     // else.
260//!     std_logger::Config::logfmt().init();
261//!
262//!     // Now we can start logging!
263//!     info!("Our application started!");
264//!
265//!     // Do useful stuff, like starting a HTTP server.
266//! #   log_handler(Request { url: "/some_page".to_owned(), status: 200,
267//! #       response_time: Duration::from_millis(100) });
268//! }
269//!
270//! # struct Request {
271//! #   url: String,
272//! #   status: u16,
273//! #   response_time: Duration,
274//! # }
275//! #
276//! /// This our example request handler, just pretend it gets called with a
277//! /// request.
278//! fn log_handler(req: Request) {
279//!     // This will be logged to standard out, rather then standard error.
280//!     request!(
281//!         url = req.url,
282//!         status = req.status,
283//!         response_time:? = req.response_time;
284//!         "HTTP request"
285//!     );
286//! }
287//! ```
288//!
289//! [`REQUEST_TARGET`]: constant.REQUEST_TARGET.html
290//! [`log`]: https://crates.io/crates/log
291//! [`RFC3339`]: https://tools.ietf.org/html/rfc3339
292//! [Timestamp feature]: #timestamp-feature
293
294#![warn(missing_debug_implementations, missing_docs, unused_results)]
295
296use std::cell::RefCell;
297use std::io::{self, IoSlice, Write};
298use std::marker::PhantomData;
299
300use log::{kv, LevelFilter, Log, Metadata, Record};
301
302mod format;
303use format::{Buffer, Format, BUFS_SIZE};
304
305mod config;
306pub use config::Config;
307
308#[cfg(feature = "timestamp")]
309mod timestamp;
310
311#[cfg(test)]
312mod tests;
313
314/// Target for logging requests.
315///
316/// The [`request`] macro provides a convenient way to log requests.
317///
318/// See the [crate level documentation] for more.
319///
320/// [crate level documentation]: index.html#logging-requests
321pub const REQUEST_TARGET: &str = "request";
322
323/// Target for logging panics.
324pub const PANIC_TARGET: &str = "panic";
325
326/// Logs a request.
327///
328/// This uses [info] level severity and the [`REQUEST_TARGET`] target to log a
329/// request. See the [crate level documentation] for more.
330///
331/// [info]: log::Level::Info
332/// [crate level documentation]: index.html#logging-requests
333#[macro_export]
334macro_rules! request {
335    ($( $arg: tt )*) => (
336        $crate::_log::log!(target: $crate::REQUEST_TARGET, $crate::_log::Level::Info, $($arg)*);
337    )
338}
339
340// Not part of the API. Only here for use in the `request!` macro.
341#[doc(hidden)]
342pub use log as _log;
343
344/// Our `Log` implementation.
345struct Logger<F, Kvs> {
346    /// The filter used to determine what messages to log.
347    filter: LevelFilter,
348    /// `add_loc` argument to `Format::format`.
349    add_loc: bool,
350    /// What logging targets to log.
351    targets: Targets,
352    /// Key-values supplied for all logs.
353    kvs: Kvs,
354    format: PhantomData<F>,
355}
356
357#[derive(Debug, Eq, PartialEq)]
358enum Targets {
359    /// Log all targets.
360    All,
361    /// Only log certain targets.
362    Only(Box<[Box<str>]>),
363}
364
365impl Targets {
366    /// Returns `true` if the `target` should be logged.
367    fn should_log(&self, target: &str) -> bool {
368        if target == REQUEST_TARGET || target == PANIC_TARGET {
369            // Always log requests and panics.
370            return true;
371        }
372        match self {
373            Targets::All => true, // All targets should be logged.
374            Targets::Only(targets) => {
375                // Log all targets that start with an allowed target. This way
376                // we can just use `LOG_TARGET=my_crate`, rather then
377                // `LOG_TARGET=my_crate::module1,my_crate::module2` etc.
378                targets
379                    .iter()
380                    .any(|log_target| target.starts_with(&**log_target))
381            }
382        }
383    }
384}
385
386impl<F, Kvs> Log for Logger<F, Kvs>
387where
388    F: Format + Sync + Send,
389    Kvs: kv::Source + Sync + Send,
390{
391    fn enabled(&self, metadata: &Metadata) -> bool {
392        self.filter >= metadata.level() && self.targets.should_log(metadata.target())
393    }
394
395    fn log(&self, record: &Record) {
396        if self.enabled(record.metadata()) {
397            log::<F, Kvs>(record, &self.kvs, self.add_loc);
398        }
399    }
400
401    fn flush(&self) {
402        // Can't flush standard error/out.
403    }
404}
405
406/// The actual logging of a record.
407#[allow(clippy::single_match_else)]
408fn log<F: Format, Kvs: kv::Source>(record: &Record, kvs: &Kvs, add_loc: bool) {
409    // Thread local buffer for logging. This way we only lock standard out/error
410    // for a single writev call and don't create half written logs.
411    thread_local! {
412        static BUF: RefCell<Buffer> = RefCell::new(Buffer::new());
413    }
414
415    BUF.with(|buf| {
416        let mut bufs = [IoSlice::new(&[]); BUFS_SIZE];
417        match buf.try_borrow_mut() {
418            Ok(mut buf) => {
419                // NOTE: keep in sync with the `Err` branch below.
420                let bufs = F::format(&mut bufs, &mut buf, record, kvs, add_loc);
421                match record.target() {
422                    REQUEST_TARGET => write_once(stdout(), bufs),
423                    _ => write_once(stderr(), bufs),
424                }
425                .unwrap_or_else(log_failure);
426            }
427            Err(_) => {
428                // NOTE: We only get to this branch if we're panicking while
429                // calling `F::format`, e.g. when a `fmt::Display` impl in the
430                // `record` panics, and the `log-panic` feature is enabled which
431                // calls `error!` and in turn this function again, while still
432                // borrowing `BUF`.
433                let mut buf = Buffer::new();
434                // NOTE: keep in sync with the `Ok` branch above.
435                let bufs = F::format(&mut bufs, &mut buf, record, kvs, add_loc);
436                match record.target() {
437                    REQUEST_TARGET => write_once(stdout(), bufs),
438                    _ => write_once(stderr(), bufs),
439                }
440                .unwrap_or_else(log_failure);
441            }
442        }
443    });
444}
445
446/// Write the entire `buf`fer into the `output` or return an error.
447#[inline]
448fn write_once<W>(mut output: W, bufs: &[IoSlice]) -> io::Result<()>
449where
450    W: Write,
451{
452    output.write_vectored(bufs).and_then(|written| {
453        let total_len = bufs.iter().map(|b| b.len()).sum();
454        if written == total_len {
455            Ok(())
456        } else {
457            // Not completely correct when going by the name alone, but it's the
458            // closest we can get to a descriptive error.
459            Err(io::Error::new(
460                io::ErrorKind::WriteZero,
461                "failed to write entire log message",
462            ))
463        }
464    })
465}
466
467/// The function that gets called when we're unable to print a message.
468#[inline(never)]
469#[cold]
470#[allow(clippy::needless_pass_by_value)]
471fn log_failure(err: io::Error) {
472    // We've just failed to log, no point in failing to log the fact that we
473    // have failed to log... So we remove our panic hook and use the default
474    // instead.
475    #[cfg(feature = "log-panic")]
476    drop(std::panic::take_hook());
477
478    panic!("unexpected error logging message: {err}")
479}
480
481// Functions to get standard out/error, which are stubbed in testing. Even
482// though the return type of the functions are different we only need them both
483// to implement `io::Write`.
484
485#[cfg(test)]
486use self::test_instruments::{stderr, stdout, LOG_OUTPUT};
487#[cfg(not(test))]
488use std::io::{stderr, stdout};
489
490// The testing variant of the functions.
491
492#[cfg(test)]
493mod test_instruments {
494    use std::io::{self, IoSlice, Write};
495    use std::mem::take;
496    use std::sync::Mutex;
497
498    /// Global log output.
499    pub(crate) static LOG_OUTPUT: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
500
501    /// Simple wrapper around a `Vec<u8>` which adds itself to `LOG_OUTPUT` when
502    /// dropped.
503    pub(crate) struct LogOutput {
504        inner: Vec<u8>,
505    }
506
507    impl Write for LogOutput {
508        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
509            self.inner.write(buf)
510        }
511
512        fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
513            self.inner.write_vectored(bufs)
514        }
515
516        fn flush(&mut self) -> io::Result<()> {
517            unreachable!()
518        }
519    }
520
521    impl Drop for LogOutput {
522        fn drop(&mut self) {
523            let buf = take(&mut self.inner);
524            LOG_OUTPUT.lock().unwrap().push(buf);
525        }
526    }
527
528    pub(crate) fn stdout() -> LogOutput {
529        LogOutput { inner: Vec::new() }
530    }
531
532    pub(crate) fn stderr() -> LogOutput {
533        LogOutput { inner: Vec::new() }
534    }
535}