quickjs_runtime/features/
console.rs1use crate::jsutils::{JsError, JsValueType};
45use crate::quickjs_utils;
46use crate::quickjs_utils::functions::call_to_string;
47use crate::quickjs_utils::json::stringify;
48use crate::quickjs_utils::{functions, json, parse_args, primitives};
49use crate::quickjsrealmadapter::QuickJsRealmAdapter;
50use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
51use crate::quickjsvalueadapter::QuickJsValueAdapter;
52use crate::reflection::Proxy;
53use libquickjs_sys as q;
54use log::LevelFilter;
55use std::str::FromStr;
56
57pub fn init(q_js_rt: &QuickJsRuntimeAdapter) -> Result<(), JsError> {
58 q_js_rt.add_context_init_hook(|_q_js_rt, q_ctx| init_ctx(q_ctx))
59}
60
61pub(crate) fn init_ctx(q_ctx: &QuickJsRealmAdapter) -> Result<(), JsError> {
62 Proxy::new()
63 .name("console")
64 .static_native_method("log", Some(console_log))
65 .static_native_method("trace", Some(console_trace))
66 .static_native_method("info", Some(console_info))
67 .static_native_method("warn", Some(console_warn))
68 .static_native_method("error", Some(console_error))
69 .static_native_method("debug", Some(console_debug))
71 .install(q_ctx, true)
72 .map(|_| {})
73}
74
75#[allow(clippy::or_fun_call)]
76unsafe fn parse_field_value(
77 ctx: *mut q::JSContext,
78 field: &str,
79 value: &QuickJsValueAdapter,
80) -> String {
81 if field.eq(&"%.0f".to_string()) {
88 return parse_field_value(ctx, "%i", value);
89 }
90
91 if field.ends_with('d') || field.ends_with('i') {
92 let mut i_val: String = call_to_string(ctx, value).unwrap_or_default();
93
94 if let Some(i) = i_val.find('.') {
96 let _ = i_val.split_off(i);
97 }
98
99 if let Some(dot_in_field_idx) = field.find('.') {
100 let mut m_field = field.to_string();
101 let mut num_decimals_str = m_field.split_off(dot_in_field_idx + 1);
103 let _ = num_decimals_str.split_off(num_decimals_str.len() - 1);
105 if !num_decimals_str.is_empty() {
107 let ct_res = usize::from_str(num_decimals_str.as_str());
108 if let Ok(ct) = ct_res {
110 while i_val.len() < ct {
112 i_val = format!("0{i_val}");
113 }
114 }
115 }
116 }
117
118 return i_val;
119 } else if field.ends_with('f') {
120 let mut f_val: String = call_to_string(ctx, value).unwrap_or_default();
121
122 if let Some(dot_in_field_idx) = field.find('.') {
123 let mut m_field = field.to_string();
124 let mut num_decimals_str = m_field.split_off(dot_in_field_idx + 1);
126 let _ = num_decimals_str.split_off(num_decimals_str.len() - 1);
128 if !num_decimals_str.is_empty() {
130 let ct_res = usize::from_str(num_decimals_str.as_str());
131 if let Ok(ct) = ct_res {
133 if ct > 0 {
135 if !f_val.contains('.') {
136 f_val.push('.');
137 }
138
139 let dot_idx = f_val.find('.').unwrap();
140
141 while f_val.len() - dot_idx <= ct {
142 f_val.push('0');
143 }
144 if f_val.len() - dot_idx > ct {
145 let _ = f_val.split_off(dot_idx + ct + 1);
146 }
147 }
148 }
149 }
150 return f_val;
151 }
152 } else if field.ends_with('o') || field.ends_with('O') {
153 let json_str_res = json::stringify(ctx, value, None);
154 let json = match json_str_res {
155 Ok(json_str) => {
156 if json_str.is_undefined() {
157 "undefined".to_string()
159 } else {
160 primitives::to_string(ctx, &json_str).unwrap_or_default()
161 }
162 }
163 Err(_e) => "".to_string(),
164 };
165 return json;
166 }
167 call_to_string(ctx, value).unwrap_or_default()
168}
169
170unsafe fn stringify_log_obj(ctx: *mut q::JSContext, arg: &QuickJsValueAdapter) -> String {
171 match stringify(ctx, arg, None) {
172 Ok(r) => match primitives::to_string(ctx, &r) {
173 Ok(s) => s,
174 Err(e) => format!("Error: {e}"),
175 },
176 Err(e) => format!("Error: {e}"),
177 }
178}
179
180#[allow(clippy::or_fun_call)]
181unsafe fn parse_line(ctx: *mut q::JSContext, args: Vec<QuickJsValueAdapter>) -> String {
182 let mut output = String::new();
183
184 output.push_str("JS_REALM:");
185 QuickJsRealmAdapter::with_context(ctx, |realm| {
186 output.push('[');
187 output.push_str(realm.id.as_str());
188 output.push_str("][");
189 if let Ok(script_or_module_name) = quickjs_utils::get_script_or_module_name_q(realm) {
190 output.push_str(script_or_module_name.as_str());
191 }
192 output.push_str("]: ");
193 });
194
195 if args.is_empty() {
196 return output;
197 }
198
199 let message = match &args[0].get_js_type() {
200 JsValueType::Object => stringify_log_obj(ctx, &args[0]),
201 JsValueType::Function => stringify_log_obj(ctx, &args[0]),
202 JsValueType::Array => stringify_log_obj(ctx, &args[0]),
203 _ => functions::call_to_string(ctx, &args[0]).unwrap_or_default(),
204 };
205
206 let mut field_code = String::new();
207 let mut in_field = false;
208
209 let mut x = 1;
210
211 let mut filled = 1;
212
213 if args[0].is_string() {
214 for chr in message.chars() {
215 if in_field {
216 field_code.push(chr);
217 if chr.eq(&'s') || chr.eq(&'d') || chr.eq(&'f') || chr.eq(&'o') || chr.eq(&'i') {
218 if x < args.len() {
221 output.push_str(
222 parse_field_value(ctx, field_code.as_str(), &args[x]).as_str(),
223 );
224 x += 1;
225 filled += 1;
226 }
227
228 in_field = false;
229 field_code = String::new();
230 }
231 } else if chr.eq(&'%') {
232 in_field = true;
233 } else {
234 output.push(chr);
235 }
236 }
237 } else {
238 output.push_str(message.as_str());
239 }
240
241 for arg in args.iter().skip(filled) {
242 output.push(' ');
244 let tail_arg = match arg.get_js_type() {
245 JsValueType::Object => stringify_log_obj(ctx, arg),
246 JsValueType::Function => stringify_log_obj(ctx, arg),
247 JsValueType::Array => stringify_log_obj(ctx, arg),
248 _ => call_to_string(ctx, arg).unwrap_or_default(),
249 };
250 output.push_str(tail_arg.as_str());
251 }
252
253 output
254}
255
256unsafe extern "C" fn console_log(
257 ctx: *mut q::JSContext,
258 _this_val: q::JSValue,
259 argc: ::std::os::raw::c_int,
260 argv: *mut q::JSValue,
261) -> q::JSValue {
262 if log::max_level() >= LevelFilter::Info {
263 let args = parse_args(ctx, argc, argv);
264 log::info!("{}", parse_line(ctx, args));
265 }
266 quickjs_utils::new_null()
267}
268
269unsafe extern "C" fn console_trace(
270 ctx: *mut q::JSContext,
271 _this_val: q::JSValue,
272 argc: ::std::os::raw::c_int,
273 argv: *mut q::JSValue,
274) -> q::JSValue {
275 if log::max_level() >= LevelFilter::Trace {
276 let args = parse_args(ctx, argc, argv);
277 log::trace!("{}", parse_line(ctx, args));
278 }
279 quickjs_utils::new_null()
280}
281
282unsafe extern "C" fn console_debug(
283 ctx: *mut q::JSContext,
284 _this_val: q::JSValue,
285 argc: ::std::os::raw::c_int,
286 argv: *mut q::JSValue,
287) -> q::JSValue {
288 if log::max_level() >= LevelFilter::Debug {
289 let args = parse_args(ctx, argc, argv);
290 log::debug!("{}", parse_line(ctx, args));
291 }
292 quickjs_utils::new_null()
293}
294
295unsafe extern "C" fn console_info(
296 ctx: *mut q::JSContext,
297 _this_val: q::JSValue,
298 argc: ::std::os::raw::c_int,
299 argv: *mut q::JSValue,
300) -> q::JSValue {
301 if log::max_level() >= LevelFilter::Info {
302 let args = parse_args(ctx, argc, argv);
303 log::info!("{}", parse_line(ctx, args));
304 }
305 quickjs_utils::new_null()
306}
307
308unsafe extern "C" fn console_warn(
309 ctx: *mut q::JSContext,
310 _this_val: q::JSValue,
311 argc: ::std::os::raw::c_int,
312 argv: *mut q::JSValue,
313) -> q::JSValue {
314 if log::max_level() >= LevelFilter::Warn {
315 let args = parse_args(ctx, argc, argv);
316 log::warn!("{}", parse_line(ctx, args));
317 }
318 quickjs_utils::new_null()
319}
320
321unsafe extern "C" fn console_error(
322 ctx: *mut q::JSContext,
323 _this_val: q::JSValue,
324 argc: ::std::os::raw::c_int,
325 argv: *mut q::JSValue,
326) -> q::JSValue {
327 if log::max_level() >= LevelFilter::Error {
328 let args = parse_args(ctx, argc, argv);
329 log::error!("{}", parse_line(ctx, args));
330 }
331 quickjs_utils::new_null()
332}
333
334#[cfg(test)]
335pub mod tests {
336 use crate::builder::QuickJsRuntimeBuilder;
337 use crate::jsutils::Script;
338 use std::thread;
339 use std::time::Duration;
340
341 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
342 pub async fn test_console() {
343 eprintln!("> test_console");
344 log::info!("> test_console");
379 let rt = QuickJsRuntimeBuilder::new().build();
380 rt.eval_sync(
381 None,
382 Script::new(
383 "test_console.es",
384 "console.log('one %s', 'two', 3);\
385 console.error('two %s %s', 'two', 3);\
386 console.error('date:', new Date());\
387 console.error('err:', new Error('testpoof'));\
388 console.error('array:', [1, 2, true, {a: 1}]);\
389 console.error('obj: %o', {a: 1});\
390 console.error({obj: true}, {obj: false});",
391 ),
392 )
393 .expect("test_console.es failed");
394 log::info!("< test_console");
395
396 thread::sleep(Duration::from_secs(1));
397 }
398}