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}