-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
misc: Add proper log manager for Rust modules
- Loading branch information
Showing
29 changed files
with
560 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "argus_logging" | ||
version = "0.0.1" | ||
edition = "2021" | ||
|
||
[lib] | ||
name = "argus_logging" | ||
crate-type = ["rlib"] | ||
|
||
[dependencies] | ||
chrono = "0.4.39" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
mod logger; | ||
mod macros; | ||
mod manager; | ||
mod settings; | ||
|
||
pub use logger::*; | ||
pub use macros::*; | ||
pub use manager::*; | ||
pub use settings::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
use crate::{LogLevel, LogManager}; | ||
use crate::manager::get_log_manager; | ||
|
||
pub struct Logger { | ||
mgr: &'static LogManager, | ||
channel: String, | ||
} | ||
|
||
impl Logger { | ||
pub fn new(channel: impl Into<String>) -> Result<Self, ()> { | ||
let Some(mgr) = get_log_manager() else { return Err(()); }; | ||
let logger = Self { mgr, channel: channel.into() }; | ||
Ok(logger) | ||
} | ||
|
||
pub fn log(&self, level: LogLevel, message: impl Into<String>) { | ||
let res = self.mgr.stage_message(self.channel.clone(), level, message.into()); | ||
if let Err(msg_obj) = res { | ||
eprintln!( | ||
"Failed to submit log message: LogManager is already deinitialized ({:?})", | ||
msg_obj, | ||
); | ||
} | ||
} | ||
|
||
pub fn trace(&self, message: impl Into<String>) { | ||
self.log(LogLevel::Trace, message); | ||
} | ||
|
||
pub fn debug(&self, message: impl Into<String>) { | ||
self.log(LogLevel::Debug, message); | ||
} | ||
|
||
pub fn info(&self, message: impl Into<String>) { | ||
self.log(LogLevel::Info, message); | ||
} | ||
|
||
pub fn warn(&self, message: impl Into<String>) { | ||
self.log(LogLevel::Warning, message); | ||
} | ||
|
||
pub fn severe(&self, message: impl Into<String>) { | ||
self.log(LogLevel::Severe, message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#[macro_export] | ||
#[doc(hidden)] | ||
macro_rules! log_internal { | ||
($method:ident, $logger:expr, $format:literal, $($arg:tt)*) => { | ||
$crate::Logger::$method(::std::ops::Deref::deref(&$logger), format!($format, $($arg)*)) | ||
}; | ||
($method:ident, $logger:expr, $message:expr) => { | ||
$crate::Logger::$method(::std::ops::Deref::deref(&$logger), $message) | ||
}; | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! severe { | ||
($logger:expr, $($arg:tt)*) => { | ||
$crate::log_internal!(severe, $logger, $($arg)*) | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! warn { | ||
($logger:expr, $($arg:tt)*) => { | ||
$crate::log_internal!(warn, $logger, $($arg)*) | ||
}; | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! info { | ||
($logger:expr, $($arg:tt)*) => { | ||
$crate::log_internal!(info, $logger, $($arg)*) | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! debug { | ||
($logger:expr, $($arg:tt)*) => { | ||
$crate::log_internal!(debug, $logger, $($arg)*) | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! trace { | ||
($logger:expr, $($arg:tt)*) => { | ||
$crate::log_internal!(trace, $logger, $($arg)*) | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! crate_logger { | ||
($name:ident, $channel:tt) => { | ||
pub(crate) static $name: ::std::sync::LazyLock<$crate::Logger> = | ||
::std::sync::LazyLock::new(|| { | ||
$crate::Logger::new($channel).expect("Failed to create crate logger") | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
use std::collections::{BTreeMap, HashMap}; | ||
use std::io::{stderr, stdout, Write}; | ||
use std::sync::atomic::{AtomicU64, Ordering}; | ||
use std::sync::mpsc::{channel, Receiver, Sender}; | ||
use std::sync::OnceLock; | ||
use std::thread; | ||
use std::thread::JoinHandle; | ||
use std::time::{Instant, SystemTime}; | ||
use chrono::{DateTime, Local}; | ||
use crate::{LogLevel, LogSettings, PreludeComponent}; | ||
use crate::settings::validate_settings; | ||
|
||
static MANAGER: OnceLock<LogManager> = OnceLock::new(); | ||
|
||
pub struct LogManager { | ||
settings: LogSettings, | ||
sender: Sender<LoggerCommandWrapper>, | ||
startup_time: Instant, | ||
counter: AtomicU64, | ||
} | ||
|
||
impl LogManager { | ||
pub fn instance() -> &'static Self { | ||
&MANAGER.get().unwrap() | ||
} | ||
|
||
pub fn initialize(settings: LogSettings) -> Result<JoinHandle<()>, ()> { | ||
let real_settings = validate_settings(settings); | ||
|
||
let (tx, rx) = channel(); | ||
|
||
let mgr = LogManager { | ||
settings: real_settings, | ||
sender: tx, | ||
startup_time: Instant::now(), | ||
counter: AtomicU64::new(0), | ||
}; | ||
MANAGER.set(mgr).map_err(|_| ())?; | ||
|
||
let join_handle = thread::spawn(move || { | ||
do_logging_loop(rx, MANAGER.get().unwrap()); | ||
}); | ||
Ok(join_handle) | ||
} | ||
|
||
pub fn deinitialize(&self) -> Result<(), ()> { | ||
self.request_halt() | ||
} | ||
|
||
pub(crate) fn stage_message(&self, channel: String, level: LogLevel, message: String) | ||
-> Result<(), StagedMessage> { | ||
if level < self.settings.min_level { | ||
return Ok(()); | ||
} | ||
|
||
let mono_time = Instant::now(); | ||
let sys_time = SystemTime::now(); | ||
let msg_obj = StagedMessage { channel, level, message, mono_time, sys_time }; | ||
let cmd = LoggerCommandWrapper { | ||
command: LoggerCommand::EmitMessage(msg_obj), | ||
ordinal: self.next_ordinal(), | ||
}; | ||
self.sender.send(cmd).map_err(|sent_cmd| { | ||
let LoggerCommand::EmitMessage(cmd) = sent_cmd.0.command | ||
else { panic!("Send error contained unrelated command object") }; | ||
cmd | ||
}) | ||
} | ||
|
||
fn next_ordinal(&self) -> u64 { | ||
self.counter.fetch_add(1, Ordering::Relaxed) | ||
} | ||
|
||
fn request_halt(&self) -> Result<(), ()> { | ||
let cmd = LoggerCommandWrapper { | ||
command: LoggerCommand::Halt, | ||
ordinal: self.next_ordinal(), | ||
}; | ||
self.sender.send(cmd).map_err(|_| ()) | ||
} | ||
} | ||
|
||
impl Drop for LogManager { | ||
fn drop(&mut self) { | ||
// don't care if it was already deinitialized | ||
_ = self.deinitialize(); | ||
} | ||
} | ||
|
||
pub(crate) fn get_log_manager() -> Option<&'static LogManager> { | ||
MANAGER.get() | ||
} | ||
|
||
|
||
#[derive(Clone, Debug)] | ||
pub(crate) struct StagedMessage { | ||
channel: String, | ||
level: LogLevel, | ||
message: String, | ||
mono_time: Instant, | ||
sys_time: SystemTime, | ||
} | ||
|
||
struct LoggerCommandWrapper { | ||
command: LoggerCommand, | ||
ordinal: u64, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
enum LoggerCommand { | ||
EmitMessage(StagedMessage), | ||
Halt, | ||
} | ||
|
||
fn emit_log_entry(mgr: &LogManager, message: StagedMessage) { | ||
if message.level < mgr.settings.min_level { | ||
return; | ||
} | ||
|
||
let prelude = mgr.settings.prelude.iter() | ||
.map(|c| match c { | ||
PreludeComponent::MonotonicTime => { | ||
let mono_elapsed = message.mono_time - mgr.startup_time; | ||
let mono_secs = mono_elapsed.as_secs_f64(); | ||
let prec = mgr.settings.clock_precision; | ||
let width = prec + 5; | ||
format!("{:>width$.prec$}", mono_secs, width = width, prec = prec) | ||
}, | ||
PreludeComponent::LocalTime => { | ||
DateTime::<Local>::from(message.sys_time) | ||
.format("%Y-%m-%d %H:%M:%S") | ||
.to_string() | ||
}, | ||
PreludeComponent::UtcTime => { | ||
DateTime::<Local>::from(message.sys_time) | ||
.to_utc() | ||
.format("%Y-%m-%d %H:%M:%S") | ||
.to_string() | ||
}, | ||
PreludeComponent::Level => format!("[{}]", message.level.to_string().to_uppercase()), | ||
PreludeComponent::Channel => format!("[{}]", message.channel), | ||
}) | ||
.collect::<Vec<_>>() | ||
.join(" "); | ||
|
||
let final_msg = if prelude.len() > 0 { | ||
format!("{} {}\n", prelude, message.message) | ||
} else { | ||
format!("{}\n", message.message) | ||
}; | ||
|
||
let console_res = match message.level { | ||
LogLevel::Severe | LogLevel::Warning => { | ||
stderr().write_all(final_msg.as_bytes()) | ||
}, | ||
LogLevel::Info | LogLevel::Debug | LogLevel::Trace => { | ||
stdout().write_all(final_msg.as_bytes()) | ||
}, | ||
}; | ||
|
||
if let Err(console_err) = console_res { | ||
eprintln!( | ||
"Failed to write log entry to stdout/stderr: {:?} (message: {:?})", | ||
console_err, | ||
final_msg | ||
); | ||
} | ||
} | ||
|
||
fn do_logging_loop(receiver: Receiver<LoggerCommandWrapper>, mgr: &'static LogManager) { | ||
let mut next_ordinal: u64 = 0; | ||
let mut buffered_cmds: HashMap<u64, LoggerCommand> = HashMap::new(); | ||
loop { | ||
let incoming_cmd = receiver.recv().unwrap(); | ||
if incoming_cmd.ordinal != next_ordinal { | ||
buffered_cmds.insert(incoming_cmd.ordinal, incoming_cmd.command); | ||
continue; | ||
} | ||
|
||
match incoming_cmd.command { | ||
LoggerCommand::EmitMessage(msg) => { | ||
emit_log_entry(mgr, msg); | ||
} | ||
LoggerCommand::Halt => { | ||
break; | ||
}, | ||
} | ||
next_ordinal += 1; | ||
|
||
while let Some(cur_cmd) = buffered_cmds.remove(&next_ordinal) { | ||
match cur_cmd { | ||
LoggerCommand::EmitMessage(msg) => { | ||
emit_log_entry(mgr, msg); | ||
} | ||
LoggerCommand::Halt => { | ||
break; | ||
}, | ||
} | ||
next_ordinal += 1; | ||
} | ||
} | ||
|
||
// flush remaining buffered message | ||
for (_, cmd) in buffered_cmds.into_iter().collect::<BTreeMap<_, _>>() { | ||
if let LoggerCommand::EmitMessage(msg) = cmd { | ||
emit_log_entry(mgr, msg); | ||
} | ||
} | ||
} |
Oops, something went wrong.