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 {}