diff --git a/esp-println/CHANGELOG.md b/esp-println/CHANGELOG.md index 1c9a28451..02a635292 100644 --- a/esp-println/CHANGELOG.md +++ b/esp-println/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Replace environment variables `ESP_LOGLEVEL` and `ESP_LOGFILTER` with just one environment variable: `ESP_LOG` (#2291) ### Fixed diff --git a/esp-println/Cargo.toml b/esp-println/Cargo.toml index f476320ef..677a41233 100644 --- a/esp-println/Cargo.toml +++ b/esp-println/Cargo.toml @@ -21,6 +21,7 @@ portable-atomic = { version = "1.7.0", optional = true, default-features = fal [build-dependencies] esp-build = { version = "0.1.0", path = "../esp-build" } +log = "0.4.20" [features] default = ["critical-section", "colors", "auto"] diff --git a/esp-println/README.md b/esp-println/README.md index 88f94527c..95f41461e 100644 --- a/esp-println/README.md +++ b/esp-println/README.md @@ -76,8 +76,7 @@ init_logger_from_env(); In this case the following environment variables are used: -- `ESP_LOGLEVEL` sets the log level, use values like `trace`, `info` etc. -- `ESP_LOGTARGETS` if set you should provide the crate names of crates (optionally with a path e.g. `esp_wifi::compat::common`) which should get logged, separated by `,` and no additional whitespace between +- `ESP_LOG` log messages you want to show, similar to `RUST_LOG`. RegEx is not supported. e.g. `warn,test::foo=info,test::foo::bar=debug` If this simple logger implementation isn't sufficient for your needs, you can implement your own logger on top of `esp-println`. See [Implementing a Logger section log documentaion] diff --git a/esp-println/build.rs b/esp-println/build.rs index 1ac2f2368..54d07c777 100644 --- a/esp-println/build.rs +++ b/esp-println/build.rs @@ -1,3 +1,5 @@ +use std::{env, path::Path}; + use esp_build::assert_unique_used_features; fn main() { @@ -29,4 +31,157 @@ fn main() { "cargo:warning=The `colors` feature is only effective when using the `log` feature" ); } + + if std::env::var("ESP_LOGLEVEL").is_ok() || std::env::var("ESP_LOGFILTER").is_ok() { + panic!("`ESP_LOGLEVEL` and `ESP_LOGFILTER` is not supported anymore. Please use `ESP_LOG` instead."); + } + + generate_filter_snippet(); + + #[cfg(target_os = "windows")] + println!("cargo:rustc-cfg=host_is_windows"); + + println!("cargo:rerun-if-env-changed=ESP_LOG"); + println!("cargo:rustc-check-cfg=cfg(host_is_windows)"); +} + +fn generate_filter_snippet() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("log_filter.rs"); + + let filter = env::var("ESP_LOG"); + let snippet = if let Ok(filter) = filter { + let res = parse_spec(&filter); + + if !res.errors.is_empty() { + panic!("Error parsing `ESP_LOG`: {:?}", res.errors); + } else { + let max = res + .directives + .iter() + .map(|v| v.level) + .max() + .unwrap_or(log::LevelFilter::Off); + let max = match max { + log::LevelFilter::Off => "Off", + log::LevelFilter::Error => "Error", + log::LevelFilter::Warn => "Warn", + log::LevelFilter::Info => "Info", + log::LevelFilter::Debug => "Debug", + log::LevelFilter::Trace => "Trace", + }; + + let mut snippet = String::new(); + + snippet.push_str(&format!( + "pub(crate) const FILTER_MAX: log::LevelFilter = log::LevelFilter::{};", + max + )); + + snippet + .push_str("pub(crate) fn is_enabled(level: log::Level, _target: &str) -> bool {"); + + for directive in res.directives { + let level = match directive.level { + log::LevelFilter::Off => "Off", + log::LevelFilter::Error => "Error", + log::LevelFilter::Warn => "Warn", + log::LevelFilter::Info => "Info", + log::LevelFilter::Debug => "Debug", + log::LevelFilter::Trace => "Trace", + }; + + if let Some(name) = directive.name { + snippet.push_str(&format!( + "if _target.starts_with(\"{}\") && level <= log::LevelFilter::{} {{ return true; }}", + &name, level + )); + } else { + snippet.push_str(&format!( + "if level <= log::LevelFilter::{} {{ return true; }}", + level + )); + } + } + snippet.push_str(" false"); + snippet.push('}'); + snippet + } + } else { + "pub(crate) const FILTER_MAX: log::LevelFilter = log::LevelFilter::Off; pub(crate) fn is_enabled(_level: log::Level, _target: &str) -> bool { true }".to_string() + }; + + std::fs::write(&dest_path, &snippet).unwrap(); +} + +#[derive(Default, Debug)] +struct ParseResult { + pub(crate) directives: Vec, + pub(crate) errors: Vec, +} + +impl ParseResult { + fn add_directive(&mut self, directive: Directive) { + self.directives.push(directive); + } + + fn add_error(&mut self, message: String) { + self.errors.push(message); + } +} + +#[derive(Debug)] +struct Directive { + pub(crate) name: Option, + pub(crate) level: log::LevelFilter, +} + +/// Parse a logging specification string (e.g: +/// `crate1,crate2::mod3,crate3::x=error/foo`) and return a vector with log +/// directives. +fn parse_spec(spec: &str) -> ParseResult { + let mut result = ParseResult::default(); + + let mut parts = spec.split('/'); + let mods = parts.next(); + + if let Some(m) = mods { + for s in m.split(',').map(|ss| ss.trim()) { + if s.is_empty() { + continue; + } + let mut parts = s.split('='); + let (log_level, name) = + match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) { + (Some(part0), None, None) => { + // if the single argument is a log-level string or number, + // treat that as a global fallback + match part0.parse() { + Ok(num) => (num, None), + Err(_) => (log::LevelFilter::max(), Some(part0)), + } + } + (Some(part0), Some(""), None) => (log::LevelFilter::max(), Some(part0)), + (Some(part0), Some(part1), None) => { + if let Ok(num) = part1.parse() { + (num, Some(part0)) + } else { + result.add_error(format!("invalid logging spec '{part1}'")); + continue; + } + } + _ => { + result.add_error(format!("invalid logging spec '{s}'")); + continue; + } + }; + + result.add_directive(Directive { + name: name.map(|s| s.to_owned()), + level: log_level, + }); + } + } + + result } diff --git a/esp-println/src/logger.rs b/esp-println/src/logger.rs index 24f843d27..b2b1b5fb1 100644 --- a/esp-println/src/logger.rs +++ b/esp-println/src/logger.rs @@ -1,11 +1,14 @@ -use core::str::FromStr; - -use log::LevelFilter; - use super::println; -const LOG_TARGETS: Option<&'static str> = option_env!("ESP_LOGTARGETS"); +#[cfg(not(host_is_windows))] +include!(concat!(env!("OUT_DIR"), "/log_filter.rs")); +#[cfg(host_is_windows)] +include!(concat!(env!("OUT_DIR"), "\\log_filter.rs")); + +/// Initialize the logger with the given maximum log level. +/// +/// `ESP_LOG` environment variable will still be honored if set. pub fn init_logger(level: log::LevelFilter) { unsafe { log::set_logger_racy(&EspLogger).unwrap(); @@ -13,37 +16,27 @@ pub fn init_logger(level: log::LevelFilter) { } } +/// Initialize the logger from the `ESP_LOG` environment variable. pub fn init_logger_from_env() { unsafe { log::set_logger_racy(&EspLogger).unwrap(); - } - - const LEVEL: Option<&'static str> = option_env!("ESP_LOGLEVEL"); - - if let Some(lvl) = LEVEL { - let level = LevelFilter::from_str(lvl).unwrap_or_else(|_| LevelFilter::Off); - unsafe { log::set_max_level_racy(level) }; + log::set_max_level_racy(FILTER_MAX); } } struct EspLogger; impl log::Log for EspLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - true + fn enabled(&self, metadata: &log::Metadata) -> bool { + let level = metadata.level(); + let target = metadata.target(); + is_enabled(level, target) } #[allow(unused)] fn log(&self, record: &log::Record) { - // check enabled log targets if any - if let Some(targets) = LOG_TARGETS { - if targets - .split(",") - .find(|v| record.target().starts_with(v)) - .is_none() - { - return; - } + if !self.enabled(&record.metadata()) { + return; } const RESET: &str = "\u{001B}[0m"; diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml index 6c23d6171..7fcbcbfad 100644 --- a/examples/.cargo/config.toml +++ b/examples/.cargo/config.toml @@ -27,7 +27,7 @@ rustflags = [ ] [env] -ESP_LOGLEVEL = "info" +ESP_LOG = "info" SSID = "SSID" PASSWORD = "PASSWORD" STATIC_IP = "1.1.1.1 "