use crate::error::{LogError, LogResult};
use std::collections::VecDeque;
use std::fs::File;
use std::io::{LineWriter, Write};
use std::str::FromStr;
use std::sync::Mutex;
pub const CACHE_STR_INIT_SIZE: usize = 256;
pub enum AsyncLogType {
Message(Vec<u8>),
Flush,
QuickwitLog(crate::quickwit::QuickwitLogEntry),
}
pub struct SkipAnsiColorIter<'a> {
data: &'a [u8],
pos: usize,
find_len: usize,
}
impl<'a> SkipAnsiColorIter<'a> {
pub fn new(data: &'a [u8]) -> Self {
let find_len = if data.len() > 3 {
data.len() - 3
} else {
0
};
SkipAnsiColorIter {
data,
pos: 0,
find_len,
}
}
}
impl<'a> Iterator for SkipAnsiColorIter<'a> {
type Item = &'a [u8];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (mut pos, find_len, data) = (self.pos, self.find_len, self.data);
while pos < find_len {
unsafe {
if *data.get_unchecked(pos) != 0x1b || *data.get_unchecked(pos + 1) != b'[' {
pos += 1;
continue;
}
let n = if *data.get_unchecked(pos + 3) == b'm' { 4 } else { 5 };
let p = self.pos;
self.pos = pos + n;
return Some(&data[p..pos]);
}
}
let dl = data.len();
if pos < dl {
let p = self.pos;
self.pos = dl;
return Some(&data[p..dl]);
}
None
}
}
pub fn parse_level(level: &str) -> LogResult<log::LevelFilter> {
match log::LevelFilter::from_str(level) {
Ok(num) => Ok(num),
Err(_) => Err(format!("can't parse log level: {level}").into()),
}
}
pub fn parse_size(size: &str) -> LogResult<u32> {
match size.parse() {
Ok(n) => Ok(n),
Err(_) => match size[..size.len() - 1].parse() {
Ok(n) => {
let s = size.as_bytes();
match s[s.len() - 1] {
b'b' | b'B' => Ok(n),
b'k' | b'K' => Ok(n * 1024),
b'm' | b'M' => Ok(n * 1024 * 1024),
b'g' | b'G' => Ok(n * 1024 * 1024 * 1024),
_ => Err(format!("parse size error, unit is unknown: {size}").into()),
}
},
Err(e) => Err(e.into()),
}
}
}
pub fn write_text(w: &mut LineWriter<File>, msg: &[u8]) -> std::io::Result<usize> {
let mut written = 0;
for chunk in SkipAnsiColorIter::new(msg) {
w.write_all(chunk)?;
written += chunk.len();
}
Ok(written)
}
#[inline]
pub fn safe_flush<W: Write>(writer: &mut W) -> LogResult<()> {
writer.flush().map_err(LogError::from)
}
pub fn open_log_file_sync(file_path: &str) -> LogResult<(LineWriter<File>, u32)> {
use std::fs::OpenOptions;
use std::io::Seek;
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(file_path)
.map_err(|e| LogError::file_operation(
file_path.to_string(),
format!("Failed to open file: {}", e)
))?;
let size = file.seek(std::io::SeekFrom::End(0))
.map_err(|e| LogError::file_operation(
file_path.to_string(),
format!("Failed to seek file: {}", e)
))? as u32;
let writer = LineWriter::new(file);
Ok((writer, size))
}
pub fn level_color(level: log::Level) -> &'static str {
match level {
log::Level::Error => "\x1b[31m", log::Level::Warn => "\x1b[33m", log::Level::Info => "\x1b[32m", log::Level::Debug => "\x1b[36m", log::Level::Trace => "\x1b[35m", }
}
pub fn get_msg_from_cache() -> Vec<u8> {
static FMT_CACHE: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());
if let Ok(mut cache) = FMT_CACHE.lock() {
cache.pop_front().unwrap_or_else(|| Vec::with_capacity(CACHE_STR_INIT_SIZE))
} else {
Vec::with_capacity(CACHE_STR_INIT_SIZE)
}
}
pub fn put_msg_to_cache(mut value: Vec<u8>) {
static FMT_CACHE: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());
value.clear();
if let Ok(mut cache) = FMT_CACHE.lock() {
if cache.len() < 16 {
cache.push_back(value);
}
}
}
pub fn debug_check_init() {
#[cfg(debug_assertions)]
{
println!("log-full debug mode enabled");
}
}
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn build_info() -> (&'static str, &'static str) {
(env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_NAME"))
}