The Worker's logger: the log facade (which whiskers-service and whiskers-core already use)
written to the runtime's console, at the console method that carries its level, so wrangler dev,
Cloudflare's log stream and celld dev --logs all show the level the code chose.
The level is the WHISKERS_LOG setting, spelled as everywhere else in Whiskers (error, warn,
info (default), debug, trace, off; parse_level below is a copy of the core's, which is
compiled out on wasm, and a native test holds the two together). Nothing here knows what a
record contains: the convention that no record carries content is the callers', and
line is the one place a record becomes text, so it is the one place to look.
11use log::{Level, LevelFilter, Log, Metadata, Record};
What a record looks like in the log: its level, where it came from and its message.
Reads a level name as WHISKERS_LOG spells it. Unknown names are None.
19fn parse_level(s: &str) -> Option<LevelFilter> { 20 match s.trim().to_ascii_lowercase().as_str() { 21 "off" | "none" => Some(LevelFilter::Off), 22 "error" => Some(LevelFilter::Error), 23 "warn" | "warning" => Some(LevelFilter::Warn), 24 "info" => Some(LevelFilter::Info), 25 "debug" => Some(LevelFilter::Debug), 26 "trace" => Some(LevelFilter::Trace), 27 _ => None, 28 } 29}
The level named by the setting, or info for none and for a name that is not a level (an
operator's typo must not silence the log).
37struct Console; 38 39impl Log for Console { 40 fn enabled(&self, metadata: &Metadata<'_>) -> bool { 41 metadata.level() <= log::max_level() 42 } 43 44 fn log(&self, record: &Record<'_>) { 45 if !self.enabled(record.metadata()) { 46 return; 47 } 48 emit(record.level(), &line(record.level(), record.target(), record.args())); 49 } 50 51 fn flush(&self) {} 52} 53 54#[cfg(target_arch = "wasm32")] 55fn emit(level: Level, text: &str) { 56 match level { 57 Level::Error => worker::console_error!("{text}"), 58 Level::Warn => worker::console_warn!("{text}"), 59 Level::Info => worker::console_log!("{text}"), 60 Level::Debug | Level::Trace => worker::console_debug!("{text}"), 61 } 62}
A native build (the tests) has no console.
68static CONSOLE: Console = Console;
Installs the logger once per isolate; later calls change nothing.
79#[cfg(test)] 80mod tests { 81 use super::*; 82 83 #[test] 84 fn the_setting_picks_the_level_and_a_typo_does_not_silence_the_log() { 85 assert_eq!(level_of(None), LevelFilter::Info); 86 assert_eq!(level_of(Some("debug")), LevelFilter::Debug); 87 assert_eq!(level_of(Some("OFF")), LevelFilter::Off); 88 assert_eq!(level_of(Some("verbose")), LevelFilter::Info); 89 } 90 91 #[test] 92 fn the_spelling_is_the_cores() { 93 for name in ["off", "none", "error", "warn", "warning", "info", "debug", "trace", " Debug ", "loud", ""] { 94 assert_eq!(parse_level(name), whiskers_core::logging::parse_level(name), "{name:?}"); 95 } 96 } 97 98 #[test] 99 fn a_line_is_a_level_a_place_and_a_message() { 100 assert_eq!(line(Level::Warn, "whiskers_service", &format_args!("{} facts", 3)), "WARN whiskers_service: 3 facts"); 101 } 102}