#![allow(unsafe_op_in_unsafe_fn)]
use crate::ffi;
use std::ffi::{CStr, CString};
use std::sync::Mutex;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
}
impl LogLevel {
pub fn as_str(&self) -> &'static str {
let level = match self {
LogLevel::Trace => ffi::nwep_log_level_NWEP_LOG_TRACE,
LogLevel::Debug => ffi::nwep_log_level_NWEP_LOG_DEBUG,
LogLevel::Info => ffi::nwep_log_level_NWEP_LOG_INFO,
LogLevel::Warn => ffi::nwep_log_level_NWEP_LOG_WARN,
LogLevel::Error => ffi::nwep_log_level_NWEP_LOG_ERROR,
};
unsafe {
let ptr = ffi::nwep_log_level_str(level);
if ptr.is_null() {
"unknown"
} else {
CStr::from_ptr(ptr).to_str().unwrap_or("unknown")
}
}
}
pub(crate) fn to_ffi(&self) -> ffi::nwep_log_level {
match self {
LogLevel::Trace => ffi::nwep_log_level_NWEP_LOG_TRACE,
LogLevel::Debug => ffi::nwep_log_level_NWEP_LOG_DEBUG,
LogLevel::Info => ffi::nwep_log_level_NWEP_LOG_INFO,
LogLevel::Warn => ffi::nwep_log_level_NWEP_LOG_WARN,
LogLevel::Error => ffi::nwep_log_level_NWEP_LOG_ERROR,
}
}
}
impl From<ffi::nwep_log_level> for LogLevel {
fn from(l: ffi::nwep_log_level) -> Self {
match l {
ffi::nwep_log_level_NWEP_LOG_TRACE => LogLevel::Trace,
ffi::nwep_log_level_NWEP_LOG_DEBUG => LogLevel::Debug,
ffi::nwep_log_level_NWEP_LOG_INFO => LogLevel::Info,
ffi::nwep_log_level_NWEP_LOG_WARN => LogLevel::Warn,
ffi::nwep_log_level_NWEP_LOG_ERROR => LogLevel::Error,
_ => LogLevel::Info,
}
}
}
#[derive(Clone, Debug)]
pub struct LogEntry {
pub level: LogLevel,
pub timestamp_ns: u64,
pub trace_id: [u8; 16],
pub component: String,
pub message: String,
}
impl LogEntry {
pub fn format_json(&self) -> String {
let component = CString::new(self.component.as_str()).unwrap_or_default();
let message = CString::new(self.message.as_str()).unwrap_or_default();
let ffi_entry = ffi::nwep_log_entry {
level: self.level.to_ffi(),
timestamp_ns: self.timestamp_ns,
trace_id: self.trace_id,
component: component.as_ptr(),
message: message.as_ptr(),
};
let mut buf = vec![0u8; 4096];
let n = unsafe {
ffi::nwep_log_format_json(buf.as_mut_ptr().cast(), buf.len(), &ffi_entry)
};
String::from_utf8_lossy(&buf[..n]).into_owned()
}
}
pub fn set_level(level: LogLevel) {
unsafe { ffi::nwep_log_set_level(level.to_ffi()) }
}
pub fn get_level() -> LogLevel {
LogLevel::from(unsafe { ffi::nwep_log_get_level() })
}
pub fn set_json(enabled: bool) {
unsafe { ffi::nwep_log_set_json(enabled as i32) }
}
pub fn set_stderr(enabled: bool) {
unsafe { ffi::nwep_log_set_stderr(enabled as i32) }
}
type LogCallbackFn = Box<dyn Fn(LogEntry) + Send + Sync + 'static>;
static LOG_CALLBACK: Mutex<Option<LogCallbackFn>> = Mutex::new(None);
unsafe extern "C" fn log_trampoline(
entry: *const ffi::nwep_log_entry,
_user_data: *mut std::ffi::c_void,
) {
if entry.is_null() {
return;
}
let e = &*entry;
let component = if e.component.is_null() {
String::new()
} else {
CStr::from_ptr(e.component).to_string_lossy().into_owned()
};
let message = if e.message.is_null() {
String::new()
} else {
CStr::from_ptr(e.message).to_string_lossy().into_owned()
};
let log_entry = LogEntry {
level: LogLevel::from(e.level),
timestamp_ns: e.timestamp_ns,
trace_id: e.trace_id,
component,
message,
};
if let Ok(guard) = LOG_CALLBACK.lock() {
if let Some(cb) = guard.as_ref() {
cb(log_entry);
}
}
}
pub fn set_callback<F: Fn(LogEntry) + Send + Sync + 'static>(cb: F) {
let mut guard = LOG_CALLBACK.lock().unwrap();
*guard = Some(Box::new(cb));
drop(guard);
unsafe { ffi::nwep_log_set_callback(Some(log_trampoline), std::ptr::null_mut()) }
}
pub fn clear_callback() {
let mut guard = LOG_CALLBACK.lock().unwrap();
*guard = None;
drop(guard);
unsafe { ffi::nwep_log_set_callback(None, std::ptr::null_mut()) }
}
pub fn write(level: LogLevel, trace_id: &[u8; 16], component: &str, msg: &str) {
let comp = CString::new(component).unwrap_or_default();
let m = CString::new(msg).unwrap_or_default();
let fmt = CString::new("%s").unwrap();
unsafe {
ffi::nwep_log_write(
level.to_ffi(),
trace_id.as_ptr(),
comp.as_ptr(),
fmt.as_ptr(),
m.as_ptr(),
)
}
}
macro_rules! log_fn {
($name:ident, $ffi_fn:ident, $doc:expr) => {
#[doc = $doc]
pub fn $name(trace_id: &[u8; 16], component: &str, msg: &str) {
let comp = CString::new(component).unwrap_or_default();
let m = CString::new(msg).unwrap_or_default();
let fmt = CString::new("%s").unwrap();
unsafe { ffi::$ffi_fn(trace_id.as_ptr(), comp.as_ptr(), fmt.as_ptr(), m.as_ptr()) }
}
};
}
log_fn!(
trace,
nwep_log_trace,
"`trace` emits a log entry at `LogLevel::Trace` level.
`trace_id` is a 16-byte distributed trace ID (use `&[0u8; 16]` when no trace
context is available). `component` identifies the source of the message and
`msg` is the log text. Internally the C library is called with a `\"%s\"` format
string so no printf-style sequences in `msg` are interpreted.
This function should only be called when the current level is
`LogLevel::Trace`; at higher levels the C library discards the entry before
any work is done.
# Example
```rust
use nwep::log::trace;
let tid = [0u8; 16];
trace(&tid, \"myapp\", \"entered request handler\");
```"
);
log_fn!(
debug,
nwep_log_debug,
"`debug` emits a log entry at `LogLevel::Debug` level.
See `write` for a description of the parameters.
# Example
```rust
use nwep::log::debug;
let tid = [0u8; 16];
debug(&tid, \"myapp\", \"connection established\");
```"
);
log_fn!(
info,
nwep_log_info,
"`info` emits a log entry at `LogLevel::Info` level.
See `write` for a description of the parameters.
# Example
```rust
use nwep::log::info;
let tid = [0u8; 16];
info(&tid, \"myapp\", \"server listening on :6937\");
```"
);
log_fn!(
warn,
nwep_log_warn,
"`warn` emits a log entry at `LogLevel::Warn` level.
See `write` for a description of the parameters.
# Example
```rust
use nwep::log::warn;
let tid = [0u8; 16];
warn(&tid, \"myapp\", \"retrying after transient error\");
```"
);
log_fn!(
error,
nwep_log_error,
"`error` emits a log entry at `LogLevel::Error` level.
See `write` for a description of the parameters.
# Example
```rust
use nwep::log::error;
let tid = [0u8; 16];
error(&tid, \"myapp\", \"fatal: failed to bind socket\");
```"
);