diff --git a/Cargo.toml b/Cargo.toml index 19e172e..7306144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["couchbase"] categories = ["database"] [dependencies] +bitflags = "2.9.0" enum_primitive = "0.1.1" [dev-dependencies] diff --git a/c_playground/main.c b/c_playground/main.c index 21bc5f2..7f4faac 100644 --- a/c_playground/main.c +++ b/c_playground/main.c @@ -23,9 +23,11 @@ void log_callback(CBLLogDomain domain, CBLLogLevel level, FLString message) { } int main(void) { - CBLLog_SetCallbackLevel(kCBLLogVerbose); - CBLLog_SetConsoleLevel(kCBLLogVerbose); - CBLLog_SetCallback(log_callback); + CBLConsoleLogSink log_sink = {}; + log_sink.level = kCBLLogDebug; + log_sink.domains = kCBLLogDomainMaskAll; + + CBLLogSinks_SetConsole(log_sink); // Open database CBLError error; diff --git a/src/error.rs b/src/error.rs index 7519ba6..8d3f851 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,6 @@ use crate::c_api::{ CBLError, CBLErrorDomain, CBLError_Message, FLError, kCBLDomain, kCBLFleeceDomain, kCBLNetworkDomain, kCBLPOSIXDomain, kCBLSQLiteDomain, kCBLWebSocketDomain, }; -use crate::error; use enum_primitive::FromPrimitive; use std::fmt; @@ -258,10 +257,7 @@ impl Error { unsafe { CBLError_Message(&self.as_cbl_error()) .to_string() - .unwrap_or_else(|| { - error!("Generating the error message for error ({:?}) and internal info ({:?}) failed", self.code, self.internal_info); - "Unknown error".to_string() - }) + .unwrap_or_default() } } } diff --git a/src/logging.rs b/src/logging.rs index d841a7e..ca19df9 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -15,14 +15,14 @@ // limitations under the License. // +use bitflags::bitflags; use crate::c_api::{ - CBLLogDomain, CBLLogLevel, CBLLog_SetCallback, CBLLog_SetCallbackLevel, CBLLog_SetConsoleLevel, - CBL_Log, FLString, + kCBLLogDomainMaskAll, kCBLLogDomainMaskDatabase, kCBLLogDomainMaskNetwork, + kCBLLogDomainMaskQuery, kCBLLogDomainMaskReplicator, CBLConsoleLogSink, CBLCustomLogSink, + CBLLogDomain, CBLLogLevel, CBLLogSinks_SetConsole, CBLLogSinks_SetCustom, FLString, }; use enum_primitive::FromPrimitive; -use std::fmt; -use std::ffi::CString; enum_from_primitive! { /** Logging domains: subsystems that generate log messages. */ @@ -50,96 +50,65 @@ enum_from_primitive! { } } -pub type LogCallback = Option; +bitflags! { + /** A bitmask representing a set of logging domains. + * + * Use this bitmask to specify one or more logging domains by combining the + * constants with the bitwise OR operator (`|`). This is helpful for enabling + * or filtering logs for specific domains. */ + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct DomainMask: u32 { + const DATABASE = kCBLLogDomainMaskDatabase; + const QUERY = kCBLLogDomainMaskQuery; + const REPLICATOR = kCBLLogDomainMaskReplicator; + const NETWORK = kCBLLogDomainMaskNetwork; + const ALL = kCBLLogDomainMaskAll; + } +} -/** Sets the detail level of console logging. -Only messages whose level is ≥ the given level will be logged to the console. -Default value is Info. */ -pub fn set_console_level(level: Level) { - unsafe { CBLLog_SetConsoleLevel(level as u8) } +/** Console log sink configuration for logging to the cosole. */ +pub struct ConsoleLogSink { + // The minimum level of message to write (Required). + pub level: Level, + // Bitmask for enabled log domains. + pub domains: DomainMask, } -/** Sets the detail level of logging to the registered callback (if any.) -Only messages whose level is ≥ the given level will be logged to the callback. -Default value is Info. */ -pub fn set_callback_level(level: Level) { - unsafe { CBLLog_SetCallbackLevel(level as u8) } +pub type LogCallback = Option; + +/** Custom log sink configuration for logging to a user-defined callback. */ +pub struct CustomLogSink { + // The minimum level of message to write (Required). + pub level: Level, + // Custom log callback (Required). + pub callback: LogCallback, + // Bitmask for enabled log domains. + pub domains: DomainMask, } -/** Registers a function that will receive log messages. */ -pub fn set_callback(callback: LogCallback) { +/** Set the console log sink. To disable the console log sink, set the log level to None. */ +pub fn set_console_log_sink(log_sink: ConsoleLogSink) { unsafe { - LOG_CALLBACK = callback; - if callback.is_some() { - CBLLog_SetCallback(Some(invoke_log_callback)); - } else { - CBLLog_SetCallback(None); - } + CBLLogSinks_SetConsole(CBLConsoleLogSink { + level: log_sink.level as u8, + domains: log_sink.domains.bits() as u16, + }) } } -/** Writes a log message. */ -pub fn write(domain: Domain, level: Level, message: &str) { +/** Set the custom log sink. To disable the custom log sink, set the log level to None. */ +pub fn set_custom_log_sink(log_sink: CustomLogSink) { unsafe { - let cstr = CString::new(message).unwrap(); - CBL_Log(domain as u8, level as u8, cstr.as_ptr()); + LOG_CALLBACK = log_sink.callback; - // CBL_Log doesn't invoke the callback, so do it manually: - if let Some(callback) = LOG_CALLBACK { - //if CBLLog_WillLogToConsole(domain as u8, level as u8) { - callback(domain, level, message); - //} - } + CBLLogSinks_SetCustom(CBLCustomLogSink { + level: log_sink.level as u8, + callback: Some(invoke_log_callback), + domains: log_sink.domains.bits() as u16, + }) } } -/** Writes a log message using the given format arguments. */ -pub fn write_args(domain: Domain, level: Level, args: fmt::Arguments) { - write(domain, level, &format!("{:?}", args)); -} - -//////// LOGGING MACROS: - -/// A macro that writes a formatted Error-level log message. -#[macro_export] -macro_rules! error { - ($($arg:tt)*) => ($crate::logging::write_args( - $crate::logging::Domain::Database, $crate::logging::Level::Error, - format_args!($($arg)*))); -} - -/// A macro that writes a formatted Warning-level log message. -#[macro_export] -macro_rules! warn { - ($($arg:tt)*) => ($crate::logging::write_args( - $crate::logging::Domain::Database, $crate::logging::Level::Warning, - format_args!($($arg)*))); -} - -/// A macro that writes a formatted Info-level log message. -#[macro_export] -macro_rules! info { - ($($arg:tt)*) => ($crate::logging::write_args( - $crate::logging::Domain::Database, $crate::logging::Level::Info, - format_args!($($arg)*))); -} - -/// A macro that writes a formatted Verbose-level log message. -#[macro_export] -macro_rules! verbose { - ($($arg:tt)*) => ($crate::logging::write_args( - $crate::logging::Domain::Database, $crate::logging::Level::Verbose, - format_args!($($arg)*))); -} - -/// A macro that writes a formatted Debug-level log message. -#[macro_export] -macro_rules! debug { - ($($arg:tt)*) => ($crate::logging::write_args( - $crate::logging::Domain::Database, $crate::logging::Level::Debug, - format_args!($($arg)*))); -} - //////// INTERNALS: static mut LOG_CALLBACK: LogCallback = None; diff --git a/src/replicator.rs b/src/replicator.rs index 35a0fb1..67cabb4 100644 --- a/src/replicator.rs +++ b/src/replicator.rs @@ -47,7 +47,7 @@ use crate::{ }; #[cfg(feature = "enterprise")] use crate::{ - CouchbaseLiteError, ErrorCode, error, + CouchbaseLiteError, ErrorCode, c_api::{ CBLEndpoint_CreateWithLocalDB, FLSlice, FLSliceResult, FLSliceResult_New, FLSlice_Copy, FLStringResult, @@ -395,14 +395,12 @@ pub extern "C" fn c_default_collection_property_encryptor( Err(err) => { match err { EncryptionError::Temporary => { - error!("Encryption callback returned with transient error"); error = Error { code: ErrorCode::WebSocket(503), internal_info: None, }; } EncryptionError::Permanent => { - error!("Encryption callback returned with non transient error"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } } @@ -411,7 +409,6 @@ pub extern "C" fn c_default_collection_property_encryptor( } }); } else { - error!("Encryption input is None"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } @@ -478,14 +475,12 @@ pub extern "C" fn c_collection_property_encryptor( Err(err) => { match err { EncryptionError::Temporary => { - error!("Encryption callback returned with transient error"); error = Error { code: ErrorCode::WebSocket(503), internal_info: None, }; } EncryptionError::Permanent => { - error!("Encryption callback returned with non transient error"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } } @@ -494,7 +489,6 @@ pub extern "C" fn c_collection_property_encryptor( } }); } else { - error!("Encryption input is None"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } @@ -556,14 +550,12 @@ pub extern "C" fn c_default_collection_property_decryptor( Err(err) => { match err { EncryptionError::Temporary => { - error!("Decryption callback returned with transient error"); error = Error { code: ErrorCode::WebSocket(503), internal_info: None, }; } EncryptionError::Permanent => { - error!("Decryption callback returned with non transient error"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } } @@ -572,7 +564,6 @@ pub extern "C" fn c_default_collection_property_decryptor( } }); } else { - error!("Decryption input is None"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } @@ -639,14 +630,12 @@ pub extern "C" fn c_collection_property_decryptor( Err(err) => { match err { EncryptionError::Temporary => { - error!("Decryption callback returned with transient error"); error = Error { code: ErrorCode::WebSocket(503), internal_info: None, }; } EncryptionError::Permanent => { - error!("Decryption callback returned with non transient error"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } } @@ -655,7 +644,6 @@ pub extern "C" fn c_collection_property_decryptor( } }); } else { - error!("Decryption input is None"); error = Error::cbl_error(CouchbaseLiteError::Crypto); } diff --git a/tests/utils.rs b/tests/utils.rs index bea1b23..1e76cb4 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -10,6 +10,7 @@ use std::{ }; #[cfg(feature = "enterprise")] use std::collections::HashMap; +use couchbase_lite::logging::CustomLogSink; pub const DB_NAME: &str = "test_db"; @@ -26,9 +27,15 @@ fn logger(domain: logging::Domain, level: logging::Level, message: &str) { } pub fn init_logging() { - logging::set_callback(Some(logger)); - logging::set_callback_level(logging::Level::Verbose); - logging::set_console_level(logging::Level::None); + logging::set_custom_log_sink(CustomLogSink { + level: logging::Level::Verbose, + callback: Some(logger), + domains: logging::DomainMask::ALL, + }); + logging::set_console_log_sink(logging::ConsoleLogSink { + level: logging::Level::None, + domains: logging::DomainMask::ALL, + }); } pub struct LeakChecker { @@ -37,6 +44,12 @@ pub struct LeakChecker { end_instance_count: usize, } +impl Default for LeakChecker { + fn default() -> Self { + Self::new() + } +} + impl LeakChecker { pub fn new() -> Self { if option_env!("LEAK_CHECK").is_some() { @@ -58,12 +71,12 @@ impl LeakChecker { impl Drop for LeakChecker { fn drop(&mut self) { if self.is_checking { - info!("Checking if Couchbase Lite objects were leaked by this test"); + println!("Checking if Couchbase Lite objects were leaked by this test"); self.end_instance_count = instance_count(); if self.start_instance_count != self.end_instance_count { - info!("Leaks detected :-("); - info!( + println!("Leaks detected :-("); + println!( "Instances before: {} | Instances after: {}", self.start_instance_count, self.end_instance_count ); @@ -73,7 +86,7 @@ impl Drop for LeakChecker { // default. Looking for changes in the `instance_count()` is intrinsically not thread safe. // Either run tests with `cargo test -- --test-threads`, or turn off `LEAK_CHECKS`. } else { - info!("All good :-)"); + println!("All good :-)"); } } }