Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = ["couchbase"]
categories = ["database"]

[dependencies]
bitflags = "2.9.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR review help: New dependency to implement bit masks (we need to map to a CBlite C structure that create bit mask to concatenate domains to log, see

/** 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. */
typedef CBL_OPTIONS(uint16_t, CBLLogDomainMask) {
kCBLLogDomainMaskDatabase = 1 << kCBLLogDomainDatabase,
kCBLLogDomainMaskQuery = 1 << kCBLLogDomainQuery,
kCBLLogDomainMaskReplicator = 1 << kCBLLogDomainReplicator,
kCBLLogDomainMaskNetwork = 1 << kCBLLogDomainNetwork,
kCBLLogDomainMaskAll = 0xFF
};

enum_primitive = "0.1.1"

[dev-dependencies]
Expand Down
8 changes: 5 additions & 3 deletions c_playground/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR review help: Just for testing...


// Open database
CBLError error;
Expand Down
6 changes: 1 addition & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
}
}
}
Expand Down
129 changes: 49 additions & 80 deletions src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -50,96 +50,65 @@ enum_from_primitive! {
}
}

pub type LogCallback = Option<fn(Domain, Level, &str)>;
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<fn(Domain, Level, &str)>;

/** 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,
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR review help: implementation of CBLite C lib (thanks to bindings), more info in

fn generate_bindings() -> Result<(), Box<dyn Error>> {

}
}

/** 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)*)));
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR review help: Removed because useless

//////// INTERNALS:

static mut LOG_CALLBACK: LogCallback = None;
Expand Down
14 changes: 1 addition & 13 deletions src/replicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
}
Expand All @@ -494,7 +489,6 @@ pub extern "C" fn c_collection_property_encryptor(
}
});
} else {
error!("Encryption input is None");
error = Error::cbl_error(CouchbaseLiteError::Crypto);
}

Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
}
Expand All @@ -655,7 +644,6 @@ pub extern "C" fn c_collection_property_decryptor(
}
});
} else {
error!("Decryption input is None");
error = Error::cbl_error(CouchbaseLiteError::Crypto);
}

Expand Down
27 changes: 20 additions & 7 deletions tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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 {
Expand All @@ -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() {
Expand All @@ -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
);
Expand All @@ -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 :-)");
}
}
}
Expand Down