cassandra_cpp/cassandra/
log.rs

1use crate::cassandra_sys::CassLogLevel_;
2use crate::cassandra_sys::CassLogMessage;
3// use cassandra_sys::cass_log_cleanup; @deprecated
4use crate::cassandra::util::ProtectedInner;
5use crate::cassandra_sys::cass_log_set_callback;
6use crate::cassandra_sys::cass_log_set_level;
7
8// use cassandra_sys::cass_log_set_queue_size; @deprecated
9
10#[cfg(feature = "slog")]
11use slog;
12
13use std::boxed::Box;
14use std::ffi::CStr;
15use std::os::raw;
16use std::ptr;
17
18/// The possible logging levels that can be set.
19#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Copy, Clone, Hash)]
20#[allow(missing_docs)] // Meanings are defined in CQL documentation.
21#[allow(non_camel_case_types)] // Names are traditional.
22pub enum LogLevel {
23    DISABLED,
24    CRITICAL,
25    ERROR,
26    WARN,
27    INFO,
28    DEBUG,
29    TRACE,
30    LAST_ENTRY,
31}
32
33enhance_nullary_enum!(LogLevel, CassLogLevel_, {
34    (DISABLED, CASS_LOG_DISABLED, "DISABLED"),
35    (CRITICAL, CASS_LOG_CRITICAL, "CRITICAL"),
36    (ERROR, CASS_LOG_ERROR, "ERROR"),
37    (WARN, CASS_LOG_WARN, "WARN"),
38    (INFO, CASS_LOG_INFO, "INFO"),
39    (DEBUG, CASS_LOG_DEBUG, "DEBUG"),
40    (TRACE, CASS_LOG_TRACE, "TRACE"),
41    (LAST_ENTRY, CASS_LOG_LAST_ENTRY, "LAST_ENTRY"),
42});
43
44/// Sets the log level.
45///
46/// <b>Note:</b> This needs to be done before any call that might log, such as
47/// any of the cass_cluster_*() or cass_ssl_*() functions.
48/// <b>Default:</b> WARN
49pub fn set_level(level: LogLevel) {
50    unsafe { cass_log_set_level(level.inner()) }
51}
52
53/// unset the logger that receives all Cassandra driver logs.
54pub fn unset_logger() {
55    unsafe { cass_log_set_callback(None, ptr::null_mut()) }
56}
57
58#[cfg(feature = "slog")]
59/// Called by Cassandra for every log if logging is enabled. Passes the log to the configured
60/// slog logger.
61unsafe extern "C" fn slog_callback(log: *const CassLogMessage, data: *mut raw::c_void) {
62    let log = &*log;
63    let logger: &slog::Logger = &*(data as *const _);
64
65    let message: &str = &CStr::from_ptr(log.message.as_ptr()).to_string_lossy();
66    let time_ms: u64 = log.time_ms;
67    let file: &str = &CStr::from_ptr(log.file).to_string_lossy();
68    let line: i32 = log.line;
69    let function: &str = &CStr::from_ptr(log.function).to_string_lossy();
70    let kv = o!(
71        "time_ms" => time_ms,
72        "file" => file,
73        "line" => line,
74        "function" => function
75    );
76
77    // Issue the correct level of log call. Sadly even though the `log!` macro exists,
78    // it's fundamental to slog that the log level is statically known for a given invocation.
79    // We can't do that in this case, so we have to use this tedious workaround.
80    match log.severity {
81        CassLogLevel_::CASS_LOG_DISABLED | CassLogLevel_::CASS_LOG_CRITICAL => {
82            crit!(logger, "{}", message; kv)
83        }
84        CassLogLevel_::CASS_LOG_ERROR => error!(logger, "{}", message; kv),
85        CassLogLevel_::CASS_LOG_WARN => warn!(logger, "{}", message; kv),
86        CassLogLevel_::CASS_LOG_INFO => info!(logger, "{}", message; kv),
87        CassLogLevel_::CASS_LOG_DEBUG => debug!(logger, "{}", message; kv),
88        CassLogLevel_::CASS_LOG_TRACE | CassLogLevel_::CASS_LOG_LAST_ENTRY => {
89            trace!(logger, "{}", message; kv)
90        }
91    };
92}
93
94#[doc(hidden)]
95#[deprecated(note = "Please use set_slog_logger instead")]
96#[cfg(feature = "slog")]
97/// Set or unset a logger to receive all Cassandra driver logs.
98pub fn set_logger(logger: Option<slog::Logger>) {
99    if let Some(logger) = logger {
100        set_slog_logger(logger);
101    } else {
102        unset_logger();
103    }
104}
105
106#[cfg(feature = "slog")]
107/// Set a [slog](https://docs.rs/slog/latest/slog) logger to receive all Cassandra driver logs.
108pub fn set_slog_logger(logger: slog::Logger) {
109    unsafe {
110        // Pass ownership to C. In fact we leak the logger; it never gets freed.
111        // We don't expect this to be called many times, so we're not worried.
112        let data = Box::new(logger);
113        cass_log_set_callback(Some(slog_callback), Box::into_raw(data) as _)
114    }
115}
116
117#[cfg(feature = "log")]
118/// Called by Cassandra for every log if logging is enabled. Passes the log to the configured
119/// log logger.
120unsafe extern "C" fn log_callback(log: *const CassLogMessage, _data: *mut raw::c_void) {
121    use log::{logger, Level, Record};
122
123    let log = &*log;
124    let message: &str = &CStr::from_ptr(log.message.as_ptr()).to_string_lossy();
125    let file = &CStr::from_ptr(log.file).to_string_lossy();
126    let line = log.line as u32;
127    let function = &CStr::from_ptr(log.function).to_string_lossy();
128    let module_and_function_name = function_definition_to_module_name(function).unwrap_or(function);
129
130    let level = match log.severity {
131        CassLogLevel_::CASS_LOG_DISABLED | CassLogLevel_::CASS_LOG_CRITICAL => Level::Error,
132        CassLogLevel_::CASS_LOG_ERROR => Level::Error,
133        CassLogLevel_::CASS_LOG_WARN => Level::Warn,
134        CassLogLevel_::CASS_LOG_INFO => Level::Info,
135        CassLogLevel_::CASS_LOG_DEBUG => Level::Debug,
136        CassLogLevel_::CASS_LOG_TRACE | CassLogLevel_::CASS_LOG_LAST_ENTRY => Level::Trace,
137    };
138
139    logger().log(
140        &Record::builder()
141            .level(level)
142            .args(format_args!("{}", message))
143            .line(Some(line))
144            .file(Some(file))
145            .module_path(Some(module_and_function_name))
146            .target(module_and_function_name)
147            .build(),
148    );
149}
150
151/// Extract the module name from a cpp function definition
152fn function_definition_to_module_name(definition: &str) -> Option<&str> {
153    // definition strings look like:
154    // void datastax::internal::core::ControlConnection::handle_refresh_keyspace(datastax::internal::core::RefreshKeyspaceCallback*))
155    let mut definition_iter = definition.split(' ');
156    // skip the return type
157    definition_iter.next()?;
158    // return the module + function name
159    // we include the function name with the module because we may as well keep the extra information and it doesnt impair log readability like the return type + args do
160    definition_iter.next()?.split('(').next()
161}
162
163#[cfg(feature = "log")]
164/// Set [log](https://docs.rs/log/latest/log) to receive all Cassandra driver logs.
165/// By default [tracing](https://docs.rs/tracing/latest/tracing) will pick up logs emitted by `log`, so also use this if you are a tracing user.
166pub fn set_log_logger() {
167    unsafe { cass_log_set_callback(Some(log_callback), ptr::null_mut()) }
168}