tarantool_module/
log.rs

1//! Logging utils. See ["log" crate documentation](https://docs.rs/log/) for details
2//!
3//! Example:
4//! ```rust
5//! use log::{info, LevelFilter};
6//! use tarantool_module::log::{TarantoolLogger, say, SayLevel};
7//!
8//! log::set_logger(&TarantoolLogger {}).unwrap();
9//! log::set_max_level(LevelFilter::Debug);
10//! info!("Hello {}", username);
11//!
12//! // Or you can write to Tarantool logger directly
13//! say(SayLevel::Info, "log_demo.rs", 9, None, "Hello world");
14//! ```
15//!
16//! See also:
17//! - [Lua reference: Module log](https://www.tarantool.io/en/doc/latest/reference/reference_lua/log/)
18//! - [C API reference: Module say (logging)](https://www.tarantool.io/en/doc/latest/dev_guide/reference_capi/say/)
19use std::ffi::CString;
20use std::mem::forget;
21
22use failure::_core::ptr::null;
23use log::{Level, Log, Metadata, Record};
24use num_traits::{FromPrimitive, ToPrimitive};
25
26/// [Log](https://docs.rs/log/latest/log/trait.Log.html) trait implementation. Wraps [say()](fn.say.html).
27pub struct TarantoolLogger {}
28
29impl Log for TarantoolLogger {
30    fn enabled(&self, metadata: &Metadata) -> bool {
31        let level: SayLevel = metadata.level().into();
32        level <= SayLevel::from_i32(unsafe { ffi::LOG_LEVEL }).unwrap()
33    }
34
35    fn log(&self, record: &Record) {
36        say(
37            record.level().into(),
38            record.file().unwrap_or_default(),
39            record.line().unwrap_or(0) as i32,
40            None,
41            record.args().to_string().as_str(),
42        )
43    }
44
45    fn flush(&self) {}
46}
47
48/// Tarantool-native logging levels (use it with [say()](fn.say.html))
49#[repr(u32)]
50#[derive(Debug, Clone, PartialEq, PartialOrd, ToPrimitive, FromPrimitive)]
51pub enum SayLevel {
52    Fatal = 0,
53    System = 1,
54    Error = 2,
55    Crit = 3,
56    Warn = 4,
57    Info = 5,
58    Debug = 6,
59}
60
61impl From<Level> for SayLevel {
62    fn from(level: Level) -> Self {
63        match level {
64            Level::Error => SayLevel::Error,
65            Level::Warn => SayLevel::Warn,
66            Level::Info => SayLevel::Info,
67            Level::Debug => SayLevel::Debug,
68            Level::Trace => SayLevel::Debug,
69        }
70    }
71}
72
73/// Format and print a message to Tarantool log file.
74#[inline]
75pub fn say(level: SayLevel, file: &str, line: i32, error: Option<&str>, message: &str) {
76    let level = level.to_i32().unwrap();
77    let file = CString::new(file).unwrap();
78    let error = error.map(|e| CString::new(e).unwrap());
79    let error_ptr = match error {
80        Some(ref error) => error.as_ptr(),
81        None => null(),
82    };
83    let message = CString::new(message).unwrap();
84
85    unsafe { ffi::SAY_FN.unwrap()(level, file.as_ptr(), line, error_ptr, message.as_ptr()) };
86
87    forget(file);
88    forget(message);
89    if error.is_some() {
90        forget(error.unwrap());
91    }
92}
93
94mod ffi {
95    use std::os::raw::{c_char, c_int};
96
97    pub type SayFunc = Option<
98        unsafe extern "C" fn(c_int, *const c_char, c_int, *const c_char, *const c_char, ...),
99    >;
100
101    extern "C" {
102        #[link_name = "log_level"]
103        pub static mut LOG_LEVEL: c_int;
104
105        #[link_name = "_say"]
106        pub static mut SAY_FN: SayFunc;
107    }
108}