Skip to content

Commit 409e17e

Browse files
committed
Provide the standard logging facility implementation.
As the crate is mostly used as a redis module, the logging will most likely be only useful when printed on the server. As many rustaceans are familiar and used to using the de-facto standard "log" crate, it makes sense to provide the logging implementation for this crate to enable the most-commonly used way of logging messages in Rust.
1 parent 02c1ab3 commit 409e17e

File tree

6 files changed

+134
-60
lines changed

6 files changed

+134
-60
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ serde = { version = "1", features = ["derive"] }
104104
nix = "0.26"
105105
cfg-if = "1"
106106
redis-module-macros-internals = { path = "./redismodule-rs-macros-internals" }
107+
log = "0.4"
107108

108109
[dev-dependencies]
109110
anyhow = "1"

examples/load_unload.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use redis_module::{redis_module, Context, LogLevel, RedisString, Status};
1+
use redis_module::{logging::RedisLogLevel, redis_module, Context, RedisString, Status};
22

33
static mut GLOBAL_STATE: Option<String> = None;
44

@@ -10,7 +10,7 @@ fn init(ctx: &Context, args: &[RedisString]) -> Status {
1010
(before, after)
1111
};
1212
ctx.log(
13-
LogLevel::Warning,
13+
RedisLogLevel::Warning,
1414
&format!("Update global state on LOAD. BEFORE: {before:?}, AFTER: {after:?}",),
1515
);
1616

@@ -24,7 +24,7 @@ fn deinit(ctx: &Context) -> Status {
2424
(before, after)
2525
};
2626
ctx.log(
27-
LogLevel::Warning,
27+
RedisLogLevel::Warning,
2828
&format!("Update global state on UNLOAD. BEFORE: {before:?}, AFTER: {after:?}"),
2929
);
3030

src/context/call_reply.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -548,20 +548,15 @@ impl TryFrom<&str> for VerbatimStringFormat {
548548
)));
549549
}
550550
let mut res = VerbatimStringFormat::default();
551-
value
552-
.chars()
553-
.into_iter()
554-
.take(3)
555-
.enumerate()
556-
.try_for_each(|(i, c)| {
557-
if c as u32 >= 127 {
558-
return Err(RedisError::String(
559-
"Verbatim format must contains only ASCI values.".to_owned(),
560-
));
561-
}
562-
res.0[i] = c as c_char;
563-
Ok(())
564-
})?;
551+
value.chars().take(3).enumerate().try_for_each(|(i, c)| {
552+
if c as u32 >= 127 {
553+
return Err(RedisError::String(
554+
"Verbatim format must contains only ASCI values.".to_owned(),
555+
));
556+
}
557+
res.0[i] = c as c_char;
558+
Ok(())
559+
})?;
565560
Ok(res)
566561
}
567562
}

src/context/mod.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use std::os::raw::{c_char, c_int, c_long, c_longlong};
66
use std::ptr::{self, NonNull};
77
use std::sync::atomic::{AtomicPtr, Ordering};
88

9+
use crate::add_info_section;
910
use crate::key::{RedisKey, RedisKeyWritable};
11+
use crate::logging::RedisLogLevel;
1012
use crate::raw::{ModuleOptions, Version};
1113
use crate::redisvalue::RedisValueKey;
1214
use crate::{add_info_field_long_long, add_info_field_str, raw, utils, Status};
13-
use crate::{add_info_section, LogLevel};
1415
use crate::{RedisError, RedisResult, RedisString, RedisValue};
1516

1617
use std::ffi::CStr;
@@ -135,25 +136,25 @@ impl Default for DetachedContext {
135136
}
136137

137138
impl DetachedContext {
138-
pub fn log(&self, level: LogLevel, message: &str) {
139+
pub fn log(&self, level: RedisLogLevel, message: &str) {
139140
let c = self.ctx.load(Ordering::Relaxed);
140141
crate::logging::log_internal(c, level, message);
141142
}
142143

143144
pub fn log_debug(&self, message: &str) {
144-
self.log(LogLevel::Debug, message);
145+
self.log(RedisLogLevel::Debug, message);
145146
}
146147

147148
pub fn log_notice(&self, message: &str) {
148-
self.log(LogLevel::Notice, message);
149+
self.log(RedisLogLevel::Notice, message);
149150
}
150151

151152
pub fn log_verbose(&self, message: &str) {
152-
self.log(LogLevel::Verbose, message);
153+
self.log(RedisLogLevel::Verbose, message);
153154
}
154155

155156
pub fn log_warning(&self, message: &str) {
156-
self.log(LogLevel::Warning, message);
157+
self.log(RedisLogLevel::Warning, message);
157158
}
158159

159160
pub fn set_context(&self, ctx: &Context) -> Result<(), RedisError> {
@@ -266,24 +267,24 @@ impl Context {
266267
}
267268
}
268269

269-
pub fn log(&self, level: LogLevel, message: &str) {
270+
pub fn log(&self, level: RedisLogLevel, message: &str) {
270271
crate::logging::log_internal(self.ctx, level, message);
271272
}
272273

273274
pub fn log_debug(&self, message: &str) {
274-
self.log(LogLevel::Debug, message);
275+
self.log(RedisLogLevel::Debug, message);
275276
}
276277

277278
pub fn log_notice(&self, message: &str) {
278-
self.log(LogLevel::Notice, message);
279+
self.log(RedisLogLevel::Notice, message);
279280
}
280281

281282
pub fn log_verbose(&self, message: &str) {
282-
self.log(LogLevel::Verbose, message);
283+
self.log(RedisLogLevel::Verbose, message);
283284
}
284285

285286
pub fn log_warning(&self, message: &str) {
286-
self.log(LogLevel::Warning, message);
287+
self.log(RedisLogLevel::Warning, message);
287288
}
288289

289290
/// # Panics

src/lib.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
//#![allow(dead_code)]
2-
31
pub use crate::context::InfoContext;
4-
use strum_macros::AsRefStr;
52
extern crate num_traits;
63

74
pub mod alloc;
@@ -44,16 +41,6 @@ pub use crate::raw::*;
4441
pub use crate::redismodule::*;
4542
use backtrace::Backtrace;
4643

47-
/// `LogLevel` is a level of logging to be specified with a Redis log directive.
48-
#[derive(Clone, Copy, Debug, AsRefStr)]
49-
#[strum(serialize_all = "snake_case")]
50-
pub enum LogLevel {
51-
Debug,
52-
Notice,
53-
Verbose,
54-
Warning,
55-
}
56-
5744
pub fn base_info_func(
5845
ctx: &InfoContext,
5946
for_crash_report: bool,

src/logging.rs

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,143 @@
11
use crate::raw;
2-
use crate::LogLevel;
32
use std::ffi::CString;
43
use std::ptr;
4+
use strum_macros::AsRefStr;
55

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) {
721
if cfg!(test) {
822
return;
923
}
1024

1125
let level = CString::new(level.as_ref()).unwrap();
1226
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+
}
1430
}
1531

1632
/// This function should be used when a callback is returning a critical error
1733
/// to the caller since cannot load or save the data for some critical reason.
1834
#[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) {
2036
if cfg!(test) {
2137
return;
2238
}
2339
let level = CString::new(level.as_ref()).unwrap();
2440
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+
}
2648
}
2749

2850
/// Log a message to the Redis log with the given log level, without
2951
/// requiring a context. This prevents Redis from including the module
3052
/// 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());
3355
}
3456

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());
3860
}
3961

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());
4365
}
4466

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());
4870
}
4971

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+
}
53142
}
143+
pub use standard_log_implementation::*;

0 commit comments

Comments
 (0)