#![cfg_attr(all(feature = "json"), doc = "```no_run")]
#![cfg_attr(not(all(feature = "json")), doc = "```ignore")]
use std::sync::OnceLock;
use crate::error::Result;
use crate::level::Level;
use crate::logger::{Logger, in_dispatch};
use crate::record::{Field, Metadata};
use crate::sink::on_background_worker;
static GLOBAL: OnceLock<Logger> = OnceLock::new();
pub fn set(logger: Logger) -> core::result::Result<(), SetGlobalError> {
GLOBAL.set(logger).map_err(SetGlobalError)
}
pub fn init(logger: Logger) -> core::result::Result<FlushGuard, SetGlobalError> {
let guard = FlushGuard::new(logger.clone());
set(logger)?;
Ok(guard)
}
#[must_use]
pub fn get() -> Option<&'static Logger> {
GLOBAL.get()
}
pub fn flush() -> Result<()> {
GLOBAL.get().map_or(Ok(()), Logger::flush)
}
pub fn shutdown() -> Result<()> {
GLOBAL.get().map_or(Ok(()), Logger::shutdown)
}
pub fn install_panic_hook() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if !in_dispatch() && !on_background_worker() {
if let Some(logger) = GLOBAL.get() {
log_panic(logger, info);
}
}
previous(info);
}));
}
fn log_panic(logger: &Logger, info: &std::panic::PanicHookInfo<'_>) {
let payload = info.payload();
let message = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("panic with a non-string payload");
let thread = std::thread::current();
let thread_name = thread.name().unwrap_or("<unnamed>");
let mut metadata = Metadata::new(Level::Error, "panic");
let mut fields = [
Field::new("thread", thread_name),
Field::new("column", 0_u32),
];
let mut count = 1;
if let Some(location) = info.location() {
metadata = metadata.with_location(location.file(), location.line());
fields[1] = Field::new("column", location.column());
count = 2;
}
let _reported = logger.try_emit(metadata, message, &fields[..count]);
let _flushed = logger.flush();
}
#[must_use = "the guard flushes the logger when dropped; bind it to a named variable"]
#[derive(Debug)]
pub struct FlushGuard {
logger: Logger,
}
impl FlushGuard {
pub(crate) const fn new(logger: Logger) -> Self {
Self { logger }
}
#[must_use]
pub fn logger(&self) -> &Logger {
&self.logger
}
}
impl Drop for FlushGuard {
fn drop(&mut self) {
let _reported = self.logger.shutdown();
}
}
pub struct SetGlobalError(Logger);
impl SetGlobalError {
#[must_use]
pub fn into_inner(self) -> Logger {
self.0
}
}
impl core::fmt::Debug for SetGlobalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("SetGlobalError(..)")
}
}
impl core::fmt::Display for SetGlobalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("a global logger is already installed")
}
}
impl std::error::Error for SetGlobalError {}
#[cfg_attr(feature = "json", doc = "```")]
#[cfg_attr(not(feature = "json"), doc = "```ignore")]
#[derive(Debug)]
#[non_exhaustive]
pub enum InitError {
AlreadyInstalled,
Filter(crate::builder::BuildError),
}
impl core::fmt::Display for InitError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::AlreadyInstalled => f.write_str("a global logger is already installed"),
Self::Filter(e) => write!(f, "LOG_IO: {e}"),
}
}
}
impl std::error::Error for InitError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::AlreadyInstalled => None,
Self::Filter(e) => Some(e),
}
}
}
#[cfg(any(feature = "json", feature = "logfmt", feature = "human"))]
fn init_stdout(
sink: impl FnOnce(crate::builder::LoggerBuilder) -> crate::builder::LoggerBuilder,
) -> core::result::Result<FlushGuard, InitError> {
let logger = sink(Logger::builder().env_filter("LOG_IO"))
.try_build()
.map_err(InitError::Filter)?;
init(logger).map_err(|_| InitError::AlreadyInstalled)
}
#[cfg(feature = "json")]
pub fn init_json() -> core::result::Result<FlushGuard, InitError> {
init_stdout(crate::builder::LoggerBuilder::stdout_json)
}
#[cfg(feature = "logfmt")]
pub fn init_logfmt() -> core::result::Result<FlushGuard, InitError> {
init_stdout(crate::builder::LoggerBuilder::stdout_logfmt)
}
#[cfg(feature = "human")]
pub fn init_human() -> core::result::Result<FlushGuard, InitError> {
init_stdout(crate::builder::LoggerBuilder::stdout_human)
}