1use crate::raw;
2use std::ffi::CString;
3use std::ptr;
4use strum_macros::AsRefStr;
56const NOT_INITIALISED_MESSAGE: &str = "Redis module hasn't been initialised.";
78/// [RedisLogLevel] is a level of logging which can be used when
9/// logging with Redis. See [raw::RedisModule_Log] and the official
10/// redis [reference](https://redis.io/docs/reference/modules/modules-api-ref/).
11#[derive(Clone, Copy, Debug, AsRefStr)]
12#[strum(serialize_all = "snake_case")]
13pub enum RedisLogLevel {
14 Debug,
15 Notice,
16 Verbose,
17 Warning,
18}
1920impl From<log::Level> for RedisLogLevel {
21fn from(value: log::Level) -> Self {
22match 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}
3031pub(crate) fn log_internal<L: Into<RedisLogLevel>>(
32 ctx: *mut raw::RedisModuleCtx,
33 level: L,
34 message: &str,
35) {
36if cfg!(test) {
37return;
38 }
3940let level = CString::new(level.into().as_ref()).unwrap();
41let fmt = CString::new(message).unwrap();
42unsafe {
43 raw::RedisModule_Log.expect(NOT_INITIALISED_MESSAGE)(ctx, level.as_ptr(), fmt.as_ptr())
44 }
45}
4647/// This function should be used when a callback is returning a critical error
48/// to the caller since cannot load or save the data for some critical reason.
49#[allow(clippy::not_unsafe_ptr_arg_deref)]
50pub fn log_io_error(io: *mut raw::RedisModuleIO, level: RedisLogLevel, message: &str) {
51if cfg!(test) {
52return;
53 }
54let level = CString::new(level.as_ref()).unwrap();
55let fmt = CString::new(message).unwrap();
56unsafe {
57 raw::RedisModule_LogIOError.expect(NOT_INITIALISED_MESSAGE)(
58 io,
59 level.as_ptr(),
60 fmt.as_ptr(),
61 )
62 }
63}
6465/// Log a message to the Redis log with the given log level, without
66/// requiring a context. This prevents Redis from including the module
67/// name in the logged message.
68pub fn log<T: AsRef<str>>(level: RedisLogLevel, message: T) {
69 log_internal(ptr::null_mut(), level, message.as_ref());
70}
7172/// Log a message to Redis at the [RedisLogLevel::Debug] level.
73pub fn log_debug<T: AsRef<str>>(message: T) {
74 log(RedisLogLevel::Debug, message.as_ref());
75}
7677/// Log a message to Redis at the [RedisLogLevel::Notice] level.
78pub fn log_notice<T: AsRef<str>>(message: T) {
79 log(RedisLogLevel::Notice, message.as_ref());
80}
8182/// Log a message to Redis at the [RedisLogLevel::Verbose] level.
83pub fn log_verbose<T: AsRef<str>>(message: T) {
84 log(RedisLogLevel::Verbose, message.as_ref());
85}
8687/// Log a message to Redis at the [RedisLogLevel::Warning] level.
88pub fn log_warning<T: AsRef<str>>(message: T) {
89 log(RedisLogLevel::Warning, message.as_ref());
90}
9192/// The [log] crate implementation of logging.
93pub mod standard_log_implementation {
94use std::sync::atomic::Ordering;
9596use crate::RedisError;
9798use super::*;
99use log::{Metadata, Record, SetLoggerError};
100101static mut LOGGER: RedisGlobalLogger = RedisGlobalLogger(ptr::null_mut());
102103/// The struct which has an implementation of the [log] crate's
104 /// logging interface.
105 ///
106 /// # Note
107 ///
108 /// Redis does not support logging at the [log::Level::Error] level,
109 /// so logging at this level will be converted to logging at the
110 /// [log::Level::Warn] level under the hood.
111struct RedisGlobalLogger(*mut raw::RedisModuleCtx);
112113// The pointer of the Global logger can only be changed once during
114 // the startup. Once one of the [std::sync::OnceLock] or
115 // [std::sync::OnceCell] is stabilised, we can remove these unsafe
116 // trait implementations in favour of using the aforementioned safe
117 // types.
118unsafe impl Send for RedisGlobalLogger {}
119unsafe impl Sync for RedisGlobalLogger {}
120121/// Sets this logger as a global logger. Use this method to set
122 /// up the logger. If this method is never called, the default
123 /// logger is used which redirects the logging to the standard
124 /// input/output streams.
125 ///
126 /// # Note
127 ///
128 /// The logging context (the module context [raw::RedisModuleCtx])
129 /// is set by the [crate::redis_module] macro. If another context
130 /// should be used, please consider using the [setup_for_context]
131 /// method instead.
132 ///
133 /// In case this function is invoked before the initialisation, and
134 /// so without the redis module context, no context will be used for
135 /// the logging, however, the logger will be set.
136 ///
137 /// # Example
138 ///
139 /// This function may be called on a module startup, within the
140 /// module initialisation function (specified in the
141 /// [crate::redis_module] as the `init` argument, which will be used
142 /// for the module initialisation and will be passed to the
143 /// [raw::Export_RedisModule_Init] function when loading the
144 /// module).
145#[allow(dead_code)]
146pub fn setup() -> Result<(), RedisError> {
147let pointer = crate::MODULE_CONTEXT.ctx.load(Ordering::Relaxed);
148if pointer.is_null() {
149return Err(RedisError::Str(NOT_INITIALISED_MESSAGE));
150 }
151 setup_for_context(pointer)
152 .map_err(|e| RedisError::String(format!("Couldn't set up the logger: {e}")))
153 }
154155/// The same as [setup] but sets the custom module context.
156#[allow(dead_code)]
157pub fn setup_for_context(context: *mut raw::RedisModuleCtx) -> Result<(), SetLoggerError> {
158unsafe {
159 LOGGER.0 = context;
160 log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Trace))
161 }
162 }
163164impl log::Log for RedisGlobalLogger {
165fn enabled(&self, _: &Metadata) -> bool {
166true
167}
168169fn log(&self, record: &Record) {
170if !self.enabled(record.metadata()) {
171return;
172 }
173174let message = match record.level() {
175 log::Level::Debug | log::Level::Trace => {
176format!(
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 };
186187 log_internal(self.0, record.level(), &message);
188 }
189190fn flush(&self) {
191// The flushing isn't required for the Redis logging.
192}
193 }
194}
195pub use standard_log_implementation::*;