1use crate::raw;
2use std::ffi::CString;
3use std::ptr;
4use strum_macros::AsRefStr;
5
6const NOT_INITIALISED_MESSAGE: &str = "Valkey module hasn't been initialised.";
7
8#[derive(Clone, Copy, Debug, AsRefStr)]
12#[strum(serialize_all = "snake_case")]
13pub enum ValkeyLogLevel {
14 Debug,
15 Notice,
16 Verbose,
17 Warning,
18}
19
20impl From<log::Level> for ValkeyLogLevel {
21 fn from(value: log::Level) -> Self {
22 match value {
23 log::Level::Error | log::Level::Warn => Self::Warning,
24 log::Level::Info => Self::Notice,
25 log::Level::Debug => Self::Verbose,
26 log::Level::Trace => Self::Debug,
27 }
28 }
29}
30
31pub(crate) fn log_internal<L: Into<ValkeyLogLevel>>(
32 ctx: *mut raw::RedisModuleCtx,
33 level: L,
34 message: &str,
35) {
36 if cfg!(test) {
37 return;
38 }
39
40 let level = CString::new(level.into().as_ref()).unwrap();
41 let fmt = CString::new(message).unwrap();
42 unsafe {
43 raw::RedisModule_Log.expect(NOT_INITIALISED_MESSAGE)(ctx, level.as_ptr(), fmt.as_ptr())
44 }
45}
46
47#[allow(clippy::not_unsafe_ptr_arg_deref)]
50pub fn log_io_error(io: *mut raw::RedisModuleIO, level: ValkeyLogLevel, message: &str) {
51 if cfg!(test) {
52 return;
53 }
54 let level = CString::new(level.as_ref()).unwrap();
55 let fmt = CString::new(message).unwrap();
56 unsafe {
57 raw::RedisModule_LogIOError.expect(NOT_INITIALISED_MESSAGE)(
58 io,
59 level.as_ptr(),
60 fmt.as_ptr(),
61 )
62 }
63}
64
65pub fn log<T: AsRef<str>>(level: ValkeyLogLevel, message: T) {
69 log_internal(ptr::null_mut(), level, message.as_ref());
70}
71
72pub fn log_debug<T: AsRef<str>>(message: T) {
74 log(ValkeyLogLevel::Debug, message.as_ref());
75}
76
77pub fn log_notice<T: AsRef<str>>(message: T) {
79 log(ValkeyLogLevel::Notice, message.as_ref());
80}
81
82pub fn log_verbose<T: AsRef<str>>(message: T) {
84 log(ValkeyLogLevel::Verbose, message.as_ref());
85}
86
87pub fn log_warning<T: AsRef<str>>(message: T) {
89 log(ValkeyLogLevel::Warning, message.as_ref());
90}
91
92pub mod standard_log_implementation {
94 use std::sync::{atomic::Ordering, OnceLock};
95
96 use crate::ValkeyError;
97
98 use super::*;
99 use log::{Metadata, Record, SetLoggerError};
100
101 struct ValkeyGlobalLogger(*mut raw::RedisModuleCtx);
110
111 unsafe impl Send for ValkeyGlobalLogger {}
117 unsafe impl Sync for ValkeyGlobalLogger {}
118
119 #[allow(dead_code)]
144 pub fn setup() -> Result<(), ValkeyError> {
145 let pointer = crate::MODULE_CONTEXT.ctx.load(Ordering::Relaxed);
146 if pointer.is_null() {
147 return Err(ValkeyError::Str(NOT_INITIALISED_MESSAGE));
148 }
149 setup_for_context(pointer)
150 .map_err(|e| ValkeyError::String(format!("Couldn't set up the logger: {e}")))
151 }
152
153 fn logger(context: *mut raw::RedisModuleCtx) -> &'static ValkeyGlobalLogger {
154 static LOGGER: OnceLock<ValkeyGlobalLogger> = OnceLock::new();
155 LOGGER.get_or_init(|| ValkeyGlobalLogger(context))
156 }
157
158 #[allow(dead_code)]
160 pub fn setup_for_context(context: *mut raw::RedisModuleCtx) -> Result<(), SetLoggerError> {
161 log::set_logger(logger(context)).map(|()| log::set_max_level(log::LevelFilter::Trace))
162 }
163
164 impl log::Log for ValkeyGlobalLogger {
165 fn enabled(&self, _: &Metadata) -> bool {
166 true
167 }
168
169 fn log(&self, record: &Record) {
170 if !self.enabled(record.metadata()) {
171 return;
172 }
173
174 let message = match record.level() {
175 log::Level::Debug | log::Level::Trace => {
176 format!(
177 "'{}' {}:{}: {}",
178 record.module_path().unwrap_or_default(),
179 record.file().unwrap_or("Unknown"),
180 record.line().unwrap_or(0),
181 record.args()
182 )
183 }
184 _ => record.args().to_string(),
185 };
186
187 log_internal(self.0, record.level(), &message);
188 }
189
190 fn flush(&self) {
191 }
193 }
194}
195pub use standard_log_implementation::*;