Skip to content

Commit

Permalink
target-side env_logger-like env filter
Browse files Browse the repository at this point in the history
  • Loading branch information
japaric committed Jun 24, 2021
1 parent fde2aab commit 688f3eb
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 51 deletions.
3 changes: 3 additions & 0 deletions macros/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-env-changed=DEFMT_LOG");
}
132 changes: 132 additions & 0 deletions macros/src/env_filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// TODO use `proc_macro_error` crate
use std::panic as abort_call_site;

use std::env;

use defmt_parser::Level;

pub(crate) struct EnvFilter {
entries: Vec<Entry>,
}

impl EnvFilter {
pub(crate) fn from_env_var() -> Self {
let defmt_log = env::var("DEFMT_LOG").ok();
let cargo_pkg_name =
env::var("CARGO_PKG_NAME").unwrap_or_else(|_| abort_call_site!("TODO"));

Self::new(defmt_log.as_deref(), &cargo_pkg_name)
}

fn new(defmt_log: Option<&str>, cargo_pkg_name: &str) -> Self {
// match `env_logger` behavior
const DEFAULT_LEVEL: Level = Level::Trace;

let caller_crate = cargo_pkg_name;

let entries = if let Some(input) = defmt_log {
input
.split(',')
.filter_map(|item| {
let (module_path, level) = if let Some((module_path, level)) =
item.rsplit_once('=')
{
let level = from_str(level).unwrap_or_else(|_| abort_call_site!("TODO"));

(module_path, level)
} else {
(item, DEFAULT_LEVEL)
};

validate_module_path(module_path);

if module_path.starts_with(&caller_crate) {
Some(Entry {
module_path: module_path.to_string(),
level,
})
} else {
None
}
})
.collect()
} else {
vec![]
};

EnvFilter { entries }
}

pub(crate) fn level(&self) -> Level {
// match `env_logger` behavior
const DEFAULT_LEVEL: Level = Level::Error;

// to match `env_logger` behaviour, use the last entry
self.entries
.iter()
.last()
.map(|entry| entry.level)
.unwrap_or(DEFAULT_LEVEL)
}
}

// TODO should be `impl FromStr for Level`
fn from_str(s: &str) -> Result<Level, ()> {
Ok(match s {
"debug" => Level::Debug,
"info" => Level::Info,
"error" => Level::Error,
"trace" => Level::Trace,
"warn" => Level::Warn,
_ => return Err(()),
})
}

fn validate_module_path(path: &str) {
// for now we only accept crate name as module paths
if path.contains("::") {
abort_call_site!("TODO")
}

validate_identifier(path)
}

fn validate_identifier(_ident: &str) {
// TODO re-use `syn` logic (?)
}

struct Entry {
#[allow(dead_code)]
module_path: String,
level: Level,
}

#[cfg(test)]
mod tests {
use super::*;

// TODO unclear if we want the same behavior as `env_logger`
#[test]
fn when_duplicates_entries_in_defmt_log_use_last_entry() {
let env_filter = EnvFilter::new(Some("krate=info,krate=debug"), "krate");
assert_eq!(Level::Debug, env_filter.level());
}

#[test]
fn when_empty_defmt_log_use_error() {
let env_filter = EnvFilter::new(None, "dont_care");
assert_eq!(Level::Error, env_filter.level());
}

#[test]
fn when_no_level_in_defmt_log_use_trace() {
let env_filter = EnvFilter::new(Some("krate"), "krate");
assert_eq!(Level::Trace, env_filter.level());
}

#[test]
fn when_level_in_defmt_log_use_it() {
let env_filter = EnvFilter::new(Some("krate=info"), "krate");
assert_eq!(Level::Info, env_filter.level());
}
}
67 changes: 16 additions & 51 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")]

mod env_filter;
mod symbol;

use std::{
Expand All @@ -25,6 +26,8 @@ use syn::{
WhereClause, WherePredicate,
};

use crate::env_filter::EnvFilter;

/// Checks if any attribute in `attrs_to_check` is in `reject_list` and returns a compiler error if there's a match
///
/// The compiler error will indicate that the attribute conflicts with `attr_name`
Expand Down Expand Up @@ -212,36 +215,6 @@ pub fn timestamp(ts: TokenStream) -> TokenStream {
}
}

/// Returns a list of features of which one has to be enabled for `level` to be active
///
/// * `debug_assertions == true` means that dev profile is enabled
/// * `"defmt-default"` is enabled for dev & release profile so debug_assertions does not matter
fn necessary_features_for_level(level: Level, debug_assertions: bool) -> &'static [&'static str] {
match level {
Level::Trace if debug_assertions => &["defmt-trace", "defmt-default"],
Level::Debug if debug_assertions => &["defmt-debug", "defmt-trace", "defmt-default"],

Level::Trace => &["defmt-trace"],
Level::Debug => &["defmt-debug", "defmt-trace"],
Level::Info => &["defmt-info", "defmt-debug", "defmt-trace", "defmt-default"],
Level::Warn => &[
"defmt-warn",
"defmt-info",
"defmt-debug",
"defmt-trace",
"defmt-default",
],
Level::Error => &[
"defmt-error",
"defmt-warn",
"defmt-info",
"defmt-debug",
"defmt-trace",
"defmt-default",
],
}
}

// `#[derive(Format)]`
#[proc_macro_derive(Format)]
pub fn format(ts: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -480,18 +453,6 @@ fn as_native_type(ty: &Type) -> Option<String> {
}
}

fn cfg_if_logging_enabled(level: Level) -> TokenStream2 {
let features_dev = necessary_features_for_level(level, true);
let features_release = necessary_features_for_level(level, false);

quote!(
any(
all( debug_assertions, any(#( feature = #features_dev ),*)),
all(not(debug_assertions), any(#( feature = #features_release ),*))
)
)
}

fn log_ts(level: Level, ts: TokenStream) -> TokenStream {
log(level, parse_macro_input!(ts as FormatArgs)).into()
}
Expand All @@ -514,9 +475,10 @@ fn log(level: Level, log: FormatArgs) -> TokenStream2 {
};

let sym = mksym(&ls, level.as_str(), true);
let logging_enabled = cfg_if_logging_enabled(level);
quote!({
#[cfg(#logging_enabled)] {
let env_filter = EnvFilter::from_env_var();
let logging_enabled = level >= env_filter.level();
if logging_enabled {
quote!(
match (#(&(#args)),*) {
(#(#pats),*) => {
defmt::export::acquire();
Expand All @@ -526,13 +488,16 @@ fn log(level: Level, log: FormatArgs) -> TokenStream2 {
defmt::export::release()
}
}
}
)
} else {
// if logging is disabled match args, so they are not unused
#[cfg(not(#logging_enabled))]
match (#(&(#args)),*) {
_ => {}
}
})
quote!({
// if logging is disabled match args, so they are not unused
match (#(&(#args)),*) {
_ => {}
}
})
}
}

struct DbgArgs {
Expand Down

0 comments on commit 688f3eb

Please sign in to comment.