use std::os::raw::c_void;
use std::sync::RwLock;
use abi_stable::std_types::RStr;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Off = 0,
Error = 1,
Warn = 2,
Info = 3,
Debug = 4,
Trace = 5,
}
impl From<log::Level> for LogLevel {
fn from(level: log::Level) -> Self {
match level {
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug => LogLevel::Debug,
log::Level::Trace => LogLevel::Trace,
}
}
}
impl From<LogLevel> for log::LevelFilter {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Off => log::LevelFilter::Off,
LogLevel::Error => log::LevelFilter::Error,
LogLevel::Warn => log::LevelFilter::Warn,
LogLevel::Info => log::LevelFilter::Info,
LogLevel::Debug => log::LevelFilter::Debug,
LogLevel::Trace => log::LevelFilter::Trace,
}
}
}
pub type LogSinkFn =
extern "C" fn(ctx: *const c_void, level: LogLevel, target: RStr<'_>, message: RStr<'_>);
pub struct HubLogSink {
inner: RwLock<Option<SinkInner>>,
}
struct SinkInner {
sink_fn: LogSinkFn,
ctx: *const c_void,
}
unsafe impl Send for SinkInner {}
unsafe impl Sync for SinkInner {}
pub static HUB_LOG_SINK: HubLogSink = HubLogSink::new();
impl HubLogSink {
const fn new() -> Self {
Self {
inner: RwLock::new(None),
}
}
#[doc(hidden)]
pub fn install(&self, sink_fn: LogSinkFn, ctx: *const c_void, max_level: LogLevel) {
let new = SinkInner { sink_fn, ctx };
match self.inner.write() {
Ok(mut guard) => *guard = Some(new),
Err(poisoned) => *poisoned.into_inner() = Some(new),
}
let _ = log::set_logger(&HUB_LOG_SINK);
log::set_max_level(max_level.into());
}
}
impl log::Log for HubLogSink {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &log::Record<'_>) {
if !self.enabled(record.metadata()) {
return;
}
let guard = match self.inner.read() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
let Some(ref inner) = *guard else {
return;
};
let message = record.args().to_string();
(inner.sink_fn)(
inner.ctx,
record.level().into(),
RStr::from(record.target()),
RStr::from(message.as_str()),
);
}
fn flush(&self) {}
}