use std::io::Write;
use std::panic;
use std::sync::Once;
use std::sync::OnceLock;
use crate::Append;
use crate::Diagnostic;
use crate::Error;
use crate::Filter;
use crate::filter::FilterResult;
use crate::record::FilterCriteria;
use crate::record::Record;
static DEFAULT_LOGGER: OnceLock<Logger> = OnceLock::new();
pub fn default_logger() -> &'static Logger {
static NOP_LOGGER: Logger = Logger { dispatches: vec![] };
DEFAULT_LOGGER.get().unwrap_or(&NOP_LOGGER)
}
pub fn set_default_logger(logger: Logger) -> Result<(), Logger> {
static ATEXIT_CALLBACK: Once = Once::new();
DEFAULT_LOGGER.set(logger)?;
ATEXIT_CALLBACK.call_once(flush_default_logger_at_exit);
Ok(())
}
fn flush_default_logger_at_exit() {
extern "C" fn handler() {
if let Some(default_logger) = DEFAULT_LOGGER.get() {
default_logger.exit();
}
}
#[must_use]
fn try_atexit() -> bool {
use std::os::raw::c_int;
unsafe extern "C" {
fn atexit(cb: extern "C" fn()) -> c_int;
}
(unsafe { atexit(handler) }) == 0
}
fn hook_panic() {
let previous_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
handler();
previous_hook(info);
}));
}
if !try_atexit() {
hook_panic();
}
}
#[derive(Debug)]
pub struct Logger {
dispatches: Vec<Dispatch>,
}
impl Logger {
pub(super) fn new(dispatches: Vec<Dispatch>) -> Self {
Self { dispatches }
}
}
impl Logger {
pub fn enabled(&self, criteria: &FilterCriteria) -> bool {
self.dispatches
.iter()
.any(|dispatch| dispatch.enabled(criteria))
}
pub fn log(&self, record: &Record) {
for dispatch in &self.dispatches {
for err in dispatch.log(record) {
handle_log_error(record, &err);
}
}
}
pub fn flush(&self) {
for dispatch in &self.dispatches {
for err in dispatch.flush() {
handle_flush_error(&err);
}
}
}
pub fn exit(&self) {
for dispatch in &self.dispatches {
for err in dispatch.exit() {
handle_exit_error(&err);
}
}
}
}
#[derive(Debug)]
pub(super) struct Dispatch {
filters: Vec<Box<dyn Filter>>,
diagnostics: Vec<Box<dyn Diagnostic>>,
appends: Vec<Box<dyn Append>>,
}
impl Dispatch {
pub(super) fn new(
filters: Vec<Box<dyn Filter>>,
diagnostics: Vec<Box<dyn Diagnostic>>,
appends: Vec<Box<dyn Append>>,
) -> Self {
debug_assert!(
!appends.is_empty(),
"A Dispatch must have at least one filter"
);
Self {
filters,
diagnostics,
appends,
}
}
fn enabled(&self, criteria: &FilterCriteria) -> bool {
let diagnostics = &self.diagnostics;
for filter in &self.filters {
match filter.enabled(criteria, diagnostics) {
FilterResult::Reject => return false,
FilterResult::Accept => return true,
FilterResult::Neutral => {}
}
}
true
}
fn log(&self, record: &Record) -> Vec<Error> {
let diagnostics = &self.diagnostics;
for filter in &self.filters {
match filter.matches(record, diagnostics) {
FilterResult::Reject => return vec![],
FilterResult::Accept => break,
FilterResult::Neutral => {}
}
}
let mut errors = vec![];
for append in &self.appends {
if let Err(err) = append.append(record, diagnostics) {
errors.push(err);
}
}
errors
}
fn flush(&self) -> Vec<Error> {
let mut errors = vec![];
for append in &self.appends {
if let Err(err) = append.flush() {
errors.push(err);
}
}
errors
}
fn exit(&self) -> Vec<Error> {
let mut errors = vec![];
for append in &self.appends {
if let Err(err) = append.exit() {
errors.push(err);
}
}
errors
}
}
fn handle_log_error(record: &Record, error: &Error) {
let Err(fallback_error) = write!(
std::io::stderr(),
r###"
Error perform logging.
Attempted to log: {args}
Record: {record:?}
Error: {error:?}
"###,
args = record.payload(),
record = record,
error = error,
) else {
return;
};
panic!(
r###"
Error performing stderr logging after error occurred during regular logging.
Attempted to log: {args}
Record: {record:?}
Error: {error:?}
Fallback error: {fallback_error}
"###,
args = record.payload(),
record = record,
error = error,
fallback_error = fallback_error,
);
}
fn handle_flush_error(error: &Error) {
let Err(fallback_error) = write!(
std::io::stderr(),
r###"
Error perform flush.
Error: {error:?}
"###,
) else {
return;
};
panic!(
r###"
Error performing stderr logging after error occurred during regular flush.
Error: {error:?}
Fallback error: {fallback_error}
"###,
);
}
fn handle_exit_error(error: &Error) {
let Err(fallback_error) = write!(
std::io::stderr(),
r###"
Error perform exit.
Error: {error:?}
"###,
) else {
return;
};
panic!(
r###"
Error performing stderr logging after error occurred during atexit.
Error: {error:?}
Fallback error: {fallback_error}
"###,
);
}