|
1 | 1 | use crate::raw; |
2 | | -use crate::LogLevel; |
3 | 2 | use std::ffi::CString; |
4 | 3 | use std::ptr; |
| 4 | +use strum_macros::AsRefStr; |
5 | 5 |
|
6 | | -pub(crate) fn log_internal(ctx: *mut raw::RedisModuleCtx, level: LogLevel, message: &str) { |
| 6 | +const NOT_INITIALISED_MESSAGE: &str = "Redis module hasn't been initialised."; |
| 7 | + |
| 8 | +/// [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")] |
| 13 | +pub enum RedisLogLevel { |
| 14 | + Debug, |
| 15 | + Notice, |
| 16 | + Verbose, |
| 17 | + Warning, |
| 18 | +} |
| 19 | + |
| 20 | +pub(crate) fn log_internal(ctx: *mut raw::RedisModuleCtx, level: RedisLogLevel, message: &str) { |
7 | 21 | if cfg!(test) { |
8 | 22 | return; |
9 | 23 | } |
10 | 24 |
|
11 | 25 | let level = CString::new(level.as_ref()).unwrap(); |
12 | 26 | let fmt = CString::new(message).unwrap(); |
13 | | - unsafe { raw::RedisModule_Log.unwrap()(ctx, level.as_ptr(), fmt.as_ptr()) } |
| 27 | + unsafe { |
| 28 | + raw::RedisModule_Log.expect(NOT_INITIALISED_MESSAGE)(ctx, level.as_ptr(), fmt.as_ptr()) |
| 29 | + } |
14 | 30 | } |
15 | 31 |
|
16 | 32 | /// This function should be used when a callback is returning a critical error |
17 | 33 | /// to the caller since cannot load or save the data for some critical reason. |
18 | 34 | #[allow(clippy::not_unsafe_ptr_arg_deref)] |
19 | | -pub fn log_io_error(io: *mut raw::RedisModuleIO, level: LogLevel, message: &str) { |
| 35 | +pub fn log_io_error(io: *mut raw::RedisModuleIO, level: RedisLogLevel, message: &str) { |
20 | 36 | if cfg!(test) { |
21 | 37 | return; |
22 | 38 | } |
23 | 39 | let level = CString::new(level.as_ref()).unwrap(); |
24 | 40 | let fmt = CString::new(message).unwrap(); |
25 | | - unsafe { raw::RedisModule_LogIOError.unwrap()(io, level.as_ptr(), fmt.as_ptr()) } |
| 41 | + unsafe { |
| 42 | + raw::RedisModule_LogIOError.expect(NOT_INITIALISED_MESSAGE)( |
| 43 | + io, |
| 44 | + level.as_ptr(), |
| 45 | + fmt.as_ptr(), |
| 46 | + ) |
| 47 | + } |
26 | 48 | } |
27 | 49 |
|
28 | 50 | /// Log a message to the Redis log with the given log level, without |
29 | 51 | /// requiring a context. This prevents Redis from including the module |
30 | 52 | /// name in the logged message. |
31 | | -pub fn log(level: LogLevel, message: &str) { |
32 | | - log_internal(ptr::null_mut(), level, message); |
| 53 | +pub fn log<T: AsRef<str>>(level: RedisLogLevel, message: T) { |
| 54 | + log_internal(ptr::null_mut(), level, message.as_ref()); |
33 | 55 | } |
34 | 56 |
|
35 | | -/// Log a message to the Redis log with DEBUG log level. |
36 | | -pub fn log_debug(message: &str) { |
37 | | - log(LogLevel::Debug, message); |
| 57 | +/// Log a message to Redis at the [RedisLogLevel::Debug] level. |
| 58 | +pub fn log_debug<T: AsRef<str>>(message: T) { |
| 59 | + log(RedisLogLevel::Debug, message.as_ref()); |
38 | 60 | } |
39 | 61 |
|
40 | | -/// Log a message to the Redis log with NOTICE log level. |
41 | | -pub fn log_notice(message: &str) { |
42 | | - log(LogLevel::Notice, message); |
| 62 | +/// Log a message to Redis at the [RedisLogLevel::Notice] level. |
| 63 | +pub fn log_notice<T: AsRef<str>>(message: T) { |
| 64 | + log(RedisLogLevel::Notice, message.as_ref()); |
43 | 65 | } |
44 | 66 |
|
45 | | -/// Log a message to the Redis log with VERBOSE log level. |
46 | | -pub fn log_verbose(message: &str) { |
47 | | - log(LogLevel::Verbose, message); |
| 67 | +/// Log a message to Redis at the [RedisLogLevel::Verbose] level. |
| 68 | +pub fn log_verbose<T: AsRef<str>>(message: T) { |
| 69 | + log(RedisLogLevel::Verbose, message.as_ref()); |
48 | 70 | } |
49 | 71 |
|
50 | | -/// Log a message to the Redis log with WARNING log level. |
51 | | -pub fn log_warning(message: &str) { |
52 | | - log(LogLevel::Warning, message); |
| 72 | +/// Log a message to Redis at the [RedisLogLevel::Warning] level. |
| 73 | +pub fn log_warning<T: AsRef<str>>(message: T) { |
| 74 | + log(RedisLogLevel::Warning, message.as_ref()); |
| 75 | +} |
| 76 | + |
| 77 | +/// The [log] crate implementation of logging. |
| 78 | +pub mod standard_log_implementation { |
| 79 | + use super::*; |
| 80 | + use log::{Level, Metadata, Record, SetLoggerError}; |
| 81 | + |
| 82 | + static LOGGER: RedisGlobalLogger = RedisGlobalLogger; |
| 83 | + |
| 84 | + /// The struct which has an implementation of the [log] crate's |
| 85 | + /// logging interface. |
| 86 | + /// |
| 87 | + /// # Note |
| 88 | + /// |
| 89 | + /// Redis does not support logging at the [log::Level::Error] level, |
| 90 | + /// so logging at this level will be converted to logging at the |
| 91 | + /// [log::Level::Warn] level under the hood. |
| 92 | + struct RedisGlobalLogger; |
| 93 | + |
| 94 | + /// Sets this logger as a global logger. Use this method to set |
| 95 | + /// up the logger. If this method is never called, the default |
| 96 | + /// logger is used which redirects the logging to the standard |
| 97 | + /// input/output streams. |
| 98 | + /// |
| 99 | + /// # Example |
| 100 | + /// |
| 101 | + /// This function may be called on a module startup, within the |
| 102 | + /// module initialisation function (specified in the |
| 103 | + /// [crate::redis_module] as the `init` argument, which will be used |
| 104 | + /// for the module initialisation and will be passed to the |
| 105 | + /// [raw::Export_RedisModule_Init] function when loading the |
| 106 | + /// module). |
| 107 | + #[allow(dead_code)] |
| 108 | + pub fn setup() -> Result<(), SetLoggerError> { |
| 109 | + log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Trace)) |
| 110 | + } |
| 111 | + |
| 112 | + impl log::Log for RedisGlobalLogger { |
| 113 | + fn enabled(&self, _: &Metadata) -> bool { |
| 114 | + true |
| 115 | + } |
| 116 | + |
| 117 | + fn log(&self, record: &Record) { |
| 118 | + if !self.enabled(record.metadata()) { |
| 119 | + return; |
| 120 | + } |
| 121 | + |
| 122 | + let message = format!( |
| 123 | + "'{}' {}:{}: {}", |
| 124 | + record.module_path().unwrap_or_default(), |
| 125 | + record.file().unwrap_or("Unknown"), |
| 126 | + record.line().unwrap_or(0), |
| 127 | + record.args() |
| 128 | + ); |
| 129 | + |
| 130 | + match record.level() { |
| 131 | + Level::Debug => log_debug(message), |
| 132 | + Level::Error | Level::Warn => log_warning(message), |
| 133 | + Level::Info => log_notice(message), |
| 134 | + Level::Trace => log_verbose(message), |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + fn flush(&self) { |
| 139 | + // The flushing isn't required for the Redis logging. |
| 140 | + } |
| 141 | + } |
53 | 142 | } |
| 143 | +pub use standard_log_implementation::*; |
0 commit comments