use crate::raw;
use std::ffi::CString;
use std::ptr;
use strum_macros::AsRefStr;
const NOT_INITIALISED_MESSAGE: &str = "Valkey module hasn't been initialised.";
#[derive(Clone, Copy, Debug, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum ValkeyLogLevel {
Debug,
Notice,
Verbose,
Warning,
}
impl From<log::Level> for ValkeyLogLevel {
fn from(value: log::Level) -> Self {
match value {
log::Level::Error | log::Level::Warn => Self::Warning,
log::Level::Info => Self::Notice,
log::Level::Debug => Self::Verbose,
log::Level::Trace => Self::Debug,
}
}
}
pub(crate) fn log_internal<L: Into<ValkeyLogLevel>>(
ctx: *mut raw::RedisModuleCtx,
level: L,
message: &str,
) {
if cfg!(test) {
return;
}
let level = CString::new(level.into().as_ref()).unwrap();
let fmt = CString::new(message).unwrap();
unsafe {
raw::RedisModule_Log.expect(NOT_INITIALISED_MESSAGE)(ctx, level.as_ptr(), fmt.as_ptr())
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn log_io_error(io: *mut raw::RedisModuleIO, level: ValkeyLogLevel, message: &str) {
if cfg!(test) {
return;
}
let level = CString::new(level.as_ref()).unwrap();
let fmt = CString::new(message).unwrap();
unsafe {
raw::RedisModule_LogIOError.expect(NOT_INITIALISED_MESSAGE)(
io,
level.as_ptr(),
fmt.as_ptr(),
)
}
}
pub fn log<T: AsRef<str>>(level: ValkeyLogLevel, message: T) {
log_internal(ptr::null_mut(), level, message.as_ref());
}
pub fn log_debug<T: AsRef<str>>(message: T) {
log(ValkeyLogLevel::Debug, message.as_ref());
}
pub fn log_notice<T: AsRef<str>>(message: T) {
log(ValkeyLogLevel::Notice, message.as_ref());
}
pub fn log_verbose<T: AsRef<str>>(message: T) {
log(ValkeyLogLevel::Verbose, message.as_ref());
}
pub fn log_warning<T: AsRef<str>>(message: T) {
log(ValkeyLogLevel::Warning, message.as_ref());
}
pub mod standard_log_implementation {
use std::sync::{atomic::Ordering, OnceLock};
use crate::ValkeyError;
use super::*;
use log::{Metadata, Record, SetLoggerError};
struct ValkeyGlobalLogger(*mut raw::RedisModuleCtx);
unsafe impl Send for ValkeyGlobalLogger {}
unsafe impl Sync for ValkeyGlobalLogger {}
#[allow(dead_code)]
pub fn setup() -> Result<(), ValkeyError> {
let pointer = crate::MODULE_CONTEXT.ctx.load(Ordering::Relaxed);
if pointer.is_null() {
return Err(ValkeyError::Str(NOT_INITIALISED_MESSAGE));
}
setup_for_context(pointer)
.map_err(|e| ValkeyError::String(format!("Couldn't set up the logger: {e}")))
}
fn logger(context: *mut raw::RedisModuleCtx) -> &'static ValkeyGlobalLogger {
static LOGGER: OnceLock<ValkeyGlobalLogger> = OnceLock::new();
LOGGER.get_or_init(|| ValkeyGlobalLogger(context))
}
#[allow(dead_code)]
pub fn setup_for_context(context: *mut raw::RedisModuleCtx) -> Result<(), SetLoggerError> {
log::set_logger(logger(context)).map(|()| log::set_max_level(log::LevelFilter::Trace))
}
impl log::Log for ValkeyGlobalLogger {
fn enabled(&self, _: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
let message = match record.level() {
log::Level::Debug | log::Level::Trace => {
format!(
"'{}' {}:{}: {}",
record.module_path().unwrap_or_default(),
record.file().unwrap_or("Unknown"),
record.line().unwrap_or(0),
record.args()
)
}
_ => record.args().to_string(),
};
log_internal(self.0, record.level(), &message);
}
fn flush(&self) {
}
}
}
pub use standard_log_implementation::*;