leveldb-rs-binding 2.2.0

An interface for the LevelDB
Documentation
//! Logger wrapper for LevelDB custom logging functionality.
//!
//! This module provides a safe Rust wrapper around the custom logger implementation
//! that can be used with LevelDB options.
//!
//! # Architecture
//!
//! The logger system uses a zero-cost abstraction pattern:
//! - `Logger<H>`: Generic logger storing concrete handler type (static dispatch)
//! - Each `Logger<H>` has a monomorphized function "log_fn" generated at creation time
//! - `OpaqueLogger`: Opaque wrapper for FFI boundary and storage in Options
//!
//! # Usage
//!
//! To use the logger:
//! 1. Implement the [`LogHandler`] trait with custom logic
//! 2. Create a [`Logger`] instance with the handler
//! 3. Pass the logger to LevelDB options using `Options::set_logger()`

use crate::binding::leveldb_logger_t;
use std::ffi::{CStr, c_void};
use std::os::raw::c_char;
use std::ptr;
use std::time::SystemTime;

/// Trait for handling log messages.
///
/// Implement this trait to create custom log handlers.
/// The handler receives:
/// - `timestamp`: SystemTime when the log was created
/// - `thread_id`: Thread ID from LevelDB internally
/// - `message`: The formatted log message from LevelDB internally. Note: The `message`` is not guaranteed to end with a newline.
/// These parameters are valid until `log` function returns.
/// The `log` function should be guaranteed for thread-safe.
pub trait LogHandler: Send + Sync {
    fn log(&self, timestamp: &SystemTime, thread_id: &str, message: &str);
}

/// Represents a log line with timestamp and rust_logger address.
/// It is used by C++ layer as callback.
#[repr(C)]
pub struct leveldb_rs_log_line_t {
    timestamp: SystemTime,
    rust_logger: usize,
}

// Type for the C FFI callback function
type LogFn = unsafe extern "C" fn(*mut leveldb_rs_log_line_t, *const c_char, *const c_char);

unsafe extern "C" {
    /// Create a new RsLogger instance with rust_logger address and log_fn
    /// Returns a pointer to the logger
    ///
    /// # Safety
    ///
    /// - The `rust_logger` must be a valid address of a Rust Logger object
    /// - The `log_fn` must be a valid function pointer
    pub fn leveldb_rs_logger_create(rust_logger: usize, log_fn: LogFn) -> *mut leveldb_logger_t;

    /// Destroy the RsLogger instance and free associated resources
    fn leveldb_rs_logger_destroy(logger: *mut leveldb_logger_t);
}

/// Create a new leveldb_rs_log_line_t with rust_logger address. It would keep current timestamp.
///
/// # Safety
///
/// - The `rust_logger` must be a valid address of a Rust Logger object
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_create(rust_logger: usize) -> *mut leveldb_rs_log_line_t {
    let logline = Box::new(leveldb_rs_log_line_t {
        timestamp: SystemTime::now(),
        rust_logger,
    });
    Box::into_raw(logline)
}

/// Destroy a leveldb_rs_log_line_t instance.
///
/// # Safety
///
/// - The `logline` must be a valid pointer returned from `leveldb_rs_logline_create`
/// - The `logline` must not be used after this call
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_destroy(logline: *mut leveldb_rs_log_line_t) {
    unsafe {
        let _ = Box::from_raw(logline);
    }
}

/// A generic logger that stores a concrete handler type.
///
/// This provides zero-cost abstraction through monomorphization - the compiler
/// generates specialized code for each handler type, enabling inlining and
/// eliminating virtual function call overhead.
///
/// # Type Parameters
///
/// * `H` - The log handler type, must implement `LogHandler` and be `Send + Sync + 'static`
pub struct Logger<H: LogHandler + Send + Sync + 'static> {
    handler: H,
}

impl<H: LogHandler + Send + Sync + 'static> Logger<H> {
    /// Create a new logger instance with a custom handler.
    pub fn new(handler: H) -> Self {
        Logger { handler }
    }

    /// Process a log message using the internal handler.
    /// This method is called from C++ layer via the specialized callback.
    fn process(&self, logline: &leveldb_rs_log_line_t, thread_id: &str, message: &str) {
        self.handler.log(&logline.timestamp, thread_id, message);
    }

    /// Generate the specialized callback function for this handler type.
    ///
    /// This returns a monomorphized callback function specific to type H,
    /// enabling static dispatch and compiler optimizations.
    fn do_log() -> LogFn {
        unsafe extern "C" fn do_log_fn<H2: LogHandler + Send + Sync + 'static>(
            logline: *mut leveldb_rs_log_line_t,
            thread_id: *const c_char,
            message: *const c_char,
        ) {
            if logline.is_null() || thread_id.is_null() || message.is_null() {
                return;
            }

            unsafe {
                let thread_id_str = match CStr::from_ptr(thread_id).to_str() {
                    Ok(s) => s,
                    Err(e) => {
                        eprintln!("Warning: Invalid UTF-8 in thread_id, error: {}", e);
                        "invalid_thread_id"
                    }
                };
                let message_str = match CStr::from_ptr(message).to_str() {
                    Ok(s) => s,
                    Err(e) => {
                        eprintln!("Warning: Invalid UTF-8 in log message, error: {}", e);
                        "invalid_message"
                    }
                };

                let logline_ref = &*logline;

                // Use Strict Provenance API to recover the pointer from address
                let logger_ptr: *mut Logger<H2> =
                    ptr::null::<Logger<H2>>().with_addr(logline_ref.rust_logger) as *mut Logger<H2>;

                (*logger_ptr).process(logline_ref, thread_id_str, message_str);
            }
        }

        do_log_fn::<H>
    }

    /// Convert this logger into a type-erased version.
    ///
    /// This is used when storing the logger in Options, which cannot be generic
    /// over the handler type.
    pub fn into_opaque(self) -> OpaqueLogger {
        OpaqueLogger::new(self)
    }
}

