use crate::cglue::{
ext::{DisplayBaseRef, DisplayRef},
COption, CSliceRef, Opaquable,
};
use log::{Level, LevelFilter, SetLoggerError};
use core::ffi::c_void;
use std::sync::atomic::{AtomicPtr, Ordering};
#[repr(C)]
pub struct Metadata<'a> {
level: Level,
target: CSliceRef<'a, u8>,
}
#[repr(C)]
pub struct Record<'a> {
metadata: Metadata<'a>,
message: DisplayRef<'a>,
module_path: COption<CSliceRef<'a, u8>>,
file: COption<CSliceRef<'a, u8>>,
line: COption<u32>,
}
#[repr(C)]
pub struct PluginLogger {
max_level: LevelFilter,
enabled: extern "C" fn(metadata: &Metadata) -> bool,
log: extern "C" fn(record: &Record) -> (),
flush: extern "C" fn() -> (),
on_level_change: AtomicPtr<c_void>,
}
impl PluginLogger {
pub fn new() -> Self {
Self {
max_level: log::max_level(),
enabled: mf_log_enabled,
log: mf_log_log,
flush: mf_log_flush,
on_level_change: AtomicPtr::new(std::ptr::null_mut()),
}
}
pub fn init(&'static self) -> Result<(), SetLoggerError> {
let val: SetMaxLevelFn = mf_log_set_max_level;
self.on_level_change
.store(val as *const c_void as *mut c_void, Ordering::SeqCst);
log::set_max_level(self.max_level);
log::set_logger(self)?;
Ok(())
}
pub fn on_level_change(&self, new_level: LevelFilter) {
let val = self.on_level_change.load(Ordering::Relaxed);
if let Some(on_change) = unsafe { std::mem::transmute::<_, Option<SetMaxLevelFn>>(val) } {
on_change(new_level);
}
}
}
impl Default for PluginLogger {
fn default() -> Self {
PluginLogger::new()
}
}
fn display_obj<'a, T: 'a + core::fmt::Display>(obj: &'a T) -> DisplayRef<'a> {
let obj: DisplayBaseRef<T> = From::from(obj);
obj.into_opaque()
}
impl log::Log for PluginLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
let m = Metadata {
level: metadata.level(),
target: metadata.target().into(),
};
(self.enabled)(&m)
}
fn log(&self, record: &log::Record) {
let message = display_obj(record.args());
let r = Record {
metadata: Metadata {
level: record.metadata().level(),
target: record.metadata().target().into(),
},
message,
module_path: record.module_path().map(|s| s.into()).into(),
file: record.file().map(|s| s.into()).into(),
line: record.line().into(),
};
(self.log)(&r)
}
fn flush(&self) {
(self.flush)()
}
}
type SetMaxLevelFn = extern "C" fn(LevelFilter);
extern "C" fn mf_log_set_max_level(level: LevelFilter) {
log::set_max_level(level);
}
extern "C" fn mf_log_enabled(metadata: &Metadata) -> bool {
log::logger().enabled(
&log::Metadata::builder()
.level(metadata.level)
.target(unsafe { metadata.target.into_str() })
.build(),
)
}
extern "C" fn mf_log_log(record: &Record) {
log::logger().log(
&log::Record::builder()
.metadata(
log::Metadata::builder()
.level(record.metadata.level)
.target(unsafe { record.metadata.target.into_str() })
.build(),
)
.args(format_args!("{}", record.message))
.module_path(match &record.module_path {
COption::Some(s) => Some(unsafe { s.into_str() }),
COption::None => None,
})
.file(match &record.file {
COption::Some(s) => Some(unsafe { s.into_str() }),
COption::None => None,
})
.line(match &record.line {
COption::Some(l) => Some(*l),
COption::None => None,
})
.build(),
)
}
extern "C" fn mf_log_flush() {
log::logger().flush()
}