use crate::logger::ErrorChannel;
use crate::{DeferredNow, FormatFunction};
use log::Record;
use std::{
cell::RefCell,
io::Write,
path::Path,
sync::{OnceLock, RwLock},
};
#[cfg(test)]
use std::io::Cursor;
#[cfg(test)]
use std::sync::{Arc, Mutex};
#[cfg(feature = "async")]
pub(crate) const ASYNC_FLUSH: &[u8] = b"F";
#[cfg(feature = "async")]
pub(crate) const ASYNC_SHUTDOWN: &[u8] = b"S";
#[derive(Copy, Clone, Debug)]
pub(crate) enum ErrorCode {
Write,
Flush,
#[cfg(feature = "affinity")]
BindToCore,
Format,
LogFile,
#[cfg(feature = "specfile")]
LogSpecFile,
Poison,
#[cfg(target_family = "unix")]
Symlink,
WriterSpec,
}
impl ErrorCode {
fn as_index(self) -> &'static str {
match self {
Self::Write => "write",
Self::Flush => "flush",
#[cfg(feature = "affinity")]
Self::BindToCore => "bindtocore",
Self::Format => "format",
Self::LogFile => "logfile",
#[cfg(feature = "specfile")]
Self::LogSpecFile => "logspecfile",
Self::Poison => "poison",
#[cfg(target_family = "unix")]
Self::Symlink => "symlink",
Self::WriterSpec => "writerspec",
}
}
}
pub(crate) fn eprint_err(error_code: ErrorCode, msg: &str, err: &dyn std::error::Error) {
try_writing_to_error_channel(&format!(
"[flexi_logger][ERRCODE::{code:?}] {msg}, caused by {err:?}\n \
See https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#{code_lc}",
msg = msg,
err = err,
code = error_code,
code_lc = error_code.as_index(),
));
}
pub(crate) fn eprint_msg(error_code: ErrorCode, msg: &str) {
try_writing_to_error_channel(&format!(
"[flexi_logger][ERRCODE::{code:?}] {msg}\n \
See https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#{code_lc}",
msg = msg,
code = error_code,
code_lc = error_code.as_index(),
));
}
fn error_channel() -> &'static RwLock<ErrorChannel> {
static ERROR_CHANNEL: OnceLock<RwLock<ErrorChannel>> = OnceLock::new();
ERROR_CHANNEL.get_or_init(|| RwLock::new(ErrorChannel::default()))
}
static PANIC_ON_ERROR_ERROR: OnceLock<bool> = OnceLock::new();
pub(crate) fn set_panic_on_error_channel_error(b: bool) {
PANIC_ON_ERROR_ERROR.get_or_init(|| b);
}
pub(crate) fn panic_on_error_error() -> bool {
*PANIC_ON_ERROR_ERROR.get().unwrap_or(&false)
}
fn handle_error_error(result: &Result<(), std::io::Error>) {
if result.is_err() {
assert!(
!panic_on_error_error(),
"flexi_logger panics because it ran into an error and cannot inform about it \
through its configured error output channel \
because the error output channel itself is broken. \n\
You can avoid this panic by using 'Logger::panic_if_error_channel_is_broken(false)' \
(see https://docs.rs/flexi_logger/latest/flexi_logger/struct.Logger.html#method.panic_if_error_channel_is_broken)."
);
}
}
pub(crate) fn set_error_channel(channel: ErrorChannel) {
match error_channel().write() {
Ok(mut guard) => {
*guard = channel;
}
Err(e) => {
eprint_err(ErrorCode::Poison, "Error channel cannot be set", &e);
}
}
}
fn try_writing_to_error_channel(s: &str) {
match &*(error_channel().read().unwrap()) {
ErrorChannel::StdErr => {
handle_error_error(&writeln!(std::io::stderr(), "{s}"));
}
ErrorChannel::StdOut => {
handle_error_error(&writeln!(std::io::stdout(), "{s}"));
}
ErrorChannel::File(path) => write_to_file(s, path).unwrap_or_else(|e| {
handle_error_error(&writeln!(
std::io::stderr(),
"{s}\nCan't open error output file, caused by: {e}"
));
}),
ErrorChannel::DevNull => {}
}
}
fn write_to_file(s: &str, path: &Path) -> Result<(), std::io::Error> {
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
writeln!(file, "{s}")?;
file.flush()
}
pub(crate) fn io_err(s: &'static str) -> std::io::Error {
std::io::Error::other(s)
}
pub(crate) fn buffer_with<F>(f: F)
where
F: FnOnce(&RefCell<Vec<u8>>),
{
thread_local! {
static BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(200));
}
BUFFER.with(f);
}
pub(crate) fn write_buffered(
format_function: FormatFunction,
now: &mut DeferredNow,
record: &Record,
w: &mut dyn Write,
#[cfg(test)] o_validation_buffer: Option<&Arc<Mutex<Cursor<Vec<u8>>>>>,
) -> Result<(), std::io::Error> {
let mut result: Result<(), std::io::Error> = Ok(());
buffer_with(|tl_buf| match tl_buf.try_borrow_mut() {
Ok(mut buffer) => {
(format_function)(&mut *buffer, now, record)
.unwrap_or_else(|e| eprint_err(ErrorCode::Format, "formatting failed", &e));
buffer
.write_all(b"\n")
.unwrap_or_else(|e| eprint_err(ErrorCode::Write, "writing failed", &e));
result = w.write_all(&buffer).inspect_err(|e| {
eprint_err(ErrorCode::Write, "writing failed", e);
});
#[cfg(test)]
if let Some(valbuf) = o_validation_buffer {
valbuf.lock().unwrap().write_all(&buffer).ok();
}
buffer.clear();
}
Err(_e) => {
let mut tmp_buf = Vec::<u8>::with_capacity(200);
(format_function)(&mut tmp_buf, now, record)
.unwrap_or_else(|e| eprint_err(ErrorCode::Format, "formatting failed", &e));
tmp_buf
.write_all(b"\n")
.unwrap_or_else(|e| eprint_err(ErrorCode::Write, "writing failed", &e));
result = w.write_all(&tmp_buf).inspect_err(|e| {
eprint_err(ErrorCode::Write, "writing failed", e);
});
#[cfg(test)]
if let Some(valbuf) = o_validation_buffer {
valbuf.lock().unwrap().write_all(&tmp_buf).ok();
}
}
});
result
}