dlog/
lib.rs

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