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}