1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
use dlog_sys::*;
use log::{Level, LevelFilter, Log, Metadata, Record};
use std::ffi::CStr;
use std::io::{Error, Result};
/// Enumeration for log priority values in ascending priority order.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum Priority {
Unknown = 0,
Default,
Verbose,
Debug,
Info,
Warn,
Error,
Fatal,
Silent,
PrioMax,
}
/// Log id.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum BufferId {
LogIdMain = 0,
LogIdRadio,
LogIdSystem,
LogIdApps,
LogIdKmsg,
LogIdSyslog,
}
impl From<log_priority> for Priority {
fn from(c_enum: log_priority) -> Self {
match c_enum {
log_priority::DLOG_UNKNOWN => Priority::Unknown,
log_priority::DLOG_DEFAULT => Priority::Default,
log_priority::DLOG_VERBOSE => Priority::Verbose,
log_priority::DLOG_DEBUG => Priority::Debug,
log_priority::DLOG_INFO => Priority::Info,
log_priority::DLOG_WARN => Priority::Warn,
log_priority::DLOG_ERROR => Priority::Error,
log_priority::DLOG_FATAL => Priority::Fatal,
log_priority::DLOG_SILENT => Priority::Silent,
log_priority::DLOG_PRIO_MAX => Priority::PrioMax,
}
}
}
impl From<log_id_t> for BufferId {
fn from(c_enum: log_id_t) -> Self {
match c_enum {
log_id_t::LOG_ID_MAIN => BufferId::LogIdMain,
log_id_t::LOG_ID_RADIO => BufferId::LogIdRadio,
log_id_t::LOG_ID_SYSTEM => BufferId::LogIdSystem,
log_id_t::LOG_ID_APPS => BufferId::LogIdApps,
log_id_t::LOG_ID_KMSG => BufferId::LogIdKmsg,
log_id_t::LOG_ID_SYSLOG => BufferId::LogIdSyslog,
_ => panic!("Invalid log_id_t"),
}
}
}
impl From<BufferId> for log_id_t {
fn from(rust_enum: BufferId) -> Self {
match rust_enum {
BufferId::LogIdMain => log_id_t::LOG_ID_MAIN,
BufferId::LogIdRadio => log_id_t::LOG_ID_RADIO,
BufferId::LogIdSystem => log_id_t::LOG_ID_SYSTEM,
BufferId::LogIdApps => log_id_t::LOG_ID_APPS,
BufferId::LogIdKmsg => log_id_t::LOG_ID_KMSG,
BufferId::LogIdSyslog => log_id_t::LOG_ID_SYSLOG,
}
}
}
struct DlogLogger {
is_critical: bool,
}
impl Log for DlogLogger {
fn enabled(&self, _meta: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
let target = record.target();
let file = record
.file()
.map(|s| s.to_owned())
.unwrap_or_else(|| "?".to_owned());
let line = record
.line()
.map(|l| l.to_string())
.unwrap_or_else(|| "?".to_owned());
let args = record.args();
let prio = match record.level() {
Level::Error => log_priority::DLOG_ERROR,
Level::Warn => log_priority::DLOG_WARN,
Level::Info => log_priority::DLOG_INFO,
Level::Debug => log_priority::DLOG_DEBUG,
Level::Trace => log_priority::DLOG_VERBOSE,
};
let tag = target.to_string().replace('\0', "\\0") + "\0";
let contents = format!("{}({}) > {}", file, line, args).replace('\0', "\\0") + "\0";
// SAFETY: the three _unchecked calls are ok, since we make sure that the
// three strings only have a single \0 and it is at the end. Then the dlog
// call is called with verified parameters, which is completely safe.
unsafe {
let tag_c = CStr::from_bytes_with_nul_unchecked(tag.as_bytes());
let fmt_c = CStr::from_bytes_with_nul_unchecked(b"%s\0");
let contents_c = CStr::from_bytes_with_nul_unchecked(contents.as_bytes());
// TODO: What about structured values?
if self.is_critical {
__dlog_critical_print(
BufferId::LogIdMain.into(),
prio as i32,
tag_c.as_ptr(),
fmt_c.as_ptr(),
contents_c.as_ptr(),
);
} else {
dlog_print(prio, tag_c.as_ptr(), fmt_c.as_ptr(), contents_c.as_ptr());
}
}
}
fn flush(&self) {}
}
/// Initializes the DLog logging system.
///
/// This function sets up the logging infrastructure by configuring the logger
/// for the current application. **It should be called once**.
///
/// Under the hood, there is a struct with [`Log`] trait implemented that uses
/// `dlog_print` for sending logs to DLog. If invoked with `is_critical = true`,
/// it sends critical logs instead.
///
/// Priority of a log is defined by a macro used and tag is constructed from
/// the binary name. However, it can be overwritten - see example below.
///
/// [`Log`]: log::Log
///
/// # Errors
///
/// * [`std::io::ErrorKind::Other`] - Wrapped error from [`set_boxed_logger`].
///
/// [`set_boxed_logger`]: log::set_boxed_logger
///
/// # Examples
///
/// Sending non-critical logs:
///
/// ```
/// use log::info;
///
/// fn main() -> Result<(), std::io::Error> {
/// dlog::init(false)?;
/// info!("my first log");
/// }
/// ```
///
/// Sending critical logs:
///
/// ```
/// use log::info;
///
/// fn main() -> Result<(), std::io::Error> {
/// dlog::init(true)?;
/// info!("my first critical log");
/// }
/// ```
///
/// You can set your custom tag by setting `target` argument:
///
/// ```
/// use log::info;
///
/// fn main() -> Result<(), std::io::Error> {
/// dlog::init(false)?;
/// info!(target: "my_tag", "my first log with a custom tag");
/// }
/// ```
pub fn init(is_critical: bool) -> Result<()> {
log::set_boxed_logger(Box::new(DlogLogger { is_critical })).map_err(Error::other)?;
log::set_max_level(LevelFilter::Trace);
Ok(())
}
/// Blocks low priority logs.
///
/// Allows filtering low priority logs globally, think a runtime "--verbose" mode,
/// except you can choose which priorities to filter.
/// Using this (as opposed to filtering later, when retrieving logs) is that it
/// helps conserve system logging resources (buffer space etc.).
///
/// # Arguments
///
/// * `prio` - Minimum priority level.
///
/// # Examples
///
/// ```
/// use dlog::Priority;
/// use log::{error, info, warn};
///
/// fn main() -> Result<(), std::io::Error> {
/// dlog::init(false)?;
/// info!("no filter so this log goes through");
/// dlog::set_minimum_priority(Priority::Warn)?;
/// info!("WARN is a higher level than INFO so this log gets filtered");
/// warn!("but this one still goes through");
/// error!("as does this one");
/// Ok(())
/// }
/// ```
pub fn set_minimum_priority(prio: Priority) -> Result<()> {
// SAFETY: the underlying C function is safe by itself, even if the parameter
// is out of range. The parameter will be in range in our case anyway, since
// our enum matches the C one.
match unsafe { dlog_set_minimum_priority(prio as i32) } {
0 => Ok(()),
e => Err(Error::from_raw_os_error(-e)),
}
}