Skip to content

Commit 9168afd

Browse files
committed
Extended logging
1 parent f8f2a49 commit 9168afd

1 file changed

Lines changed: 206 additions & 52 deletions

File tree

src/log.rs

Lines changed: 206 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use crate::sys::*;
1313

1414
extern crate alloc;
1515

16+
const RUST_LOG: Option<&str> = option_env!("RUST_LOG");
17+
1618
/// Exposes the newlib stdout file descriptor to allow writing formatted
1719
/// messages to stdout without a std dependency or allocation
1820
///
@@ -121,41 +123,67 @@ impl From<Level> for Newtype<esp_log_level_t> {
121123
}
122124
}
123125

124-
static LOGGER: EspLogger = EspLogger::new();
126+
/// Trait for a log filter backend that can be used with the `EspIdfLogger`.
127+
pub trait LogFilterBackend {
128+
/// Initialize the log filter backend.
129+
fn initialize(&self) {}
125130

126-
pub struct EspLogger {
127-
// esp-idf function `esp_log_level_get` builds a cache using the address
128-
// of the target and not doing a string compare. This means we need to
129-
// build a cache of our own mapping the str value to a consistant
130-
// Cstr value.
131-
cache: Mutex<BTreeMap<String, CString>>,
131+
/// Check if logging for the given metadata is enabled.
132+
fn enabled(&self, metadata: &Metadata) -> bool;
132133
}
133134

134-
unsafe impl Send for EspLogger {}
135-
unsafe impl Sync for EspLogger {}
135+
impl<T> LogFilterBackend for &T
136+
where
137+
T: LogFilterBackend,
138+
{
139+
fn initialize(&self) {
140+
(**self).initialize()
141+
}
142+
143+
fn enabled(&self, metadata: &Metadata) -> bool {
144+
(**self).enabled(metadata)
145+
}
146+
}
136147

137-
impl EspLogger {
138-
/// Public in case user code would like to compose this logger in their own one
148+
/// Log filter backend based on the ESP-IDF logging configuration.
149+
///
150+
/// This filter is useful when the user would like to control the verbosity
151+
/// of the logging system based on the ESP-IDF configuration settings, which
152+
/// should apply both to the ESP-IDF native C logging, as well as to logging from Rust.
153+
///
154+
/// This backend uses the ESP-IDF logging system to filter log messages based on their target and level.
155+
/// Specifically:
156+
/// - The `log` crate is set to max level equal to the `CONFIG_LOG_MAXIMUM_LEVEL` ESP-IDF configuration.
157+
/// - The `set_target_level` method allows setting the log level for specific log targets
158+
/// (both targets based on Rust logging - i.e. most often than not Rust modules, as well as native ESP-IDF targets).
159+
#[derive(Debug)]
160+
pub struct EspIdfLogFilter {
161+
cache: Mutex<BTreeMap<String, CString>>,
162+
}
163+
164+
impl EspIdfLogFilter {
165+
/// Create a new instance of `EspIdfLogFilter`.
139166
pub const fn new() -> Self {
140167
Self {
141168
cache: Mutex::new(BTreeMap::new()),
142169
}
143170
}
144171

145-
pub fn initialize_default() {
146-
::log::set_logger(&LOGGER)
147-
.map(|()| LOGGER.initialize())
148-
.unwrap();
149-
}
150-
172+
/// Initialize the ESP-IDF log filter backend.
151173
pub fn initialize(&self) {
152174
::log::set_max_level(self.get_max_level());
153175
}
154176

177+
/// Return the maximum log level configured in the ESP-IDF.
155178
pub fn get_max_level(&self) -> LevelFilter {
156179
LevelFilter::from(Newtype(CONFIG_LOG_MAXIMUM_LEVEL))
157180
}
158181

182+
/// Set the log level for a specific target.
183+
///
184+
/// Arguments:
185+
/// - `target`: The target for which to set the log level. This can be a Rust log target, or an ESP-IDF native target.
186+
/// - `level_filter`: The log level to set for the target.
159187
pub fn set_target_level(
160188
&self,
161189
target: impl AsRef<str>,
@@ -185,6 +213,114 @@ impl EspLogger {
185213
Ok(())
186214
}
187215

216+
/// Check if logging for the given metadata is enabled,
217+
/// based on the ESP-IDF current log level, including taeget-specific log levels.
218+
pub fn enabled(&self, metadata: &Metadata) -> bool {
219+
let level = Newtype::<esp_log_level_t>::from(metadata.level()).0;
220+
221+
let mut cache = self.cache.lock();
222+
223+
let ctarget = loop {
224+
if let Some(ctarget) = cache.get(metadata.target()) {
225+
break ctarget;
226+
}
227+
228+
if let Ok(ctarget) = to_cstring_arg(metadata.target()) {
229+
cache.insert(metadata.target().into(), ctarget);
230+
} else {
231+
return true;
232+
}
233+
};
234+
235+
let max_level = unsafe { esp_log_level_get(ctarget.as_c_str().as_ptr()) };
236+
level <= max_level
237+
}
238+
}
239+
240+
impl Default for EspIdfLogFilter {
241+
fn default() -> Self {
242+
Self::new()
243+
}
244+
}
245+
246+
impl LogFilterBackend for EspIdfLogFilter {
247+
fn initialize(&self) {
248+
self.initialize();
249+
}
250+
251+
fn enabled(&self, metadata: &Metadata) -> bool {
252+
self.enabled(metadata)
253+
}
254+
}
255+
256+
/// Log filter backend that does not consider the ESP-IDF configuration settings
257+
/// that control the log verbosity.
258+
///
259+
/// This way, the control of the log verbosity from within Rust is completely disconnected
260+
/// from the log verbosity for the ESP-IDF native C code.
261+
#[derive(Debug)]
262+
pub struct RustLogFilter(());
263+
264+
impl RustLogFilter {
265+
pub const fn new() -> Self {
266+
Self(())
267+
}
268+
}
269+
270+
impl Default for RustLogFilter {
271+
fn default() -> Self {
272+
Self::new()
273+
}
274+
}
275+
276+
impl LogFilterBackend for RustLogFilter {
277+
fn enabled(&self, _metadata: &Metadata) -> bool {
278+
// For the Rust backend, we always return true as it does not have a
279+
// level filter like the esp-idf backend.
280+
true
281+
}
282+
}
283+
284+
static ESP_IDF_LOGGER: EspIdfLogger<EspIdfLogFilter> = EspIdfLogger::new(EspIdfLogFilter::new());
285+
static RUST_LOGGER: EspIdfLogger<RustLogFilter> = EspIdfLogger::new(RustLogFilter::new());
286+
287+
/// A type alias for the ESP-IDf logger configured with the ESP-IDF log filter.
288+
///
289+
/// For backwards compatibility.
290+
pub type EspLogger = EspIdfLogger<EspIdfLogFilter>;
291+
292+
/// A logger that integrates with the ESP-IDF logging system.
293+
///
294+
/// Specifically:
295+
/// - It logs to `stdout`/`stderr` just like the ESP-IDF native C logging functions
296+
/// - The format of the logs matches the ESP-IDF native C logging format
297+
/// - If the `EspIdfLogFilter` backend is used, it respects the ESP-IDF log level configuration
298+
#[derive(Debug)]
299+
pub struct EspIdfLogger<T> {
300+
filter: T,
301+
}
302+
303+
impl<T> EspIdfLogger<T> {
304+
/// Create a new instance of `EspIdfLogger` with the specified log filter backend.
305+
///
306+
/// # Arguments
307+
/// - `filter`: The log filter backend to use for filtering log messages.
308+
pub const fn new(filter: T) -> Self {
309+
Self { filter }
310+
}
311+
312+
/// Return a reference to the log filter backend used by this logger.
313+
pub fn filter(&self) -> &T {
314+
&self.filter
315+
}
316+
317+
/// For backwards compatibility
318+
///
319+
/// Equivalent to calling `init_from_esp_idf()`
320+
pub fn initialize_default() {
321+
init_from_esp_idf();
322+
}
323+
188324
fn get_marker(level: Level) -> &'static str {
189325
match level {
190326
Level::Error => "E",
@@ -211,44 +347,20 @@ impl EspLogger {
211347
None
212348
}
213349
}
214-
215-
fn should_log(&self, record: &Record) -> bool {
216-
let level = Newtype::<esp_log_level_t>::from(record.level()).0;
217-
218-
let mut cache = self.cache.lock();
219-
220-
let ctarget = loop {
221-
if let Some(ctarget) = cache.get(record.target()) {
222-
break ctarget;
223-
}
224-
225-
if let Ok(ctarget) = to_cstring_arg(record.target()) {
226-
cache.insert(record.target().into(), ctarget);
227-
} else {
228-
return true;
229-
}
230-
};
231-
232-
let max_level = unsafe { esp_log_level_get(ctarget.as_c_str().as_ptr()) };
233-
level <= max_level
234-
}
235350
}
236351

237-
impl Default for EspLogger {
238-
fn default() -> Self {
239-
Self::new()
240-
}
241-
}
242-
243-
impl ::log::Log for EspLogger {
352+
impl<T> ::log::Log for EspIdfLogger<T>
353+
where
354+
T: LogFilterBackend + Send + Sync,
355+
{
244356
fn enabled(&self, metadata: &Metadata) -> bool {
245-
metadata.level() <= LevelFilter::from(Newtype(CONFIG_LOG_MAXIMUM_LEVEL))
357+
self.filter.enabled(metadata)
246358
}
247359

248360
fn log(&self, record: &Record) {
249361
let metadata = record.metadata();
250362

251-
if self.enabled(metadata) && self.should_log(record) {
363+
if self.enabled(metadata) {
252364
let marker = Self::get_marker(metadata.level());
253365
let target = record.metadata().target();
254366
let args = record.args();
@@ -283,9 +395,51 @@ impl ::log::Log for EspLogger {
283395
fn flush(&self) {}
284396
}
285397

286-
pub fn set_target_level(
287-
target: impl AsRef<str>,
288-
level_filter: LevelFilter,
289-
) -> Result<(), EspError> {
290-
LOGGER.set_target_level(target, level_filter)
398+
/// Initialize the Rust logging system with the ESP-IDF logger and with the Rust log filter backend
399+
/// (i.e. logging verbosity is controlled by the Rust log crate settings and disconnected from the ESP-IDF configuration settings).
400+
///
401+
/// Arguments:
402+
/// - `filter`: The log level filter to set for the Rust logger.
403+
pub fn init(filter: LevelFilter) -> &'static EspIdfLogger<RustLogFilter> {
404+
init_with_logger(&RUST_LOGGER);
405+
406+
::log::set_max_level(filter);
407+
408+
&RUST_LOGGER
409+
}
410+
411+
/// Initialize the Rust logging system with the ESP-IDF logger and with the Rust log filter backend
412+
/// (i.e. logging verbosity is controlled by the `RUST_LOG` environment variable).
413+
///
414+
/// This function reads the `RUST_LOG` environment variable to determine the log level.
415+
pub fn init_from_env() -> &'static EspIdfLogger<RustLogFilter> {
416+
let level = match RUST_LOG.unwrap_or("info").to_ascii_lowercase().as_str() {
417+
"off" | "none" => LevelFilter::Off,
418+
"error" => LevelFilter::Error,
419+
"warn" | "warning" => LevelFilter::Warn,
420+
"info" => LevelFilter::Info,
421+
"debug" => LevelFilter::Debug,
422+
"trace" => LevelFilter::Trace,
423+
_ => LevelFilter::Info, // Default to Info if the level is not recognized
424+
};
425+
426+
init(level)
427+
}
428+
429+
/// Initialize the Rust logging system with the ESP-IDF logger and with the ESP-IDF log filter backend
430+
/// (i.e. logging verbosity is controlled by the ESP-IDF configuration settings).
431+
pub fn init_from_esp_idf() -> &'static EspIdfLogger<EspIdfLogFilter> {
432+
init_with_logger(&ESP_IDF_LOGGER);
433+
434+
&ESP_IDF_LOGGER
435+
}
436+
437+
/// Initialize the Rust logging system with the provided ESP-IDF logger.
438+
pub fn init_with_logger<T>(logger: &'static EspIdfLogger<T>)
439+
where
440+
T: LogFilterBackend + Send + Sync,
441+
{
442+
::log::set_logger(logger)
443+
.map(|()| logger.filter().initialize())
444+
.unwrap();
291445
}

0 commit comments

Comments
 (0)