Skip to main content

log_io/
global.rs

1//! The process-wide logger.
2//!
3//! Most programs build one [`Logger`] at startup and use it everywhere.
4//! [`init`] installs it globally and returns a [`FlushGuard`]; the
5//! logging macros use the global logger when called without a logger
6//! argument (`log_io::info!("ready")`).
7//!
8//! The global logger lives in a `static`, and Rust never drops statics.
9//! Keep the [`FlushGuard`] alive for the whole of `main` so buffered
10//! records are flushed (and background writers drained) when `main`
11//! returns:
12//!
13#![cfg_attr(all(feature = "json"), doc = "```no_run")]
14#![cfg_attr(not(all(feature = "json")), doc = "```ignore")]
15//! use log_io::{Level, Logger};
16//!
17//! fn main() -> Result<(), Box<dyn std::error::Error>> {
18//!     let logger = Logger::builder().level(Level::Info).stdout_json().build();
19//!     let _guard = log_io::init(logger)?;
20//!
21//!     log_io::info!("service started", port = 8080_u16);
22//!     Ok(())
23//! } // `_guard` drops here: every sink is flushed and shut down.
24//! ```
25//!
26//! Exit paths that skip destructors (`std::process::exit`, `abort`,
27//! fatal signals) also skip the guard: call [`shutdown`] before
28//! `process::exit`, and see [`install_panic_hook`] for
29//! `panic = "abort"` builds.
30
31use std::sync::OnceLock;
32
33use crate::error::Result;
34use crate::level::Level;
35use crate::logger::{Logger, in_dispatch};
36use crate::record::{Field, Metadata};
37use crate::sink::on_background_worker;
38
39static GLOBAL: OnceLock<Logger> = OnceLock::new();
40
41/// Install `logger` as the process-wide logger.
42///
43/// # Errors
44///
45/// Returns [`SetGlobalError`] (which hands the logger back) if a global
46/// logger is already installed. The global logger can be set once.
47///
48/// # Examples
49///
50/// ```
51/// use log_io::{Logger, sink::NullSink};
52///
53/// let first = Logger::builder().with_sink(NullSink::new()).build();
54/// let second = first.clone();
55/// let _ = log_io::set_global(first);
56/// assert!(log_io::set_global(second).is_err());
57/// ```
58pub fn set(logger: Logger) -> core::result::Result<(), SetGlobalError> {
59    GLOBAL.set(logger).map_err(SetGlobalError)
60}
61
62/// Install `logger` globally and return a guard that flushes and shuts
63/// down its sinks when dropped. Keep the guard alive in `main`.
64///
65/// # Errors
66///
67/// Returns [`SetGlobalError`] if a global logger is already installed.
68pub fn init(logger: Logger) -> core::result::Result<FlushGuard, SetGlobalError> {
69    let guard = FlushGuard::new(logger.clone());
70    set(logger)?;
71    Ok(guard)
72}
73
74/// The process-wide logger, if one has been installed.
75#[must_use]
76pub fn get() -> Option<&'static Logger> {
77    GLOBAL.get()
78}
79
80/// Flush the global logger's sinks. Does nothing if none is installed.
81///
82/// # Errors
83///
84/// Returns the first sink error (see [`Logger::flush`]).
85pub fn flush() -> Result<()> {
86    GLOBAL.get().map_or(Ok(()), Logger::flush)
87}
88
89/// Shut down the global logger's sinks: flush, drain background queues
90/// and join their threads. Call it before `std::process::exit`. The
91/// logger keeps working afterwards (background sinks write
92/// synchronously). Does nothing if no global logger is installed.
93///
94/// # Errors
95///
96/// Returns the first sink error (see [`Logger::shutdown`]).
97pub fn shutdown() -> Result<()> {
98    GLOBAL.get().map_or(Ok(()), Logger::shutdown)
99}
100
101/// Install a panic hook that logs every panic through the global logger
102/// and flushes it, then runs the previously installed hook.
103///
104/// The record is emitted at [`Level::Error`] with target `panic`, the
105/// panic message as its message, the panic location as its source
106/// location, and `thread` and `column` fields. Because the hook runs
107/// before unwinding (or before the process aborts under
108/// `panic = "abort"`), this preserves the final records that would
109/// otherwise sit in a buffer.
110///
111/// The hook does not touch the logger, and only runs the previous hook
112/// (which prints the panic message by default), when the panic happens
113/// inside the logging pipeline itself:
114///
115/// * in a sink or format on the panicking thread, because the
116///   panicking frame may hold a sink's lock;
117/// * on the background thread of a [`crate::sink::NonBlocking`] sink
118///   (its writer or `on_error` handler panicked), because flushing
119///   would wait for that same thread. The sink reports
120///   [`crate::Error::Closed`] afterwards and the process can still shut
121///   down.
122///
123/// Install it once, after [`init`]; installing it twice logs each panic
124/// twice.
125pub fn install_panic_hook() {
126    let previous = std::panic::take_hook();
127    std::panic::set_hook(Box::new(move |info| {
128        if !in_dispatch() && !on_background_worker() {
129            if let Some(logger) = GLOBAL.get() {
130                log_panic(logger, info);
131            }
132        }
133        previous(info);
134    }));
135}
136
137fn log_panic(logger: &Logger, info: &std::panic::PanicHookInfo<'_>) {
138    let payload = info.payload();
139    let message = payload
140        .downcast_ref::<&str>()
141        .copied()
142        .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
143        .unwrap_or("panic with a non-string payload");
144    let thread = std::thread::current();
145    let thread_name = thread.name().unwrap_or("<unnamed>");
146    let mut metadata = Metadata::new(Level::Error, "panic");
147    let mut fields = [
148        Field::new("thread", thread_name),
149        Field::new("column", 0_u32),
150    ];
151    let mut count = 1;
152    if let Some(location) = info.location() {
153        metadata = metadata.with_location(location.file(), location.line());
154        fields[1] = Field::new("column", location.column());
155        count = 2;
156    }
157    // Errors were already passed to the logger's error handler.
158    let _reported = logger.try_emit(metadata, message, &fields[..count]);
159    let _flushed = logger.flush();
160}
161
162/// Flushes and shuts down a logger's sinks when dropped.
163///
164/// Returned by [`init`] and [`Logger::flush_guard`]. Bind it to a named
165/// variable (`let _guard = ...`), not `_`, which would drop it at once.
166/// Errors during the final shutdown go to the logger's error handler.
167#[must_use = "the guard flushes the logger when dropped; bind it to a named variable"]
168#[derive(Debug)]
169pub struct FlushGuard {
170    logger: Logger,
171}
172
173impl FlushGuard {
174    pub(crate) const fn new(logger: Logger) -> Self {
175        Self { logger }
176    }
177
178    /// The logger this guard flushes.
179    #[must_use]
180    pub fn logger(&self) -> &Logger {
181        &self.logger
182    }
183}
184
185impl Drop for FlushGuard {
186    fn drop(&mut self) {
187        // Every failure was already passed to the error handler.
188        let _reported = self.logger.shutdown();
189    }
190}
191
192/// Error returned when a global logger is already installed. Holds the
193/// logger that was not installed.
194pub struct SetGlobalError(Logger);
195
196impl SetGlobalError {
197    /// Recover the logger that was not installed.
198    #[must_use]
199    pub fn into_inner(self) -> Logger {
200        self.0
201    }
202}
203
204impl core::fmt::Debug for SetGlobalError {
205    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206        f.write_str("SetGlobalError(..)")
207    }
208}
209
210impl core::fmt::Display for SetGlobalError {
211    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212        f.write_str("a global logger is already installed")
213    }
214}
215
216impl std::error::Error for SetGlobalError {}
217
218/// Error returned by [`init_json`], [`init_logfmt`] and [`init_human`].
219///
220/// # Examples
221///
222#[cfg_attr(feature = "json", doc = "```")]
223#[cfg_attr(not(feature = "json"), doc = "```ignore")]
224/// match log_io::init_json() {
225///     Ok(_guard) => { /* keep the guard alive in `main` */ }
226///     Err(log_io::InitError::AlreadyInstalled) => { /* someone else set it */ }
227///     Err(e) => eprintln!("logging not started: {e}"),
228/// }
229/// ```
230#[derive(Debug)]
231#[non_exhaustive]
232pub enum InitError {
233    /// A global logger is already installed.
234    AlreadyInstalled,
235    /// The `LOG_IO` environment variable is set but did not parse.
236    Filter(crate::builder::BuildError),
237}
238
239impl core::fmt::Display for InitError {
240    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241        match self {
242            Self::AlreadyInstalled => f.write_str("a global logger is already installed"),
243            Self::Filter(e) => write!(f, "LOG_IO: {e}"),
244        }
245    }
246}
247
248impl std::error::Error for InitError {
249    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
250        match self {
251            Self::AlreadyInstalled => None,
252            Self::Filter(e) => Some(e),
253        }
254    }
255}
256
257/// Builds a standard-output logger at `info` (or the `LOG_IO` filter
258/// when that variable is set) with `sink`, and installs it globally.
259#[cfg(any(feature = "json", feature = "logfmt", feature = "human"))]
260fn init_stdout(
261    sink: impl FnOnce(crate::builder::LoggerBuilder) -> crate::builder::LoggerBuilder,
262) -> core::result::Result<FlushGuard, InitError> {
263    let logger = sink(Logger::builder().env_filter("LOG_IO"))
264        .try_build()
265        .map_err(InitError::Filter)?;
266    init(logger).map_err(|_| InitError::AlreadyInstalled)
267}
268
269/// Install a global logger that writes JSON lines to standard output, at
270/// `info`, or with the filter in the `LOG_IO` environment variable when
271/// it is set (for example `LOG_IO=debug,hyper=warn`). Keep the returned
272/// guard alive in `main`.
273///
274/// The one-line set-up; use [`Logger::builder`] and [`init`] for anything
275/// else.
276///
277/// # Errors
278///
279/// [`InitError::AlreadyInstalled`] if a global logger is already
280/// installed, [`InitError::Filter`] if `LOG_IO` is set but does not parse.
281///
282/// # Examples
283///
284/// ```no_run
285/// fn main() -> Result<(), Box<dyn std::error::Error>> {
286///     let _guard = log_io::init_json()?;
287///     log_io::info!("listening", port = 8080_u16);
288///     Ok(())
289/// }
290/// ```
291#[cfg(feature = "json")]
292pub fn init_json() -> core::result::Result<FlushGuard, InitError> {
293    init_stdout(crate::builder::LoggerBuilder::stdout_json)
294}
295
296/// As [`init_json`], writing logfmt (`key=value`) lines.
297///
298/// # Errors
299///
300/// As [`init_json`].
301///
302/// # Examples
303///
304/// ```no_run
305/// let _guard = log_io::init_logfmt()?;
306/// log_io::info!("listening", port = 8080_u16);
307/// # Ok::<(), log_io::InitError>(())
308/// ```
309#[cfg(feature = "logfmt")]
310pub fn init_logfmt() -> core::result::Result<FlushGuard, InitError> {
311    init_stdout(crate::builder::LoggerBuilder::stdout_logfmt)
312}
313
314/// As [`init_json`], writing the human-readable layout, coloured when
315/// standard output is a terminal.
316///
317/// # Errors
318///
319/// As [`init_json`].
320///
321/// # Examples
322///
323/// ```no_run
324/// let _guard = log_io::init_human()?;
325/// log_io::info!("listening", port = 8080_u16);
326/// # Ok::<(), log_io::InitError>(())
327/// ```
328#[cfg(feature = "human")]
329pub fn init_human() -> core::result::Result<FlushGuard, InitError> {
330    init_stdout(crate::builder::LoggerBuilder::stdout_human)
331}