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;
pub trait LogHandler: Send + Sync {
fn log(&self, timestamp: &SystemTime, thread_id: &str, message: &str);
}
#[repr(C)]
pub struct leveldb_rs_log_line_t {
timestamp: SystemTime,
rust_logger: usize,
}
type LogFn = unsafe extern "C" fn(*mut leveldb_rs_log_line_t, *const c_char, *const c_char);
unsafe extern "C" {
pub fn leveldb_rs_logger_create(rust_logger: usize, log_fn: LogFn) -> *mut leveldb_logger_t;
fn leveldb_rs_logger_destroy(logger: *mut leveldb_logger_t);
}
#[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)
}
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_destroy(logline: *mut leveldb_rs_log_line_t) {
unsafe {
let _ = Box::from_raw(logline);
}
}
pub struct Logger<H: LogHandler + Send + Sync + 'static> {
handler: H,
}
impl<H: LogHandler + Send + Sync + 'static> Logger<H> {
pub fn new(handler: H) -> Self {
Logger { handler }
}
fn process(&self, logline: &leveldb_rs_log_line_t, thread_id: &str, message: &str) {
self.handler.log(&logline.timestamp, thread_id, message);
}
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;
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>
}
pub fn into_opaque(self) -> OpaqueLogger {
OpaqueLogger::new(self)
}
}
pub struct OpaqueLogger {
ptr: *mut c_void,
raw_ptr: *mut leveldb_logger_t,
log_fn: LogFn,
drop_fn: unsafe extern "C" fn(*mut c_void),
}
unsafe impl Send for OpaqueLogger {}
unsafe impl Sync for OpaqueLogger {}
impl OpaqueLogger {
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>,
}
}
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;
}
}
pub fn raw_ptr(&self) -> *mut leveldb_logger_t {
self.raw_ptr
}
}
impl Drop for OpaqueLogger {
fn drop(&mut self) {
unsafe {
if !self.raw_ptr.is_null() {
leveldb_rs_logger_destroy(self.raw_ptr);
self.raw_ptr = ptr::null_mut();
}
(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() {
let callback = Logger::<TestHandler>::do_log();
assert!(!(callback as usize == 0));
}
}