mod r#macro; pub mod r#trait;
pub mod structs;
pub mod webwriter;
mod size_limit;
mod signals;
use lazy_init::Lazy;
use r#macro::logging_parts;
use lazy_static::lazy_static;
use size_limit::CircularFileBuffer;
use r#trait::WriteImmut;
use structs::{PoisonErrorWrapper, ErrorWrapper};
use url::Url;
use webwriter::WebWriter;
use std::collections::HashMap;
use smallvec::{SmallVec, smallvec};
use log::{LevelFilter, Metadata, Record};
use std::fmt::{Display, Formatter};
use std::sync::{RwLock, Arc};
use chrono::{DateTime, Utc};
use std::io::{stderr, stdout, ErrorKind, Write};
use std::fs::OpenOptions;
use std::path::Path;
pub (crate) use std::io::{Error as IOError, Result as IOResult};
#[doc(hidden)]
pub mod __internal_redirects {
pub use log::{trace, debug, info, warn, error};
}
pub use log::Level;
pub use arcs_logging_rs_proc_macro::with_target;
use const_format::concatcp;
macro_rules! color_text_fmt {
(bold $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
format_args!(
"{}{}{}",
concatcp!("\x1b[1m", "\x1b[", $color_sequence, "m"),
format_args!($formatting_lit, $text),
"\x1b[0m",
)
};
($color_sequence:literal, $formatting_lit:literal, $text:expr) => {
format_args!(
"{}{}{}",
concatcp!("\x1b[", $color_sequence, "m"),
format_args!($formatting_lit, $text),
"\x1b[0m",
)
};
(option bold $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
{
let module_path = $text;
module_path.and(
Some(format_args!(
"{}{}{}",
concatcp!("\x1b[1m", "\x1b[", $color_sequence, "m"),
format!($formatting_lit, $text.unwrap()),
"\x1b[0m",
))
)
}
};
(option $color_sequence:literal, $formatting_lit:literal, $text:expr) => {
{
let module_path = $text;
module_path.and(
Some(format_args!(
"{}{}{}",
concatcp!("\x1b[", $color_sequence, "m"),
format!($formatting_lit, $text.unwrap()),
"\x1b[0m",
))
)
}
};
}
pub type LogLocationTargetMap<'a> = HashMap<Level, SmallVec<[LogLocationTarget<'a>; 6]>>;
#[derive(Debug)]
pub enum LogLocationTarget<'a> {
StdOut,
StdErr,
File(&'a Path, Option<u64>),
WebWriter(&'a str),
}
#[derive(Debug, Clone)]
enum WritableLogLocationTarget {
StdOut,
StdErr,
File(Arc<RwLock<CircularFileBuffer>>),
WebWriter(Arc<WebWriter>),
}
impl WriteImmut for WritableLogLocationTarget {
fn write(&self, buf: &[u8]) -> IOResult<usize> {
match self {
WritableLogLocationTarget::StdOut => Write::write(&mut stdout(), buf),
WritableLogLocationTarget::StdErr => Write::write(&mut stderr(), buf),
WritableLogLocationTarget::File(f) => f.write().map_or_else(
|err| {
Err(IOError::new(
ErrorKind::Other,
PoisonErrorWrapper::from(err),
))
},
|mut file| file.write(buf),
),
WritableLogLocationTarget::WebWriter(w) => w.add_line(buf),
}
}
fn write_vectored(&self, bufs: &[std::io::IoSlice<'_>]) -> IOResult<usize> {
self.write(
bufs.iter()
.find(|buf| !buf.is_empty())
.map_or(&[][..], |buf| &**buf),
)
}
fn is_write_vectored(&self) -> bool {
false
}
fn flush(&self) -> IOResult<()> {
match self {
WritableLogLocationTarget::StdOut => stdout().flush(),
WritableLogLocationTarget::StdErr => stdout().flush(),
WritableLogLocationTarget::File(f) => f.write().map_or_else(
|err| {
Err(IOError::new(
ErrorKind::Other,
PoisonErrorWrapper::from(err),
))
},
|mut file| {
file.flush()
},
),
WritableLogLocationTarget::WebWriter(w) => w.flush(),
}
}
}
impl Write for WritableLogLocationTarget {
fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
WriteImmut::write(self, buf)
}
fn flush(&mut self) -> IOResult<()> {
WriteImmut::flush(self)
}
}
#[derive(Debug, Default)]
pub struct WritableLogLocationTargetMap(HashMap<Level, SmallVec<[WritableLogLocationTarget; 6]>>);
impl Display for WritableLogLocationTargetMap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:#?}", self))
}
}
struct FileLogger {
targets: RwLock<WritableLogLocationTargetMap>,
name: Lazy<&'static str>,
file_prefix: Lazy<(String, String)>,
}
impl FileLogger {
fn clear_targets(&self) {
use std::borrow::BorrowMut;
if let Ok(mut targets) = self.targets.write() {
let prev_map: WritableLogLocationTargetMap = std::mem::replace(
targets.borrow_mut(),
WritableLogLocationTargetMap(HashMap::new()),
);
drop(prev_map);
}
}
}
struct LevelStringStruct {
error: String,
warn: String,
info: String,
debug: String,
trace: String,
}
impl LevelStringStruct {
fn get_level(&self, level: Level) -> &str {
use Level::*;
match level {
Error => &self.error,
Warn => &self.warn,
Info => &self.info,
Debug => &self.debug,
Trace => &self.trace,
}
}
}
impl Default for LevelStringStruct {
fn default() -> Self {
Self {
error: color_text_fmt!(bold "31", "{:<5}", "ERROR").to_string(),
warn: color_text_fmt!(bold "33", "{:<5}", "WARN ").to_string(),
info: color_text_fmt!(bold "36", "{:<5}", "INFO ").to_string(),
debug: color_text_fmt!(bold "32", "{:<5}", "DEBUG").to_string(),
trace: color_text_fmt!(bold "35", "{:<5}", "TRACE").to_string(),
}
}
}
lazy_static! {
static ref LEVEL_STRINGS: LevelStringStruct = LevelStringStruct::default();
}
impl log::Log for FileLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
Some(&metadata.target()) == self.name.get()
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) { return; }
let target_map = match self.targets.read() {
Ok(guard) => guard,
Err(e) => {
eprintln!("Logging target poisoned! {}", e);
return;
}
};
let utc: DateTime<Utc> = Utc::now();
let (level, args) = (record.level(), record.args());
for target in target_map.0.get(&record.level()).into_iter().flatten() {
let stripped = with_path_prefix_stripped(record.file(), self.file_prefix.get());
let full: Option<String> = stripped.map(|(prefix, body)| [prefix, body].into_iter().collect());
let log_result = logging_parts!(
target; <==
"{} " - Some(color_text_fmt!("38;5;147", "{}", utc.format("%b %d"))),
"{}" - Some(color_text_fmt!("38;5;86", "{}", utc.format("%H:%M:%S"))),
"{} | " - Some(color_text_fmt!("38;5;23", "{}", utc.format("%.3f"))),
"{} " - color_text_fmt!(option "38;5;47", "{}", record.module_path()),
"{}" - color_text_fmt!(option "38;5;159", "{}", full.as_ref())
=> ":{}" - color_text_fmt!(option "38;5;159", "{:<3}", record.line())
=> * "; ",
"{} - " - Some(LEVEL_STRINGS.get_level(level)),
"{}" - Some(args),
);
if let Err(error) = log_result {
eprintln!("Failed to log to x! Error: {:?}", error);
}
}
}
fn flush(&self) {
let target_map = match self.targets.read() {
Ok(guard) => guard,
Err(e) => {
eprintln!("Logging target poisoned! {}", e);
return;
}
};
for target in target_map.0.values().flatten() {
if let Err(error) = target.flush() {
eprintln!("Failed to flush to x! Error: {:?}", error);
}
}
}
}
lazy_static! {
static ref LOGGER: FileLogger = FileLogger {
targets: RwLock::default(),
name: Lazy::new(),
file_prefix: Lazy::new(),
};
}
pub fn with_path_prefix_stripped<'a>(path: Option<&'a str>, prefix: Option<&'a (String, String)>) -> Option<(&'a str, &'a str)> {
if let (Some(path), Some((prefix, replace))) = (path, prefix) {
path.strip_prefix(prefix).map_or_else(
|| Some(("", path)),
|stripped| Some((replace, stripped)),
)
} else {
path.map(|p| ("", p))
}
}
pub fn set_up_logging(input: &LogLocationTargetMap<'_>, name: &'static str) -> IOResult<impl FnOnce()> {
LOGGER.name.get_or_create(|| name);
if let Ok(value) = std::env::var("LOGGING_PREFIX_REPLACE") {
if let Some((prefix, replace)) = value.split_once("->") {
LOGGER.file_prefix.get_or_create(|| (prefix.to_string(), replace.to_string()));
}
}
let mut target_hashmap = LOGGER
.targets
.write()
.map_err(|error| IOError::new(ErrorKind::Other, PoisonErrorWrapper::from(error)))?;
*target_hashmap = generate_writable_log_location_target_map(input, Utc::now());
let log_handler = signals::setup_signal_handler()?;
log::set_logger(&*LOGGER)
.map(|()| log::set_max_level(LevelFilter::Trace))
.map_err(|error| IOError::new(ErrorKind::Other, ErrorWrapper::from(error)))?;
Ok(log_handler)
}
pub fn generate_writable_log_location_target_map(
from: &LogLocationTargetMap,
time_startup: DateTime<Utc>,
) -> WritableLogLocationTargetMap {
let mut file_map = HashMap::new();
let mut webwriter_map = HashMap::new();
WritableLogLocationTargetMap(
from.iter()
.map(|(level, targets)| {
let mut writable_targets = vec![];
for target in targets {
let writable_target = match target {
LogLocationTarget::File(path, size_limit) if !file_map.contains_key(path) => 'result: {
let new_file = Arc::new(RwLock::new({
let file = match OpenOptions::new().write(true).read(true).create(true).open(path) {
Ok(file) => file,
Err(e) => {
eprintln!("Failed to open file! Error: {}", e);
break 'result Err(IOError::new(ErrorKind::Other, e));
}
};
let mut circular_file = match CircularFileBuffer::new(file, size_limit.unwrap_or(u64::MAX)) {
Ok(circular_file) => circular_file,
Err(e) => {
eprintln!("Failed to create circular file buffer! Error: {:?}", e);
break 'result Err(IOError::new(ErrorKind::Other, e));
}
};
let write_result = writeln!(
circular_file,
"{:-^50}",
format!(
"Logging started at {}",
time_startup.format("%b %d %H:%M:%S%.3f"),
),
);
match write_result {
Ok(_) => circular_file,
Err(e) => {
eprintln!("Failed to write to circular file buffer! Error: {}", e);
break 'result Err(IOError::new(ErrorKind::Other, e));
}
}
}));
let new_file = file_map.entry(path).or_insert(new_file.clone()).clone();
Ok(WritableLogLocationTarget::File(new_file))
}
LogLocationTarget::File(path, _) => match file_map.get(path) {
Some(file) => Ok(WritableLogLocationTarget::File(file.clone())),
None => Err(std::io::Error::new(
ErrorKind::Other,
format!("File not found in file_map! {:?}", path),
)),
},
LogLocationTarget::WebWriter(url) => 'result: {
let url = match Url::parse(url) {
Ok(url) => url,
Err(e) => {
eprintln!("Failed to parse url! Error: {}", e);
break 'result Err(IOError::new(ErrorKind::Other, e));
}
};
let web_writer = webwriter_map
.entry(url.clone())
.or_insert_with(|| Arc::new(WebWriter::new(url.clone())))
.clone();
Ok(WritableLogLocationTarget::WebWriter(web_writer))
}
LogLocationTarget::StdOut => Ok(WritableLogLocationTarget::StdOut),
LogLocationTarget::StdErr => Ok(WritableLogLocationTarget::StdErr),
};
match writable_target {
Ok(writable_target) => writable_targets.push(writable_target),
Err(error) => eprintln!("{}", error),
}
}
(*level, smallvec::SmallVec::from_vec(writable_targets))
})
.collect(),
)
}
lazy_static! {
pub static ref ERR_FILE: &'static Path = Path::new("./err.log");
pub static ref ERR_WARN_FILE: &'static Path = Path::new("./err_warn.log");
pub static ref INFO_DEBUG_FILE: &'static Path = Path::new("./info_debug.log");
pub static ref ALL_LOG_FILE: &'static Path = Path::new("./all.log");
pub static ref LOGGING_URL: Option<String> = {
if let Ok(url) = std::env::var("LOGGING_TARGET_URL") {
if let Ok(url) = Url::parse(&url) {
Some(url.to_string())
} else { None }
} else { None }
};
pub static ref DEFAULT_LOGGING_TARGETS: LogLocationTargetMap<'static> = {
use Level::*;
use LogLocationTarget::*;
let mut target_map = vec![
(Trace, smallvec![
StdOut,
File(&ALL_LOG_FILE, None),
]),
(Debug, smallvec![
File(&INFO_DEBUG_FILE, None),
File(&ALL_LOG_FILE, None),
]),
(Info, smallvec![
StdOut,
File(&INFO_DEBUG_FILE, None),
File(&ALL_LOG_FILE, None),
]),
(Warn, smallvec![
StdErr,
File(&ERR_WARN_FILE, None),
File(&ALL_LOG_FILE, None),
]),
(Error, smallvec![
StdErr,
File(&ERR_FILE, None),
File(&ERR_WARN_FILE, None),
File(&ALL_LOG_FILE, None),
]),
];
if let Some(url) = LOGGING_URL.as_ref() {
target_map[0].1.push(WebWriter(url));
target_map[1].1.push(WebWriter(url));
target_map[2].1.push(WebWriter(url));
target_map[3].1.push(WebWriter(url));
target_map[4].1.push(WebWriter(url));
}
target_map.into_iter().collect()
};
}
pub fn default_logging_targets_with_size_limit(limit: u64) -> LogLocationTargetMap<'static> {
use Level::*;
use LogLocationTarget::*;
let mut target_map = vec![
(Trace, smallvec![
StdOut,
File(&ALL_LOG_FILE, Some(limit)),
]),
(Debug, smallvec![
File(&INFO_DEBUG_FILE, Some(limit)),
File(&ALL_LOG_FILE, Some(limit)),
]),
(Info, smallvec![
StdOut,
File(&INFO_DEBUG_FILE, Some(limit)),
File(&ALL_LOG_FILE, Some(limit)),
]),
(Warn, smallvec![
StdErr,
File(&ERR_WARN_FILE, Some(limit)),
File(&ALL_LOG_FILE, Some(limit)),
]),
(Error, smallvec![
StdErr,
File(&ERR_FILE, Some(limit)),
File(&ERR_WARN_FILE, Some(limit)),
File(&ALL_LOG_FILE, Some(limit)),
]),
];
if let Some(url) = LOGGING_URL.as_ref() {
target_map[0].1.push(WebWriter(url));
target_map[1].1.push(WebWriter(url));
target_map[2].1.push(WebWriter(url));
target_map[3].1.push(WebWriter(url));
target_map[4].1.push(WebWriter(url));
}
target_map.into_iter().collect()
}