pub mod emit;
pub mod patterns;
use std::ffi::OsString;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub const DEFAULT_MAX_LOG_BYTES: u64 = 50 * 1024 * 1024;
pub const DEFAULT_MAX_LOG_FILES: usize = 5;
struct RotatingState {
path: PathBuf,
max_bytes: u64,
max_files: usize,
file: File,
written: u64,
}
impl RotatingState {
fn open(path: PathBuf, max_bytes: u64, max_files: usize) -> io::Result<Self> {
let file = OpenOptions::new().create(true).append(true).open(&path)?;
let written = file.metadata()?.len();
Ok(Self {
path,
max_bytes,
max_files,
file,
written,
})
}
fn rotated_path(&self, n: usize) -> PathBuf {
let mut name: OsString = self.path.clone().into_os_string();
name.push(format!(".{n}"));
PathBuf::from(name)
}
fn rotate(&mut self) -> io::Result<()> {
self.file.flush()?;
if self.max_files == 0 {
let _ = fs::remove_file(&self.path);
} else {
let oldest = self.rotated_path(self.max_files);
if oldest.exists() {
fs::remove_file(&oldest)?;
}
for i in (1..self.max_files).rev() {
let from = self.rotated_path(i);
if from.exists() {
fs::rename(&from, self.rotated_path(i + 1))?;
}
}
if self.path.exists() {
fs::rename(&self.path, self.rotated_path(1))?;
}
}
self.file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
self.written = 0;
Ok(())
}
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.max_bytes > 0 && self.written >= self.max_bytes {
self.rotate()?;
}
let n = self.file.write(buf)?;
self.written += n as u64;
Ok(n)
}
fn write_all(&mut self, mut buf: &[u8]) -> io::Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
))
}
Ok(n) => buf = &buf[n..],
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct RotatingFileWriter {
state: Arc<Mutex<RotatingState>>,
}
impl RotatingFileWriter {
pub fn new<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
Self::with_limits(path, DEFAULT_MAX_LOG_BYTES, DEFAULT_MAX_LOG_FILES)
}
pub fn with_limits<P: Into<PathBuf>>(
path: P,
max_bytes: u64,
max_files: usize,
) -> io::Result<Self> {
let state = RotatingState::open(path.into(), max_bytes, max_files)?;
Ok(Self {
state: Arc::new(Mutex::new(state)),
})
}
}
impl Write for RotatingFileWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut guard = self
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.write(buf)
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
let mut guard = self
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.write_all(buf)
}
fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> {
use std::fmt::Write as _;
let mut formatted = String::new();
formatted
.write_fmt(fmt)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
self.write_all(formatted.as_bytes())
}
fn flush(&mut self) -> io::Result<()> {
let mut guard = self
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.file.flush()
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RotatingFileWriter {
type Writer = RotatingFileWriter;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
#[macro_export]
macro_rules! console_trace {
($status_tx:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
tracing::trace!("{}", msg);
if !msg.starts_with("__") {
let _ = $status_tx.send(format!("[TRACE] {}", msg));
} else {
let _ = $status_tx.send(msg);
}
}};
}
#[macro_export]
macro_rules! console_debug {
($status_tx:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
tracing::debug!("{}", msg);
if !msg.starts_with("__") {
let _ = $status_tx.send(format!("[DEBUG] {}", msg));
} else {
let _ = $status_tx.send(msg);
}
}};
}
#[macro_export]
macro_rules! console_info {
($status_tx:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
tracing::info!("{}", msg);
if !msg.starts_with("__") {
let _ = $status_tx.send(format!("[INFO] {}", msg));
} else {
let _ = $status_tx.send(msg);
}
}};
}
#[macro_export]
macro_rules! console_warn {
($status_tx:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
tracing::warn!("{}", msg);
if !msg.starts_with("__") {
let _ = $status_tx.send(format!("[WARN] {}", msg));
} else {
let _ = $status_tx.send(msg);
}
}};
}
#[macro_export]
macro_rules! console_error {
($status_tx:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
tracing::error!("{}", msg);
if !msg.starts_with("__") {
let _ = $status_tx.send(format!("[ERROR] {}", msg));
} else {
let _ = $status_tx.send(msg);
}
}};
}