@@ -13,6 +13,8 @@ use crate::sys::*;
1313
1414extern 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