quickjs_rs/
console.rs

1//! Javascript console integration.
2//! See the [ConsoleBackend] trait for more info.
3
4use super::JsValue;
5
6/// Log level of a log message sent via the console.
7/// These levels represent the different functions defined in the spec:
8/// <https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html>
9#[allow(missing_docs)]
10#[derive(PartialEq, Eq, Clone, Copy, Debug)]
11pub enum Level {
12    Trace,
13    Debug,
14    Log,
15    Info,
16    Warn,
17    Error,
18}
19
20impl std::fmt::Display for Level {
21    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
22        use Level::*;
23        let v = match self {
24            Trace => "trace",
25            Debug => "debug",
26            Log => "log",
27            Info => "info",
28            Warn => "warn",
29            Error => "error",
30        };
31        write!(f, "{}", v)
32    }
33}
34
35/// A console backend that handles console messages sent from JS via
36/// console.{log,debug,trace,...} functions.
37///
38/// A backend has to be registered via the `ContextBuilder::console` method.
39///
40/// A backend that forwads to the `log` crate is available with the `log` feature.
41///
42/// Note that any closure of type `Fn(Level, Vec<JsValue>)` implements this trait.
43///
44/// A very simple logger that just prints to stderr could look like this:
45///
46/// ```rust
47/// use quickjs_rs::{Context, JsValue, console::Level};
48///
49/// Context::builder()
50///     .console(|level: Level, args: Vec<JsValue>| {
51///         eprintln!("{}: {:?}", level, args);
52///     })
53///     .build()
54///     # .unwrap();
55/// ```
56///
57pub trait ConsoleBackend: std::panic::RefUnwindSafe + 'static {
58    /// Handle a log message.
59    fn log(&self, level: Level, values: Vec<JsValue>);
60}
61
62impl<F> ConsoleBackend for F
63where
64    F: Fn(Level, Vec<JsValue>) + std::panic::RefUnwindSafe + 'static,
65{
66    fn log(&self, level: Level, values: Vec<JsValue>) {
67        (self)(level, values);
68    }
69}
70
71#[cfg(feature = "log")]
72mod log {
73    use super::{JsValue, Level};
74
75    /// A console implementation that logs messages via the `log` crate.
76    ///
77    /// Only available with the `log` feature.
78    pub struct LogConsole;
79
80    fn print_value(value: JsValue) -> String {
81        match value {
82            JsValue::Undefined => "undefined".to_string(),
83            JsValue::Null => "null".to_string(),
84            JsValue::Bool(v) => v.to_string(),
85            JsValue::Int(v) => v.to_string(),
86            JsValue::Float(v) => v.to_string(),
87            JsValue::String(v) => v,
88            JsValue::Array(values) => {
89                let parts = values
90                    .into_iter()
91                    .map(print_value)
92                    .collect::<Vec<_>>()
93                    .join(", ");
94                format!("[{}]", parts)
95            }
96            JsValue::Object(map) => {
97                let parts = map
98                    .into_iter()
99                    .map(|(key, value)| format!("{}: {}", key, print_value(value)))
100                    .collect::<Vec<_>>()
101                    .join(", ");
102                format!("{{{}}}", parts)
103            }
104            #[cfg(feature = "chrono")]
105            JsValue::Date(v) => v.to_string(),
106            #[cfg(feature = "bigint")]
107            JsValue::BigInt(v) => v.to_string(),
108            JsValue::__NonExhaustive => unreachable!(),
109        }
110    }
111
112    impl super::ConsoleBackend for LogConsole {
113        fn log(&self, level: Level, values: Vec<JsValue>) {
114            if values.is_empty() {
115                return;
116            }
117            let log_level = match level {
118                Level::Trace => log::Level::Trace,
119                Level::Debug => log::Level::Debug,
120                Level::Log => log::Level::Info,
121                Level::Info => log::Level::Info,
122                Level::Warn => log::Level::Warn,
123                Level::Error => log::Level::Error,
124            };
125
126            let msg = values
127                .into_iter()
128                .map(print_value)
129                .collect::<Vec<_>>()
130                .join(" ");
131
132            log::log!(log_level, "{}", msg);
133        }
134    }
135}
136
137#[cfg(feature = "log")]
138pub use self::log::LogConsole;