Skip to content

Commit e4353e8

Browse files
Nao-riscorentinmb
andauthored
cblite 3.2.2 update: New logging API (#30)
# New Logging API Couchbase Lite 3.2.2 introduces a new Logging API. The new Logging API has the following benefits: - Log sinks are now thread safe, removing risk of inconsistent states during initialization. - Simplified API and reduced implementation complexity. --------- Co-authored-by: Corentin Moreau <corentin.moreau@doctolib.com>
1 parent 3ffe3a4 commit e4353e8

File tree

6 files changed

+77
-108
lines changed

6 files changed

+77
-108
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ keywords = ["couchbase"]
1111
categories = ["database"]
1212

1313
[dependencies]
14+
bitflags = "2.9.0"
1415
enum_primitive = "0.1.1"
1516

1617
[dev-dependencies]

c_playground/main.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ void log_callback(CBLLogDomain domain, CBLLogLevel level, FLString message) {
2323
}
2424

2525
int main(void) {
26-
CBLLog_SetCallbackLevel(kCBLLogVerbose);
27-
CBLLog_SetConsoleLevel(kCBLLogVerbose);
28-
CBLLog_SetCallback(log_callback);
26+
CBLConsoleLogSink log_sink = {};
27+
log_sink.level = kCBLLogDebug;
28+
log_sink.domains = kCBLLogDomainMaskAll;
29+
30+
CBLLogSinks_SetConsole(log_sink);
2931

3032
// Open database
3133
CBLError error;

src/error.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use crate::c_api::{
2121
CBLError, CBLErrorDomain, CBLError_Message, FLError, kCBLDomain, kCBLFleeceDomain,
2222
kCBLNetworkDomain, kCBLPOSIXDomain, kCBLSQLiteDomain, kCBLWebSocketDomain,
2323
};
24-
use crate::error;
2524
use enum_primitive::FromPrimitive;
2625
use std::fmt;
2726

@@ -258,10 +257,7 @@ impl Error {
258257
unsafe {
259258
CBLError_Message(&self.as_cbl_error())
260259
.to_string()
261-
.unwrap_or_else(|| {
262-
error!("Generating the error message for error ({:?}) and internal info ({:?}) failed", self.code, self.internal_info);
263-
"Unknown error".to_string()
264-
})
260+
.unwrap_or_default()
265261
}
266262
}
267263
}

src/logging.rs

Lines changed: 49 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
// limitations under the License.
1616
//
1717

18+
use bitflags::bitflags;
1819
use crate::c_api::{
19-
CBLLogDomain, CBLLogLevel, CBLLog_SetCallback, CBLLog_SetCallbackLevel, CBLLog_SetConsoleLevel,
20-
CBL_Log, FLString,
20+
kCBLLogDomainMaskAll, kCBLLogDomainMaskDatabase, kCBLLogDomainMaskNetwork,
21+
kCBLLogDomainMaskQuery, kCBLLogDomainMaskReplicator, CBLConsoleLogSink, CBLCustomLogSink,
22+
CBLLogDomain, CBLLogLevel, CBLLogSinks_SetConsole, CBLLogSinks_SetCustom, FLString,
2123
};
2224

2325
use enum_primitive::FromPrimitive;
24-
use std::fmt;
25-
use std::ffi::CString;
2626

2727
enum_from_primitive! {
2828
/** Logging domains: subsystems that generate log messages. */
@@ -50,96 +50,65 @@ enum_from_primitive! {
5050
}
5151
}
5252

53-
pub type LogCallback = Option<fn(Domain, Level, &str)>;
53+
bitflags! {
54+
/** A bitmask representing a set of logging domains.
55+
*
56+
* Use this bitmask to specify one or more logging domains by combining the
57+
* constants with the bitwise OR operator (`|`). This is helpful for enabling
58+
* or filtering logs for specific domains. */
59+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60+
pub struct DomainMask: u32 {
61+
const DATABASE = kCBLLogDomainMaskDatabase;
62+
const QUERY = kCBLLogDomainMaskQuery;
63+
const REPLICATOR = kCBLLogDomainMaskReplicator;
64+
const NETWORK = kCBLLogDomainMaskNetwork;
65+
const ALL = kCBLLogDomainMaskAll;
66+
}
67+
}
5468

55-
/** Sets the detail level of console logging.
56-
Only messages whose level is ≥ the given level will be logged to the console.
57-
Default value is Info. */
58-
pub fn set_console_level(level: Level) {
59-
unsafe { CBLLog_SetConsoleLevel(level as u8) }
69+
/** Console log sink configuration for logging to the cosole. */
70+
pub struct ConsoleLogSink {
71+
// The minimum level of message to write (Required).
72+
pub level: Level,
73+
// Bitmask for enabled log domains.
74+
pub domains: DomainMask,
6075
}
6176

62-
/** Sets the detail level of logging to the registered callback (if any.)
63-
Only messages whose level is ≥ the given level will be logged to the callback.
64-
Default value is Info. */
65-
pub fn set_callback_level(level: Level) {
66-
unsafe { CBLLog_SetCallbackLevel(level as u8) }
77+
pub type LogCallback = Option<fn(Domain, Level, &str)>;
78+
79+
/** Custom log sink configuration for logging to a user-defined callback. */
80+
pub struct CustomLogSink {
81+
// The minimum level of message to write (Required).
82+
pub level: Level,
83+
// Custom log callback (Required).
84+
pub callback: LogCallback,
85+
// Bitmask for enabled log domains.
86+
pub domains: DomainMask,
6787
}
6888

69-
/** Registers a function that will receive log messages. */
70-
pub fn set_callback(callback: LogCallback) {
89+
/** Set the console log sink. To disable the console log sink, set the log level to None. */
90+
pub fn set_console_log_sink(log_sink: ConsoleLogSink) {
7191
unsafe {
72-
LOG_CALLBACK = callback;
73-
if callback.is_some() {
74-
CBLLog_SetCallback(Some(invoke_log_callback));
75-
} else {
76-
CBLLog_SetCallback(None);
77-
}
92+
CBLLogSinks_SetConsole(CBLConsoleLogSink {
93+
level: log_sink.level as u8,
94+
domains: log_sink.domains.bits() as u16,
95+
})
7896
}
7997
}
8098

81-
/** Writes a log message. */
82-
pub fn write(domain: Domain, level: Level, message: &str) {
99+
/** Set the custom log sink. To disable the custom log sink, set the log level to None. */
100+
pub fn set_custom_log_sink(log_sink: CustomLogSink) {
83101
unsafe {
84-
let cstr = CString::new(message).unwrap();
85-
CBL_Log(domain as u8, level as u8, cstr.as_ptr());
102+
LOG_CALLBACK = log_sink.callback;
86103

87-
// CBL_Log doesn't invoke the callback, so do it manually:
88-
if let Some(callback) = LOG_CALLBACK {
89-
//if CBLLog_WillLogToConsole(domain as u8, level as u8) {
90-
callback(domain, level, message);
91-
//}
92-
}
104+
CBLLogSinks_SetCustom(CBLCustomLogSink {
105+
level: log_sink.level as u8,
106+
callback: Some(invoke_log_callback),
107+
domains: log_sink.domains.bits() as u16,
108+
})
93109
}
94110
}
95111

96-
/** Writes a log message using the given format arguments. */
97-
pub fn write_args(domain: Domain, level: Level, args: fmt::Arguments) {
98-
write(domain, level, &format!("{:?}", args));
99-
}
100-
101-
//////// LOGGING MACROS:
102-
103-
/// A macro that writes a formatted Error-level log message.
104-
#[macro_export]
105-
macro_rules! error {
106-
($($arg:tt)*) => ($crate::logging::write_args(
107-
$crate::logging::Domain::Database, $crate::logging::Level::Error,
108-
format_args!($($arg)*)));
109-
}
110-
111-
/// A macro that writes a formatted Warning-level log message.
112-
#[macro_export]
113-
macro_rules! warn {
114-
($($arg:tt)*) => ($crate::logging::write_args(
115-
$crate::logging::Domain::Database, $crate::logging::Level::Warning,
116-
format_args!($($arg)*)));
117-
}
118-
119-
/// A macro that writes a formatted Info-level log message.
120-
#[macro_export]
121-
macro_rules! info {
122-
($($arg:tt)*) => ($crate::logging::write_args(
123-
$crate::logging::Domain::Database, $crate::logging::Level::Info,
124-
format_args!($($arg)*)));
125-
}
126-
127-
/// A macro that writes a formatted Verbose-level log message.
128-
#[macro_export]
129-
macro_rules! verbose {
130-
($($arg:tt)*) => ($crate::logging::write_args(
131-
$crate::logging::Domain::Database, $crate::logging::Level::Verbose,
132-
format_args!($($arg)*)));
133-
}
134-
135-
/// A macro that writes a formatted Debug-level log message.
136-
#[macro_export]
137-
macro_rules! debug {
138-
($($arg:tt)*) => ($crate::logging::write_args(
139-
$crate::logging::Domain::Database, $crate::logging::Level::Debug,
140-
format_args!($($arg)*)));
141-
}
142-
143112
//////// INTERNALS:
144113

145114
static mut LOG_CALLBACK: LogCallback = None;

src/replicator.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::{
4747
};
4848
#[cfg(feature = "enterprise")]
4949
use crate::{
50-
CouchbaseLiteError, ErrorCode, error,
50+
CouchbaseLiteError, ErrorCode,
5151
c_api::{
5252
CBLEndpoint_CreateWithLocalDB, FLSlice, FLSliceResult, FLSliceResult_New, FLSlice_Copy,
5353
FLStringResult,
@@ -395,14 +395,12 @@ pub extern "C" fn c_default_collection_property_encryptor(
395395
Err(err) => {
396396
match err {
397397
EncryptionError::Temporary => {
398-
error!("Encryption callback returned with transient error");
399398
error = Error {
400399
code: ErrorCode::WebSocket(503),
401400
internal_info: None,
402401
};
403402
}
404403
EncryptionError::Permanent => {
405-
error!("Encryption callback returned with non transient error");
406404
error = Error::cbl_error(CouchbaseLiteError::Crypto);
407405
}
408406
}
@@ -411,7 +409,6 @@ pub extern "C" fn c_default_collection_property_encryptor(
411409
}
412410
});
413411
} else {
414-
error!("Encryption input is None");
415412
error = Error::cbl_error(CouchbaseLiteError::Crypto);
416413
}
417414

@@ -478,14 +475,12 @@ pub extern "C" fn c_collection_property_encryptor(
478475
Err(err) => {
479476
match err {
480477
EncryptionError::Temporary => {
481-
error!("Encryption callback returned with transient error");
482478
error = Error {
483479
code: ErrorCode::WebSocket(503),
484480
internal_info: None,
485481
};
486482
}
487483
EncryptionError::Permanent => {
488-
error!("Encryption callback returned with non transient error");
489484
error = Error::cbl_error(CouchbaseLiteError::Crypto);
490485
}
491486
}
@@ -494,7 +489,6 @@ pub extern "C" fn c_collection_property_encryptor(
494489
}
495490
});
496491
} else {
497-
error!("Encryption input is None");
498492
error = Error::cbl_error(CouchbaseLiteError::Crypto);
499493
}
500494

@@ -556,14 +550,12 @@ pub extern "C" fn c_default_collection_property_decryptor(
556550
Err(err) => {
557551
match err {
558552
EncryptionError::Temporary => {
559-
error!("Decryption callback returned with transient error");
560553
error = Error {
561554
code: ErrorCode::WebSocket(503),
562555
internal_info: None,
563556
};
564557
}
565558
EncryptionError::Permanent => {
566-
error!("Decryption callback returned with non transient error");
567559
error = Error::cbl_error(CouchbaseLiteError::Crypto);
568560
}
569561
}
@@ -572,7 +564,6 @@ pub extern "C" fn c_default_collection_property_decryptor(
572564
}
573565
});
574566
} else {
575-
error!("Decryption input is None");
576567
error = Error::cbl_error(CouchbaseLiteError::Crypto);
577568
}
578569

@@ -639,14 +630,12 @@ pub extern "C" fn c_collection_property_decryptor(
639630
Err(err) => {
640631
match err {
641632
EncryptionError::Temporary => {
642-
error!("Decryption callback returned with transient error");
643633
error = Error {
644634
code: ErrorCode::WebSocket(503),
645635
internal_info: None,
646636
};
647637
}
648638
EncryptionError::Permanent => {
649-
error!("Decryption callback returned with non transient error");
650639
error = Error::cbl_error(CouchbaseLiteError::Crypto);
651640
}
652641
}
@@ -655,7 +644,6 @@ pub extern "C" fn c_collection_property_decryptor(
655644
}
656645
});
657646
} else {
658-
error!("Decryption input is None");
659647
error = Error::cbl_error(CouchbaseLiteError::Crypto);
660648
}
661649

tests/utils.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
};
1111
#[cfg(feature = "enterprise")]
1212
use std::collections::HashMap;
13+
use couchbase_lite::logging::CustomLogSink;
1314

1415
pub const DB_NAME: &str = "test_db";
1516

@@ -26,9 +27,15 @@ fn logger(domain: logging::Domain, level: logging::Level, message: &str) {
2627
}
2728

2829
pub fn init_logging() {
29-
logging::set_callback(Some(logger));
30-
logging::set_callback_level(logging::Level::Verbose);
31-
logging::set_console_level(logging::Level::None);
30+
logging::set_custom_log_sink(CustomLogSink {
31+
level: logging::Level::Verbose,
32+
callback: Some(logger),
33+
domains: logging::DomainMask::ALL,
34+
});
35+
logging::set_console_log_sink(logging::ConsoleLogSink {
36+
level: logging::Level::None,
37+
domains: logging::DomainMask::ALL,
38+
});
3239
}
3340

3441
pub struct LeakChecker {
@@ -37,6 +44,12 @@ pub struct LeakChecker {
3744
end_instance_count: usize,
3845
}
3946

47+
impl Default for LeakChecker {
48+
fn default() -> Self {
49+
Self::new()
50+
}
51+
}
52+
4053
impl LeakChecker {
4154
pub fn new() -> Self {
4255
if option_env!("LEAK_CHECK").is_some() {
@@ -58,12 +71,12 @@ impl LeakChecker {
5871
impl Drop for LeakChecker {
5972
fn drop(&mut self) {
6073
if self.is_checking {
61-
info!("Checking if Couchbase Lite objects were leaked by this test");
74+
println!("Checking if Couchbase Lite objects were leaked by this test");
6275
self.end_instance_count = instance_count();
6376

6477
if self.start_instance_count != self.end_instance_count {
65-
info!("Leaks detected :-(");
66-
info!(
78+
println!("Leaks detected :-(");
79+
println!(
6780
"Instances before: {} | Instances after: {}",
6881
self.start_instance_count, self.end_instance_count
6982
);
@@ -73,7 +86,7 @@ impl Drop for LeakChecker {
7386
// default. Looking for changes in the `instance_count()` is intrinsically not thread safe.
7487
// Either run tests with `cargo test -- --test-threads`, or turn off `LEAK_CHECKS`.
7588
} else {
76-
info!("All good :-)");
89+
println!("All good :-)");
7790
}
7891
}
7992
}

0 commit comments

Comments
 (0)