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}