/// Opaque logger for storage in non-generic contexts.
///
/// This struct wraps a `Logger<H>` and hides the handler type, allowing
/// it to be stored in `Options` which cannot be generic over H.
pub struct OpaqueLogger {
    /// Type-erased pointer to the Logger<H>
    ptr: *mut c_void,
    /// Raw pointer to the C++ logger
    raw_ptr: *mut leveldb_logger_t,
    /// Monomorphized callback function for this specific handler type
    log_fn: LogFn,
    /// Drop function for cleaning up the Logger<H>
    drop_fn: unsafe extern "C" fn(*mut c_void),
}

// Safety: OpaqueLogger manages a pointer to Logger<H> which is Send + Sync
unsafe impl Send for OpaqueLogger {}
unsafe impl Sync for OpaqueLogger {}

impl OpaqueLogger {
    /// Create a new OpaqueLogger from a generic Logger<H>.
    pub fn new<H: LogHandler + Send + Sync + 'static>(logger: Logger<H>) -> Self {
        let logger_box = Box::new(logger);
        let logger_ptr = Box::into_raw(logger_box) as *mut c_void;

        unsafe extern "C" fn drop<H2: LogHandler + Send + Sync + 'static>(ptr: *mut c_void) {
            unsafe {
                let _ = Box::from_raw(ptr as *mut Logger<H2>);
            }
        }

        OpaqueLogger {
            ptr: logger_ptr,
            raw_ptr: ptr::null_mut(),
            log_fn: Logger::<H>::do_log(),
            drop_fn: drop::<H>,
        }
    }

    /// Initialize the logger used by C FFI side.
    ///
    /// This must be called after creation for FFI setup.
    pub fn initialize(&mut self) {
        unsafe {
            let rust_logger_addr = self.ptr.addr();
            let raw_ptr = leveldb_rs_logger_create(rust_logger_addr, self.log_fn);
            self.raw_ptr = raw_ptr;
        }
    }

    /// Get the raw C++ logger pointer.
    pub fn raw_ptr(&self) -> *mut leveldb_logger_t {
        self.raw_ptr
    }
}

impl Drop for OpaqueLogger {
    fn drop(&mut self) {
        unsafe {
            // Clean up C++ resources first
            if !self.raw_ptr.is_null() {
                leveldb_rs_logger_destroy(self.raw_ptr);
                self.raw_ptr = ptr::null_mut();
            }
            // Then clean up the Logger<H>
            (self.drop_fn)(self.ptr);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Arc, Mutex};

    struct TestHandler;

    impl LogHandler for TestHandler {
        fn log(&self, _time: &SystemTime, _thread_id: &str, _msg: &str) {}
    }

    #[test]
    fn test_logger_lifecycle() {
        let logger = Logger::new(TestHandler);
        drop(logger);
    }

    #[test]
    fn test_opaque_logger_has_callback() {
        let logger = Logger::new(TestHandler);
        let opaque = logger.into_opaque();
        assert!(!(opaque.log_fn as usize == 0));
    }

    #[test]
    fn test_opaque_logger_lifecycle() {
        let logger = Logger::new(TestHandler);
        let opaque = logger.into_opaque();
        drop(opaque);
    }

    #[test]
    fn test_opaque_logger_initialize() {
        let logger = Logger::new(TestHandler);
        let mut opaque = logger.into_opaque();
        opaque.initialize();
        assert!(!opaque.raw_ptr().is_null());
    }

    #[test]
    fn test_logline_lifecycle() {
        let logger = Logger::new(TestHandler);
        let logger_ptr = &logger as *const Logger<TestHandler> as *mut c_void;
        unsafe {
            let logline = leveldb_rs_logline_create(logger_ptr.addr());
            assert!(!logline.is_null());
            leveldb_rs_logline_destroy(logline);
        }
    }

    #[test]
    fn test_logline_process() {
        let received = Arc::new(Mutex::new(Vec::new()));
        struct TestProcessHandler {
            received: Arc<Mutex<Vec<(SystemTime, String, String)>>>,
        }
        impl LogHandler for TestProcessHandler {
            fn log(&self, time: &SystemTime, thread_id: &str, msg: &str) {
                self.received.lock().unwrap().push((
                    time.clone(),
                    thread_id.to_string(),
                    msg.to_string(),
                ));
            }
        }
        let handler = TestProcessHandler {
            received: received.clone(),
        };
        let logger = Logger::new(handler);
        let logger_ptr = &logger as *const Logger<TestProcessHandler> as *mut c_void;

        unsafe {
            let logline = leveldb_rs_logline_create(logger_ptr.addr());
            let thread_id = std::ffi::CString::new("thread-test-id").unwrap();
            let message = std::ffi::CString::new("Test message\n").unwrap();
            let log_fn = Logger::<TestProcessHandler>::do_log();
            log_fn(logline, thread_id.as_ptr(), message.as_ptr());
            leveldb_rs_logline_destroy(logline);
        }

        let logs = received.lock().unwrap();
        assert_eq!(logs.len(), 1);
        assert_eq!(logs[0].1, "thread-test-id");
        assert_eq!(logs[0].2, "Test message\n");
    }

    #[test]
    fn test_specialized_fn_do_log() {
        // This test verifies that specialized function "do_log" are generated correctly
        let callback = Logger::<TestHandler>::do_log();
        assert!(!(callback as usize == 0));
    }
}