From 7c672c32fe738d72b78454f1c32526301ebc6277 Mon Sep 17 00:00:00 2001 From: abbycin Date: Sun, 17 Aug 2025 08:03:54 +0800 Subject: [PATCH] add logger --- Cargo.toml | 3 + logger/.gitignore | 2 + logger/Cargo.toml | 12 ++ logger/src/lib.rs | 298 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 + 5 files changed, 319 insertions(+) create mode 100644 logger/.gitignore create mode 100644 logger/Cargo.toml create mode 100644 logger/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 02b668b..9a6bf92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,14 @@ edition = "2024" mace = { path = "/home/workspace/gits/github/mace"} clap = { version = "4.5.42", features = ["derive"] } rand = "0.9.2" +log = "0.4.22" coreid = { path = "coreid"} +logger = { path = "logger"} [profile.release] lto = true opt-level = 3 +debug = true [profile.release-with-symbol] inherits = "release" diff --git a/logger/.gitignore b/logger/.gitignore new file mode 100644 index 0000000..869df07 --- /dev/null +++ b/logger/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock \ No newline at end of file diff --git a/logger/Cargo.toml b/logger/Cargo.toml new file mode 100644 index 0000000..0d37f75 --- /dev/null +++ b/logger/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "logger" +version = "0.1.1" +edition = "2024" +authors = ["abbytsing@gmail.com"] + +[target.'cfg(target_os = "linux")'.dependencies] +libc = "0.2.161" + +[dependencies] +chrono = "0.4.38" +log = "0.4.22" diff --git a/logger/src/lib.rs b/logger/src/lib.rs new file mode 100644 index 0000000..fd4629a --- /dev/null +++ b/logger/src/lib.rs @@ -0,0 +1,298 @@ +use log::{LevelFilter, Metadata, Record}; +use std::cell::OnceCell; +use std::io::Write; +use std::ops::Deref; +use std::path::Path; +use std::ptr::addr_of_mut; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::Relaxed; +use std::sync::{Mutex, MutexGuard}; + +thread_local! { + static G_TID: OnceCell = OnceCell::new(); +} +#[cfg(not(target_os = "linux"))] +static G_ID: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1); + +static mut G_LOGGER: Logger = Logger { + mtx_shard: [const { Mutex::new(()) }; 8], + sink: Vec::new(), + abort_on_error: AtomicBool::new(false), +}; +static G_INIIED: Mutex = Mutex::new(false); + +const G_CONSOLE: &'static str = "console"; +const G_FILE: &'static str = "file"; + +#[cfg(target_os = "linux")] +fn get_tid() -> i32 { + G_TID.with(|x| *x.get_or_init(|| unsafe { libc::gettid() })) +} + +#[cfg(not(target_os = "linux"))] +fn get_tid() -> i32 { + use std::sync::atomic::Ordering::Relaxed; + G_TID.with(|x| *x.get_or_init(|| G_ID.fetch_add(1, Relaxed))) +} + +/// a simple sync logger which impl log::Log +pub struct Logger { + mtx_shard: [Mutex<()>; 8], + sink: Vec, + abort_on_error: AtomicBool, +} + +struct SinkHandle { + raw: *mut dyn Sink, +} + +unsafe impl Send for SinkHandle {} +unsafe impl Sync for SinkHandle {} + +impl SinkHandle { + fn new(x: T) -> Self + where + T: Sink + 'static, + { + let x = Box::new(x); + let raw = Box::into_raw(x); + Self { raw } + } + + fn as_mut(&self) -> &mut dyn Sink { + unsafe { &mut *self.raw } + } +} + +impl Deref for SinkHandle { + type Target = dyn Sink; + fn deref(&self) -> &Self::Target { + unsafe { &*self.raw } + } +} + +impl Drop for SinkHandle { + fn drop(&mut self) { + unsafe { + let _ = Box::from_raw(self.raw); + } + } +} + +trait Sink: Send + Sync { + fn sink(&mut self, str: &String); + + fn flush(&mut self); + + fn name(&self) -> &'static str; +} + +impl log::Log for Logger { + fn enabled(&self, _metadata: &Metadata) -> bool { + return true; + } + + fn log(&self, record: &Record) { + let s = format!( + "{} {} [{}] {}:{} {}\n", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S.%6f"), + get_tid(), + record.level().as_str(), + record.file().unwrap(), + record.line().unwrap(), + record.args() + ); + let _lk = self.lock(); + + for p in &self.sink { + p.as_mut().sink(&s); + } + + if record.level() == log::LevelFilter::Error && self.should_abort() { + let bt = std::backtrace::Backtrace::force_capture(); + let buf = format!("{}", bt); + for p in &self.sink { + p.as_mut().sink(&buf); + } + std::process::abort(); + } + } + + fn flush(&self) { + let _lk = self.lock(); + for p in &self.sink { + p.as_mut().flush(); + } + } +} + +struct Console {} + +impl Console { + fn new() -> Self { + Self {} + } +} + +/// NOTE: file rolling is not support at present +struct File { + w: std::fs::File, +} + +impl File { + fn new(path: impl AsRef, trunc: bool) -> Result { + let mut ops = std::fs::File::options(); + ops.write(true).create(true); + if trunc { + ops.truncate(true); + } else { + ops.append(true); + } + match ops.open(path) { + Err(e) => Err(e), + Ok(f) => Ok(Self { w: f }), + } + } +} + +impl Sink for Console { + fn sink(&mut self, str: &String) { + std::io::stdout().write(str.as_bytes()).unwrap(); + } + + fn flush(&mut self) { + std::io::stdout().flush().unwrap(); + } + + fn name(&self) -> &'static str { + G_CONSOLE + } +} + +impl Sink for File { + fn sink(&mut self, str: &String) { + self.w.write(str.as_bytes()).unwrap(); + } + + fn flush(&mut self) { + self.w.flush().unwrap(); + } + + fn name(&self) -> &'static str { + G_FILE + } +} + +impl Logger { + fn is_set() -> bool { + *G_INIIED.lock().unwrap() + } + pub fn init() -> &'static mut Self { + if !Self::is_set() { + *G_INIIED.lock().unwrap() = true; + log::set_logger(Self::get()).unwrap(); + log::set_max_level(LevelFilter::Trace); + } + return Self::get(); + } + + pub fn get() -> &'static mut Self { + unsafe { + let a = addr_of_mut!(G_LOGGER); + return &mut *a; + } + } + + fn exist(&self, sink: &'static str) -> Option<&mut Self> { + let _lk = self.mtx_shard[0].lock().unwrap(); + for i in &self.sink { + if i.name() == sink { + return Some(Self::get()); + } + } + return None; + } + + fn should_abort(&self) -> bool { + self.abort_on_error.load(Relaxed) + } + + fn lock(&self) -> MutexGuard<()> { + self.mtx_shard[get_tid() as usize & (self.mtx_shard.len() - 1)] + .lock() + .unwrap() + } + + pub fn abort_on_error(&mut self, flag: bool) -> &mut Self { + self.abort_on_error.store(flag, Relaxed); + self + } + + pub fn add_console(&mut self) -> &mut Self { + if self.exist(G_CONSOLE).is_none() { + self.sink.push(SinkHandle::new(Console::new())); + } + self + } + + pub fn add_file(&mut self, path: impl AsRef, trunc: bool) -> Option<&mut Self> { + if self.exist(G_FILE).is_none() { + match File::new(&path, trunc) { + Err(e) => { + eprintln!( + "can't open {}, error {}", + path.as_ref().to_str().unwrap(), + e.to_string() + ); + return None; + } + Ok(f) => { + self.sink.push(SinkHandle::new(f)); + return Some(self); + } + } + } + Some(self) + } + + fn remove_impl(&mut self, name: &'static str) { + let _lk = self.mtx_shard[0].lock().unwrap(); + for (idx, s) in self.sink.iter().enumerate() { + if s.name() == name { + self.sink.remove(idx); + break; + } + } + } + + pub fn remove_file(&mut self) { + self.remove_impl(G_FILE); + } + + pub fn remove_console(&mut self) { + self.remove_impl(G_CONSOLE); + } +} + +impl Drop for Logger { + fn drop(&mut self) { + let _lk = self.lock(); + for p in &self.sink { + p.as_mut().flush(); + } + } +} + +#[cfg(test)] +mod test { + use crate::Logger; + + #[test] + fn test_console() { + let l = Logger::init(); + + let p = log::logger() as *const dyn log::Log; + let q = &*l as *const dyn log::Log; + assert!(std::ptr::addr_eq(p, q)); + } +} diff --git a/src/main.rs b/src/main.rs index 24891e5..338bbec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use clap::Parser; +use logger::Logger; use mace::{Mace, Options}; use rand::prelude::*; use std::path::Path; @@ -35,6 +36,8 @@ struct Args { } fn main() { + Logger::init().add_file("/tmp/x.log", true); + log::set_max_level(log::LevelFilter::Debug); let args = Args::parse(); let path = Path::new(&args.path); @@ -108,6 +111,7 @@ fn main() { let val = value.clone(); std::thread::spawn(move || { + coreid::bind_core(tid); barrier.wait(); {