Skip to content

Commit

Permalink
Use the module context for the standard logging.
Browse files Browse the repository at this point in the history
  • Loading branch information
iddm committed May 11, 2023
1 parent 409e17e commit de65b4e
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 24 deletions.
12 changes: 9 additions & 3 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,23 @@ impl CallOptionsBuilder {
/// It is implemented `Send` and `Sync` so it can safely be used
/// from within different threads.
pub struct DetachedContext {
ctx: AtomicPtr<raw::RedisModuleCtx>,
pub(crate) ctx: AtomicPtr<raw::RedisModuleCtx>,
}

impl Default for DetachedContext {
fn default() -> Self {
impl DetachedContext {
pub const fn new() -> Self {
DetachedContext {
ctx: AtomicPtr::new(ptr::null_mut()),
}
}
}

impl Default for DetachedContext {
fn default() -> Self {
Self::new()
}
}

impl DetachedContext {
pub fn log(&self, level: RedisLogLevel, message: &str) {
let c = self.ctx.load(Ordering::Relaxed);
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ pub use crate::raw::*;
pub use crate::redismodule::*;
use backtrace::Backtrace;

/// The detached Redis module context (the context of this module). It
/// is only set to a proper value after the module is initialised via the
/// provided [redis_module] macro.
/// See [DetachedContext].
pub static MODULE_CONTEXT: DetachedContext = DetachedContext::new();

pub fn base_info_func(
ctx: &InfoContext,
for_crash_report: bool,
Expand Down
100 changes: 79 additions & 21 deletions src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,27 @@ pub enum RedisLogLevel {
Warning,
}

pub(crate) fn log_internal(ctx: *mut raw::RedisModuleCtx, level: RedisLogLevel, message: &str) {
impl From<log::Level> for RedisLogLevel {
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::Debug,
log::Level::Trace => Self::Verbose,
}
}
}

pub(crate) fn log_internal<L: Into<RedisLogLevel>>(
ctx: *mut raw::RedisModuleCtx,
level: L,
message: &str,
) {
if cfg!(test) {
return;
}

let level = CString::new(level.as_ref()).unwrap();
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())
Expand Down Expand Up @@ -76,10 +91,14 @@ pub fn log_warning<T: AsRef<str>>(message: T) {

/// The [log] crate implementation of logging.
pub mod standard_log_implementation {
use std::sync::atomic::Ordering;

use crate::RedisError;

use super::*;
use log::{Level, Metadata, Record, SetLoggerError};
use log::{Metadata, Record, SetLoggerError};

static LOGGER: RedisGlobalLogger = RedisGlobalLogger;
static mut LOGGER: RedisGlobalLogger = RedisGlobalLogger(ptr::null_mut());

/// The struct which has an implementation of the [log] crate's
/// logging interface.
Expand All @@ -89,13 +108,32 @@ pub mod standard_log_implementation {
/// Redis does not support logging at the [log::Level::Error] level,
/// so logging at this level will be converted to logging at the
/// [log::Level::Warn] level under the hood.
struct RedisGlobalLogger;
struct RedisGlobalLogger(*mut raw::RedisModuleCtx);

// The pointer of the Global logger can only be changed once during
// the startup. Once one of the [std::sync::OnceLock] or
// [std::sync::OnceCell] is stabilised, we can remove these unsafe
// trait implementations in favour of using the aforementioned safe
// types.
unsafe impl Send for RedisGlobalLogger {}
unsafe impl Sync for RedisGlobalLogger {}

/// Sets this logger as a global logger. Use this method to set
/// up the logger. If this method is never called, the default
/// logger is used which redirects the logging to the standard
/// input/output streams.
///
/// # Note
///
/// The logging context (the module context [raw::RedisModuleCtx])
/// is set by the [crate::redis_module] macro. If another context
/// should be used, please consider using the [setup_for_context]
/// method instead.
///
/// In case this function is invoked before the initialisation, and
/// so without the redis module context, no context will be used for
/// the logging, however, the logger will be set.
///
/// # Example
///
/// This function may be called on a module startup, within the
Expand All @@ -105,8 +143,22 @@ pub mod standard_log_implementation {
/// [raw::Export_RedisModule_Init] function when loading the
/// module).
#[allow(dead_code)]
pub fn setup() -> Result<(), SetLoggerError> {
log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Trace))
pub fn setup() -> Result<(), RedisError> {
let pointer = crate::MODULE_CONTEXT.ctx.load(Ordering::Relaxed);
if pointer.is_null() {
return Err(RedisError::Str(NOT_INITIALISED_MESSAGE));
}
setup_for_context(pointer)
.map_err(|e| RedisError::String(format!("Couldn't set up the logger: {e}")))
}

/// The same as [setup] but sets the custom module context.
#[allow(dead_code)]
pub fn setup_for_context(context: *mut raw::RedisModuleCtx) -> Result<(), SetLoggerError> {
unsafe {
LOGGER.0 = context;
log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Trace))
}
}

impl log::Log for RedisGlobalLogger {
Expand All @@ -119,20 +171,26 @@ pub mod standard_log_implementation {
return;
}

let message = format!(
"'{}' {}:{}: {}",
record.module_path().unwrap_or_default(),
record.file().unwrap_or("Unknown"),
record.line().unwrap_or(0),
record.args()
);

match record.level() {
Level::Debug => log_debug(message),
Level::Error | Level::Warn => log_warning(message),
Level::Info => log_notice(message),
Level::Trace => log_verbose(message),
}
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()
)
}
_ => {
format!(
"'{}' {}",
record.module_path().unwrap_or_default(),
record.args()
)
}
};

log_internal(self.0, record.level(), &message);
}

fn flush(&self) {
Expand Down
3 changes: 3 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ macro_rules! redis_module {
) } == raw::Status::Err as c_int { return raw::Status::Err as c_int; }

let context = $crate::Context::new(ctx);
unsafe {
let _ = $crate::MODULE_CONTEXT.set_context(&context);
}
let args = $crate::decode_args(ctx, argv, argc);

$(
Expand Down

0 comments on commit de65b4e

Please sign in to comment.