use core::fmt;
use std::sync::Mutex;
#[cfg(test)]
mod tests;
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::Display,
derive_more::IsVariant,
)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum LogLevel {
Debug,
Info,
Error,
None,
}
impl LogLevel {
#[inline(always)]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Debug => "debug",
Self::Info => "info",
Self::Error => "error",
Self::None => "none",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("unknown log level name")]
pub struct ParseLogLevelError(());
impl core::str::FromStr for LogLevel {
type Err = ParseLogLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"debug" => Self::Debug,
"info" => Self::Info,
"error" => Self::Error,
"none" => Self::None,
_ => return Err(ParseLogLevelError(())),
})
}
}
pub type LoggingCallback = std::sync::Arc<dyn Fn(LogLevel, &str) + Send + Sync>;
struct LoggerState {
level: LogLevel,
callback: Option<LoggingCallback>,
}
pub struct Logger {
state: Mutex<LoggerState>,
}
impl fmt::Debug for Logger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.lock().expect("logger lock poisoned");
f.debug_struct("Logger")
.field("level", &state.level)
.field("callback", &state.callback.as_ref().map(|_| "<installed>"))
.finish()
}
}
impl Logger {
pub const fn new(level: LogLevel) -> Self {
Self {
state: Mutex::new(LoggerState {
level,
callback: None,
}),
}
}
pub fn set_level(&self, level: LogLevel) {
self.state.lock().expect("logger lock poisoned").level = level;
}
pub fn set_callback(&self, callback: LoggingCallback) {
self.state.lock().expect("logger lock poisoned").callback = Some(callback);
}
pub fn clear_callback(&self) {
self.state.lock().expect("logger lock poisoned").callback = None;
}
pub fn log(&self, level: LogLevel, args: fmt::Arguments<'_>) {
if level.is_none() {
return;
}
let (gate, callback) = {
let state = self.state.lock().expect("logger lock poisoned");
(state.level, state.callback.clone())
};
if gate.is_none() || level < gate {
return;
}
let message = std::fmt::format(args);
#[cfg(feature = "tracing")]
match level {
LogLevel::Debug => tracing::debug!("{message}"),
LogLevel::Info => tracing::info!("{message}"),
LogLevel::Error => tracing::error!("{message}"),
LogLevel::None => {}
}
match callback.as_ref() {
Some(callback) => callback(level, &message),
_ => eprintln!("[whisperkit {level}] {message}"),
}
}
}
pub fn resident_memory_bytes() -> Option<u64> {
let mut info: libc::mach_task_basic_info = unsafe { core::mem::zeroed() };
let mut count = (core::mem::size_of::<libc::mach_task_basic_info>()
/ core::mem::size_of::<libc::natural_t>()) as libc::mach_msg_type_number_t;
let result = unsafe {
libc::task_info(
mach2::traps::mach_task_self(),
libc::MACH_TASK_BASIC_INFO,
(&raw mut info).cast(),
&mut count,
)
};
(result == libc::KERN_SUCCESS).then_some(info.resident_size)
}