use crate::logger::ErrorChannel;
use crate::{DeferredNow, FormatFunction};
use log::Record;
use std::cell::RefCell;
use std::io::Write;
use std::path::Path;
use std::sync::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 ERRCODE {
Write,
Flush,
Format,
LogFile,
#[cfg(feature = "specfile")]
LogSpecFile,
Poison,
#[cfg(target_os = "linux")]
Symlink,
Time,
WriterSpec,
}
impl ERRCODE {
fn as_index(self) -> &'static str {
match self {
Self::Write => "write",
Self::Flush => "flush",
Self::Format => "format",
Self::LogFile => "logfile",
#[cfg(feature = "specfile")]
Self::LogSpecFile => "logspecfile",
Self::Poison => "poison",
#[cfg(target_os = "linux")]
Self::Symlink => "symlink",
Self::Time => "time",
Self::WriterSpec => "writerspec",
}
}
}
pub(crate) fn eprint_err(errcode: ERRCODE, msg: &str, err: &dyn std::error::Error) {
let s = 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 = errcode,
code_lc = errcode.as_index(),
);
try_to_write(&s);
}
pub(crate) fn eprint_msg(errcode: ERRCODE, msg: &str) {
let s = 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 = errcode,
code_lc = errcode.as_index(),
);
try_to_write(&s);
}
lazy_static::lazy_static! {
pub(crate) static ref ERROR_CHANNEL: RwLock<ErrorChannel> = RwLock::new(ErrorChannel::default());
}
pub(crate) fn set_error_channel(channel: ErrorChannel) {
match ERROR_CHANNEL.write() {
Ok(mut guard) => {
*guard = channel;
}
Err(e) => {
eprint_err(ERRCODE::Poison, "Error channel cannot be set", &e);
}
}
}
fn try_to_write(s: &str) {
match &*(ERROR_CHANNEL.read().unwrap()) {
ErrorChannel::StdErr => {
eprintln!("{}", s);
}
ErrorChannel::StdOut => {
println!("{}", s);
}
ErrorChannel::File(path) => try_to_write_to_file(s, path).unwrap_or_else(|e| {
eprintln!("{}", s);
eprintln!("Can't open error output file, caused by: {}", e);
}),
ErrorChannel::DevNull => {}
}
}
fn try_to_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)
}
pub(crate) fn io_err(s: &'static str) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::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(ERRCODE::Format, "formatting failed", &e));
buffer
.write_all(b"\n")
.unwrap_or_else(|e| eprint_err(ERRCODE::Write, "writing failed", &e));
result = w.write_all(&*buffer).map_err(|e| {
eprint_err(ERRCODE::Write, "writing failed", &e);
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(ERRCODE::Format, "formatting failed", &e));
tmp_buf
.write_all(b"\n")
.unwrap_or_else(|e| eprint_err(ERRCODE::Write, "writing failed", &e));
result = w.write_all(&tmp_buf).map_err(|e| {
eprint_err(ERRCODE::Write, "writing failed", &e);
e
});
#[cfg(test)]
if let Some(valbuf) = o_validation_buffer {
valbuf.lock().unwrap().write_all(&tmp_buf).ok();
}
}
});
result
}