use crate::bindings::{plugin_log, LOG_DEBUG, LOG_ERR, LOG_INFO, LOG_NOTICE, LOG_WARNING};
use crate::errors::FfiError;
use log::{error, log_enabled, Level, LevelFilter, Metadata, Record, SetLoggerError};
use std::cell::Cell;
use std::error::Error;
use std::ffi::{CStr, CString};
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
pub struct CollectdLoggerBuilder {
plugin: Option<&'static str>,
filter_level: LevelFilter,
format: Box<FormatFn>,
}
type FormatFn = dyn Fn(&mut dyn Write, &Record<'_>) -> io::Result<()> + Sync + Send;
impl CollectdLoggerBuilder {
pub fn new() -> Self {
Self {
plugin: None,
filter_level: LevelFilter::Trace,
format: Format::default().into_boxed_fn(),
}
}
pub fn prefix_plugin<T: crate::plugins::PluginManager>(mut self) -> Self {
self.plugin = Some(T::name());
self
}
pub fn filter_level(mut self, level: LevelFilter) -> Self {
self.filter_level = level;
self
}
pub fn format<F>(mut self, format: F) -> Self
where
F: Fn(&mut dyn Write, &Record<'_>) -> io::Result<()> + Sync + Send + 'static,
{
self.format = Box::new(format);
self
}
pub fn build(self) -> CollectdLogger {
CollectdLogger {
plugin: self.plugin,
filter_level: self.filter_level,
format: self.format,
}
}
pub fn try_init(self) -> Result<(), SetLoggerError> {
let logger = self.build();
log::set_max_level(logger.filter_level);
log::set_boxed_logger(Box::new(logger))
}
}
#[derive(Default)]
struct Format {
custom_format: Option<Box<FormatFn>>,
}
impl Format {
fn into_boxed_fn(self) -> Box<FormatFn> {
if let Some(fmt) = self.custom_format {
fmt
} else {
Box::new(move |buf, record| {
if let Some(path) = record.module_path() {
write!(buf, "{}: ", path)?;
}
write!(buf, "{}", record.args())
})
}
}
}
pub struct CollectdLogger {
plugin: Option<&'static str>,
filter_level: LevelFilter,
format: Box<FormatFn>,
}
impl log::Log for CollectdLogger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= self.filter_level
}
fn log(&self, record: &Record<'_>) {
if !self.enabled(record.metadata()) {
return;
}
thread_local!(static LOG_BUF: Cell<Vec<u8>> = const { Cell::new(Vec::new()) });
LOG_BUF.with(|cell| {
let mut write_buffer = cell.take();
if let Some(plugin) = self.plugin {
let _ = write!(write_buffer, "{}: ", plugin);
}
if (self.format)(&mut write_buffer, record).is_ok() {
let lvl = LogLevel::from(record.level());
write_buffer.push(b'\0');
{
let cs = unsafe { CStr::from_bytes_with_nul_unchecked(&write_buffer[..]) };
unsafe { plugin_log(lvl as i32, cs.as_ptr()) };
}
}
write_buffer.clear();
cell.set(write_buffer);
});
}
fn flush(&self) {}
}
pub fn log_err(desc: &str, err: &FfiError<'_>) {
let mut msg = format!("{} error: {}", desc, err);
let mut ie = err.source();
while let Some(cause) = ie {
let _ = write!(msg, "; {}", cause);
ie = cause.source();
}
if log_enabled!(Level::Error) {
error!("{}", msg);
} else {
collectd_log(LogLevel::Error, &msg);
}
}
pub fn collectd_log(lvl: LogLevel, message: &str) {
let cs = CString::new(message).expect("Collectd log to not contain nulls");
unsafe {
plugin_log(lvl as i32, cs.as_ptr());
}
}
#[macro_export]
macro_rules! collectd_log_raw {
($lvl:expr, $fmt:expr) => ({
let level: $crate::LogLevel = $lvl;
let level = level as i32;
unsafe {
$crate::bindings::plugin_log(level, ($fmt).as_ptr() as *const ::std::os::raw::c_char);
}
});
($lvl:expr, $fmt:expr, $($arg:expr),*) => ({
let level: $crate::LogLevel = $lvl;
let level = level as i32;
unsafe {
$crate::bindings::plugin_log(level, ($fmt).as_ptr() as *const ::std::os::raw::c_char, $($arg)*);
}
});
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum LogLevel {
Error = LOG_ERR,
Warning = LOG_WARNING,
Notice = LOG_NOTICE,
Info = LOG_INFO,
Debug = LOG_DEBUG,
}
impl LogLevel {
pub fn try_from(s: u32) -> Option<LogLevel> {
match s {
LOG_ERR => Some(LogLevel::Error),
LOG_WARNING => Some(LogLevel::Warning),
LOG_NOTICE => Some(LogLevel::Notice),
LOG_INFO => Some(LogLevel::Info),
LOG_DEBUG => Some(LogLevel::Debug),
_ => None,
}
}
}
impl From<Level> for LogLevel {
fn from(lvl: Level) -> Self {
match lvl {
Level::Error => LogLevel::Error,
Level::Warn => LogLevel::Warning,
Level::Info => LogLevel::Info,
Level::Debug | Level::Trace => LogLevel::Debug,
}
}
}