Browse Source

Allow a custom Logger to be used as the backend for boa_runtime::Console (#3943)

* Allow a custom Logger to be used as the backend for boa_runtime::Console

* Comments
pull/3798/merge
Hans Larsen 3 months ago committed by GitHub
parent
commit
c6c6e4c1ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 253
      core/runtime/src/console/mod.rs
  2. 2
      core/runtime/src/lib.rs

253
core/runtime/src/console/mod.rs

@ -25,26 +25,64 @@ use boa_gc::{Finalize, Trace};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc, time::SystemTime}; use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc, time::SystemTime};
/// This represents the different types of log messages. /// A trait that can be used to forward console logs to an implementation.
#[derive(Debug)] pub trait Logger: Trace + Sized {
enum LogMessage { /// Log a log message (`console.log`).
Log(String), ///
Info(String), /// # Errors
Warn(String), /// Returning an error will throw an exception in JavaScript.
Error(String), fn log(&self, msg: String, state: &Console) -> JsResult<()>;
}
/// Log an info message (`console.info`).
///
/// # Errors
/// Returning an error will throw an exception in JavaScript.
fn info(&self, msg: String, state: &Console) -> JsResult<()>;
/// Helper function for logging messages. /// Log a warning message (`console.warn`).
fn logger(msg: LogMessage, console_state: &Console) { ///
let indent = 2 * console_state.groups.len(); /// # Errors
/// Returning an error will throw an exception in JavaScript.
fn warn(&self, msg: String, state: &Console) -> JsResult<()>;
match msg { /// Log an error message (`console.error`).
LogMessage::Error(msg) => { ///
eprintln!("{msg:>indent$}"); /// # Errors
/// Returning an error will throw an exception in JavaScript.
fn error(&self, msg: String, state: &Console) -> JsResult<()>;
} }
LogMessage::Log(msg) | LogMessage::Info(msg) | LogMessage::Warn(msg) => {
/// The default implementation for logging from the console.
///
/// Implements the [`Logger`] trait and output errors to stderr and all
/// the others to stdout. Will add indentation based on the number of
/// groups.
#[derive(Trace, Finalize)]
struct DefaultLogger;
impl Logger for DefaultLogger {
#[inline]
fn log(&self, msg: String, state: &Console) -> JsResult<()> {
let indent = 2 * state.groups.len();
println!("{msg:>indent$}"); println!("{msg:>indent$}");
Ok(())
} }
#[inline]
fn info(&self, msg: String, state: &Console) -> JsResult<()> {
self.log(msg, state)
}
#[inline]
fn warn(&self, msg: String, state: &Console) -> JsResult<()> {
self.log(msg, state)
}
#[inline]
fn error(&self, msg: String, state: &Console) -> JsResult<()> {
let indent = 2 * state.groups.len();
eprintln!("{msg:>indent$}");
Ok(())
} }
} }
@ -123,134 +161,156 @@ fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
/// This is the internal console object state. /// This is the internal console object state.
#[derive(Debug, Default, Trace, Finalize, JsData)] #[derive(Debug, Default, Trace, Finalize, JsData)]
pub struct Console { pub struct Console {
count_map: FxHashMap<JsString, u32>, /// The map of console counters, used in `console.count()`.
timer_map: FxHashMap<JsString, u128>, pub count_map: FxHashMap<JsString, u32>,
groups: Vec<String>,
/// The map of console timers, used in `console.time`, `console.timeLog`
/// and `console.timeEnd`.
pub timer_map: FxHashMap<JsString, u128>,
/// The current list of groups. Groups should be indented, but some logging
/// libraries may want to use them in a different way.
pub groups: Vec<String>,
} }
impl Console { impl Console {
/// Name of the built-in `console` property. /// Name of the built-in `console` property.
pub const NAME: JsStr<'static> = js_str!("console"); pub const NAME: JsStr<'static> = js_str!("console");
/// Initializes the `console` built-in object. /// Initializes the `console` with a special logger.
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn init(context: &mut Context) -> JsObject { pub fn init_with_logger<L>(context: &mut Context, logger: L) -> JsObject
fn console_method( where
f: fn(&JsValue, &[JsValue], &Console, &mut Context) -> JsResult<JsValue>, L: Logger + 'static,
{
fn console_method<L: Logger + 'static>(
f: fn(&JsValue, &[JsValue], &Console, &L, &mut Context) -> JsResult<JsValue>,
state: Rc<RefCell<Console>>, state: Rc<RefCell<Console>>,
logger: Rc<L>,
) -> NativeFunction { ) -> NativeFunction {
// SAFETY: `Console` doesn't contain types that need tracing. // SAFETY: `Console` doesn't contain types that need tracing.
unsafe { unsafe {
NativeFunction::from_closure(move |this, args, context| { NativeFunction::from_closure(move |this, args, context| {
f(this, args, &state.borrow(), context) f(this, args, &state.borrow(), &logger, context)
}) })
} }
} }
fn console_method_mut( fn console_method_mut<L: Logger + 'static>(
f: fn(&JsValue, &[JsValue], &mut Console, &mut Context) -> JsResult<JsValue>, f: fn(&JsValue, &[JsValue], &mut Console, &L, &mut Context) -> JsResult<JsValue>,
state: Rc<RefCell<Console>>, state: Rc<RefCell<Console>>,
logger: Rc<L>,
) -> NativeFunction { ) -> NativeFunction {
// SAFETY: `Console` doesn't contain types that need tracing. // SAFETY: `Console` doesn't contain types that need tracing.
unsafe { unsafe {
NativeFunction::from_closure(move |this, args, context| { NativeFunction::from_closure(move |this, args, context| {
f(this, args, &mut state.borrow_mut(), context) f(this, args, &mut state.borrow_mut(), &logger, context)
}) })
} }
} }
// let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init"); // let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let state = Rc::new(RefCell::new(Self::default())); let state = Rc::new(RefCell::new(Self::default()));
let logger = Rc::new(logger);
ObjectInitializer::with_native_data(Self::default(), context) ObjectInitializer::with_native_data(Self::default(), context)
.function( .function(
console_method(Self::assert, state.clone()), console_method(Self::assert, state.clone(), logger.clone()),
js_string!("assert"), js_string!("assert"),
0, 0,
) )
.function( .function(
console_method_mut(Self::clear, state.clone()), console_method_mut(Self::clear, state.clone(), logger.clone()),
js_string!("clear"), js_string!("clear"),
0, 0,
) )
.function( .function(
console_method(Self::debug, state.clone()), console_method(Self::debug, state.clone(), logger.clone()),
js_string!("debug"), js_string!("debug"),
0, 0,
) )
.function( .function(
console_method(Self::error, state.clone()), console_method(Self::error, state.clone(), logger.clone()),
js_string!("error"), js_string!("error"),
0, 0,
) )
.function( .function(
console_method(Self::info, state.clone()), console_method(Self::info, state.clone(), logger.clone()),
js_string!("info"), js_string!("info"),
0, 0,
) )
.function( .function(
console_method(Self::log, state.clone()), console_method(Self::log, state.clone(), logger.clone()),
js_string!("log"), js_string!("log"),
0, 0,
) )
.function( .function(
console_method(Self::trace, state.clone()), console_method(Self::trace, state.clone(), logger.clone()),
js_string!("trace"), js_string!("trace"),
0, 0,
) )
.function( .function(
console_method(Self::warn, state.clone()), console_method(Self::warn, state.clone(), logger.clone()),
js_string!("warn"), js_string!("warn"),
0, 0,
) )
.function( .function(
console_method_mut(Self::count, state.clone()), console_method_mut(Self::count, state.clone(), logger.clone()),
js_string!("count"), js_string!("count"),
0, 0,
) )
.function( .function(
console_method_mut(Self::count_reset, state.clone()), console_method_mut(Self::count_reset, state.clone(), logger.clone()),
js_string!("countReset"), js_string!("countReset"),
0, 0,
) )
.function( .function(
console_method_mut(Self::group, state.clone()), console_method_mut(Self::group, state.clone(), logger.clone()),
js_string!("group"), js_string!("group"),
0, 0,
) )
.function( .function(
console_method_mut(Self::group_collapsed, state.clone()), console_method_mut(Self::group_collapsed, state.clone(), logger.clone()),
js_string!("groupCollapsed"), js_string!("groupCollapsed"),
0, 0,
) )
.function( .function(
console_method_mut(Self::group_end, state.clone()), console_method_mut(Self::group_end, state.clone(), logger.clone()),
js_string!("groupEnd"), js_string!("groupEnd"),
0, 0,
) )
.function( .function(
console_method_mut(Self::time, state.clone()), console_method_mut(Self::time, state.clone(), logger.clone()),
js_string!("time"), js_string!("time"),
0, 0,
) )
.function( .function(
console_method(Self::time_log, state.clone()), console_method(Self::time_log, state.clone(), logger.clone()),
js_string!("timeLog"), js_string!("timeLog"),
0, 0,
) )
.function( .function(
console_method_mut(Self::time_end, state.clone()), console_method_mut(Self::time_end, state.clone(), logger.clone()),
js_string!("timeEnd"), js_string!("timeEnd"),
0, 0,
) )
.function( .function(
console_method(Self::dir, state.clone()), console_method(Self::dir, state.clone(), logger.clone()),
js_string!("dir"), js_string!("dir"),
0, 0,
) )
.function(console_method(Self::dir, state), js_string!("dirxml"), 0) .function(
console_method(Self::dir, state, logger.clone()),
js_string!("dirxml"),
0,
)
.build() .build()
} }
/// Initializes the `console` built-in object.
pub fn init(context: &mut Context) -> JsObject {
Self::init_with_logger(context, DefaultLogger)
}
/// `console.assert(condition, ...data)` /// `console.assert(condition, ...data)`
/// ///
/// Prints a JavaScript value to the standard error if first argument evaluates to `false` or there /// Prints a JavaScript value to the standard error if first argument evaluates to `false` or there
@ -266,6 +326,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let assertion = args.first().map_or(false, JsValue::to_boolean); let assertion = args.first().map_or(false, JsValue::to_boolean);
@ -283,7 +344,7 @@ impl Console {
args[0] = JsValue::new(concat); args[0] = JsValue::new(concat);
} }
logger(LogMessage::Error(formatter(&args, context)?), console); logger.error(formatter(&args, context)?, console)?;
} }
Ok(JsValue::undefined()) Ok(JsValue::undefined())
@ -300,7 +361,13 @@ impl Console {
/// [spec]: https://console.spec.whatwg.org/#clear /// [spec]: https://console.spec.whatwg.org/#clear
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/clear
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn clear(_: &JsValue, _: &[JsValue], console: &mut Self, _: &mut Context) -> JsResult<JsValue> { fn clear(
_: &JsValue,
_: &[JsValue],
console: &mut Self,
_: &impl Logger,
_: &mut Context,
) -> JsResult<JsValue> {
console.groups.clear(); console.groups.clear();
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -319,9 +386,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
logger(LogMessage::Log(formatter(args, context)?), console); logger.log(formatter(args, context)?, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -339,9 +407,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
logger(LogMessage::Error(formatter(args, context)?), console); logger.error(formatter(args, context)?, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -359,9 +428,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
logger(LogMessage::Info(formatter(args, context)?), console); logger.info(formatter(args, context)?, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -379,9 +449,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
logger(LogMessage::Log(formatter(args, context)?), console); logger.log(formatter(args, context)?, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -399,10 +470,11 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
if !args.is_empty() { if !args.is_empty() {
logger(LogMessage::Log(formatter(args, context)?), console); logger.log(formatter(args, context)?, console)?;
} }
let stack_trace_dump = context let stack_trace_dump = context
@ -413,7 +485,7 @@ impl Console {
.map(JsString::to_std_string_escaped) .map(JsString::to_std_string_escaped)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
logger(LogMessage::Log(stack_trace_dump), console); logger.log(stack_trace_dump, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -432,9 +504,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
logger(LogMessage::Warn(formatter(args, context)?), console); logger.warn(formatter(args, context)?, console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -452,6 +525,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let label = match args.first() { let label = match args.first() {
@ -463,7 +537,7 @@ impl Console {
let c = console.count_map.entry(label).or_insert(0); let c = console.count_map.entry(label).or_insert(0);
*c += 1; *c += 1;
logger(LogMessage::Info(format!("{msg} {c}")), console); logger.info(format!("{msg} {c}"), console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -481,6 +555,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let label = match args.first() { let label = match args.first() {
@ -490,10 +565,10 @@ impl Console {
console.count_map.remove(&label); console.count_map.remove(&label);
logger( logger.warn(
LogMessage::Warn(format!("countReset {}", label.to_std_string_escaped())), format!("countReset {}", label.to_std_string_escaped()),
console, console,
); )?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -520,6 +595,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let label = match args.first() { let label = match args.first() {
@ -531,13 +607,10 @@ impl Console {
let time = Self::system_time_in_ms(); let time = Self::system_time_in_ms();
e.insert(time); e.insert(time);
} else { } else {
logger( logger.warn(
LogMessage::Warn(format!( format!("Timer '{}' already exist", label.to_std_string_escaped()),
"Timer '{}' already exist",
label.to_std_string_escaped()
)),
console, console,
); )?;
} }
Ok(JsValue::undefined()) Ok(JsValue::undefined())
@ -557,6 +630,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &Self, console: &Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let label = match args.first() { let label = match args.first() {
@ -566,13 +640,10 @@ impl Console {
console.timer_map.get(&label).map_or_else( console.timer_map.get(&label).map_or_else(
|| { || {
logger( logger.warn(
LogMessage::Warn(format!( format!("Timer '{}' doesn't exist", label.to_std_string_escaped()),
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console, console,
); )
}, },
|t| { |t| {
let time = Self::system_time_in_ms(); let time = Self::system_time_in_ms();
@ -580,9 +651,9 @@ impl Console {
for msg in args.iter().skip(1) { for msg in args.iter().skip(1) {
concat = concat + " " + &msg.display().to_string(); concat = concat + " " + &msg.display().to_string();
} }
logger(LogMessage::Log(concat), console); logger.log(concat, console)
}, },
); )?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -601,6 +672,7 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let label = match args.first() { let label = match args.first() {
@ -610,26 +682,23 @@ impl Console {
console.timer_map.remove(&label).map_or_else( console.timer_map.remove(&label).map_or_else(
|| { || {
logger( logger.warn(
LogMessage::Warn(format!( format!("Timer '{}' doesn't exist", label.to_std_string_escaped()),
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console, console,
); )
}, },
|t| { |t| {
let time = Self::system_time_in_ms(); let time = Self::system_time_in_ms();
logger( logger.info(
LogMessage::Info(format!( format!(
"{}: {} ms - timer removed", "{}: {} ms - timer removed",
label.to_std_string_escaped(), label.to_std_string_escaped(),
time - t time - t
)), ),
console, console,
); )
}, },
); )?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
@ -648,11 +717,12 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let group_label = formatter(args, context)?; let group_label = formatter(args, context)?;
logger(LogMessage::Info(format!("group: {group_label}")), console); logger.info(format!("group: {group_label}"), console)?;
console.groups.push(group_label); console.groups.push(group_label);
Ok(JsValue::undefined()) Ok(JsValue::undefined())
@ -672,9 +742,10 @@ impl Console {
_: &JsValue, _: &JsValue,
args: &[JsValue], args: &[JsValue],
console: &mut Self, console: &mut Self,
logger: &impl Logger,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Console::group(&JsValue::Undefined, args, console, context) Console::group(&JsValue::Undefined, args, console, logger, context)
} }
/// `console.groupEnd(label)` /// `console.groupEnd(label)`
@ -692,6 +763,7 @@ impl Console {
_: &JsValue, _: &JsValue,
_: &[JsValue], _: &[JsValue],
console: &mut Self, console: &mut Self,
_: &impl Logger,
_: &mut Context, _: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
console.groups.pop(); console.groups.pop();
@ -710,11 +782,14 @@ impl Console {
/// [spec]: https://console.spec.whatwg.org/#dir /// [spec]: https://console.spec.whatwg.org/#dir
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn dir(_: &JsValue, args: &[JsValue], console: &Self, _: &mut Context) -> JsResult<JsValue> { fn dir(
logger( _: &JsValue,
LogMessage::Info(args.get_or_undefined(0).display_obj(true)), args: &[JsValue],
console, console: &Self,
); logger: &impl Logger,
_: &mut Context,
) -> JsResult<JsValue> {
logger.info(args.get_or_undefined(0).display_obj(true), console)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
} }
} }

2
core/runtime/src/lib.rs

@ -54,7 +54,7 @@
mod console; mod console;
#[doc(inline)] #[doc(inline)]
pub use console::Console; pub use console::{Console, Logger};
#[cfg(test)] #[cfg(test)]
pub(crate) mod test { pub(crate) mod test {

Loading…
Cancel
Save