[njs] Modules: introduced console object.

Dmitry Volyntsev xeioex at nginx.com
Sat Sep 23 00:39:22 UTC 2023


details:   https://hg.nginx.org/njs/rev/ed935fa4805b
branches:  
changeset: 2208:ed935fa4805b
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Fri Sep 22 13:00:06 2023 -0700
description:
Modules: introduced console object.

The following methods were added: dump(), error(), info(),
log(), time(), timeEnd(), warn().

diffstat:

 nginx/ngx_js.c       |  365 ++++++++++++++++++++++++++++++++++++++++++++++++--
 nginx/t/js_console.t |  149 ++++++++++++++++++++
 2 files changed, 499 insertions(+), 15 deletions(-)

diffs (592 lines):

diff -r cf7e8f006bd8 -r ed935fa4805b nginx/ngx_js.c
--- a/nginx/ngx_js.c	Fri Sep 22 13:00:05 2023 -0700
+++ b/nginx/ngx_js.c	Fri Sep 22 13:00:06 2023 -0700
@@ -11,6 +11,18 @@
 #include "ngx_js.h"
 
 
+typedef struct {
+    ngx_queue_t       labels;
+} ngx_js_console_t;
+
+
+typedef struct {
+    njs_str_t         name;
+    uint64_t          time;
+    ngx_queue_t       queue;
+} ngx_js_timelabel_t;
+
+
 static njs_int_t ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop,
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
 static njs_int_t ngx_js_ext_conf_file_path(njs_vm_t *vm,
@@ -27,9 +39,14 @@ static njs_int_t ngx_js_ext_version(njs_
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
 static njs_int_t ngx_js_ext_worker_id(njs_vm_t *vm, njs_object_prop_t *prop,
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t ngx_js_ext_console_time(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 static void ngx_js_cleanup_vm(void *data);
 
 static njs_int_t ngx_js_core_init(njs_vm_t *vm);
+static uint64_t ngx_js_monotonic_time(void);
 
 
 static njs_external_t  ngx_js_ext_global_shared[] = {
@@ -197,6 +214,103 @@ static njs_external_t  ngx_js_ext_core[]
 };
 
 
+static njs_external_t  ngx_js_ext_console[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "Console",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("dump"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_log,
+#define NGX_JS_LOG_DUMP  16
+#define NGX_JS_LOG_MASK  15
+            .magic8 = NGX_LOG_INFO | NGX_JS_LOG_DUMP,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("error"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_log,
+            .magic8 = NGX_LOG_ERR,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("info"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_log,
+            .magic8 = NGX_LOG_INFO,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("log"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_log,
+            .magic8 = NGX_LOG_INFO,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("time"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_console_time,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("timeEnd"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_console_time_end,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("warn"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_log,
+            .magic8 = NGX_LOG_WARN,
+        }
+    },
+
+};
+
+
 njs_module_t  ngx_js_ngx_module = {
     .name = njs_str("ngx"),
     .preinit = NULL,
@@ -210,6 +324,9 @@ njs_module_t *njs_js_addon_modules_share
 };
 
 
+static njs_int_t      ngx_js_console_proto_id;
+
+
 ngx_int_t
 ngx_js_call(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log,
     njs_opaque_value_t *args, njs_uint_t nargs)
@@ -345,6 +462,26 @@ ngx_js_core_init(njs_vm_t *vm)
         return NJS_ERROR;
     }
 
+    ngx_js_console_proto_id = njs_vm_external_prototype(vm, ngx_js_ext_console,
+                                               njs_nitems(ngx_js_ext_console));
+    if (ngx_js_console_proto_id < 0) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_vm_external_create(vm, njs_value_arg(&value),
+                                 ngx_js_console_proto_id, NULL, 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    name.length = 7;
+    name.start = (u_char *) "console";
+
+    ret = njs_vm_bind(vm, &name, njs_value_arg(&value), 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
     return NJS_OK;
 }
 
@@ -509,14 +646,13 @@ ngx_js_ext_worker_id(njs_vm_t *vm, njs_o
 
 njs_int_t
 ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t level, njs_value_t *retval)
+    njs_index_t magic, njs_value_t *retval)
 {
-    char                *p;
-    ngx_int_t            lvl;
-    njs_str_t            msg;
-    njs_value_t         *value;
-    ngx_connection_t    *c;
-    ngx_log_handler_pt   handler;
+    char             *p;
+    ngx_int_t        lvl;
+    njs_str_t        msg;
+    njs_uint_t       n;
+    njs_log_level_t  level;
 
     p = njs_vm_external(vm, NJS_PROTO_ID_ANY, njs_argument(args, 0));
     if (p == NULL) {
@@ -524,7 +660,7 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t
         return NJS_ERROR;
     }
 
-    value =  njs_arg(args, nargs, (level != 0) ? 1 : 2);
+    level = (njs_log_level_t) magic & NGX_JS_LOG_MASK;
 
     if (level == 0) {
         if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &lvl) != NGX_OK) {
@@ -532,20 +668,196 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t
         }
 
         level = lvl;
+        n = 2;
+
+    } else {
+        n = 1;
+    }
+
+    for (; n < nargs; n++) {
+        if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1,
+                              !!(magic & NGX_JS_LOG_DUMP))
+            == NJS_ERROR)
+        {
+            return NJS_ERROR;
+        }
+
+        ngx_js_logger(vm, p, level, msg.start, msg.length);
     }
 
-    if (ngx_js_string(vm, value, &msg) != NGX_OK) {
+    njs_value_undefined_set(retval);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+ngx_js_ext_console_time(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    njs_int_t           ret;
+    njs_str_t           name;
+    ngx_queue_t         *labels, *q;
+    njs_value_t         *value, *this;
+    ngx_js_console_t    *console;
+    ngx_js_timelabel_t  *label;
+
+    static const njs_str_t  default_label = njs_str("default");
+
+    this = njs_argument(args, 0);
+
+    if (njs_slow_path(!njs_value_is_external(this, ngx_js_console_proto_id))) {
+        njs_vm_type_error(vm, "\"this\" is not a console external");
         return NJS_ERROR;
     }
 
-    c = ngx_external_connection(vm, p);
-    handler = c->log->handler;
-    c->log->handler = NULL;
+    name = default_label;
+
+    value = njs_arg(args, nargs, 1);
+
+    if (njs_slow_path(!njs_value_is_string(value))) {
+        if (!njs_value_is_undefined(value)) {
+            ret = njs_value_to_string(vm, value, value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            njs_value_string_get(value, &name);
+        }
+
+    } else {
+        njs_value_string_get(value, &name);
+    }
+
+    console = njs_value_external(this);
+
+    if (console == NULL) {
+        console = njs_mp_alloc(njs_vm_memory_pool(vm),
+                               sizeof(ngx_js_console_t));
+        if (console == NULL) {
+            njs_vm_memory_error(vm);
+            return NJS_ERROR;
+        }
+
+        ngx_queue_init(&console->labels);
+
+        njs_value_external_set(this, console);
+    }
+
+    labels = &console->labels;
+
+    for (q = ngx_queue_head(labels);
+         q != ngx_queue_sentinel(labels);
+         q = ngx_queue_next(q))
+    {
+        label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+        if (njs_strstr_eq(&name, &label->name)) {
+            njs_vm_log(vm, "Timer \"%V\" already exists.\n", &name);
+            njs_value_undefined_set(retval);
+            return NJS_OK;
+        }
+    }
+
+    label = njs_mp_alloc(njs_vm_memory_pool(vm),
+                         sizeof(ngx_js_timelabel_t) + name.length);
+    if (njs_slow_path(label == NULL)) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    label->name.length = name.length;
+    label->name.start = (u_char *) label + sizeof(ngx_js_timelabel_t);
+    memcpy(label->name.start, name.start, name.length);
+
+    label->time = ngx_js_monotonic_time();
+
+    ngx_queue_insert_tail(&console->labels, &label->queue);
+
+    njs_value_undefined_set(retval);
+
+    return NJS_OK;
+}
+
 
-    ngx_log_error((ngx_uint_t) level, c->log, 0, "js: %*s",
-                  msg.length, msg.start);
+static njs_int_t
+ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    uint64_t            ns, ms;
+    njs_int_t           ret;
+    njs_str_t           name;
+    ngx_queue_t         *labels, *q;
+    njs_value_t         *value, *this;
+    ngx_js_console_t    *console;
+    ngx_js_timelabel_t  *label;
+
+    static const njs_str_t  default_label = njs_str("default");
+
+    ns = ngx_js_monotonic_time();
+
+    this = njs_argument(args, 0);
+
+    if (njs_slow_path(!njs_value_is_external(this, ngx_js_console_proto_id))) {
+        njs_vm_type_error(vm, "\"this\" is not a console external");
+        return NJS_ERROR;
+    }
+
+    name = default_label;
+
+    value = njs_arg(args, nargs, 1);
+
+    if (njs_slow_path(!njs_value_is_string(value))) {
+        if (!njs_value_is_undefined(value)) {
+            ret = njs_value_to_string(vm, value, value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            njs_value_string_get(value, &name);
+        }
 
-    c->log->handler = handler;
+    } else {
+        njs_value_string_get(value, &name);
+    }
+
+    console = njs_value_external(this);
+    if (njs_slow_path(console == NULL)) {
+        goto not_found;
+    }
+
+    labels = &console->labels;
+    q = ngx_queue_head(labels);
+
+    for ( ;; ) {
+        if (q == ngx_queue_sentinel(labels)) {
+            goto not_found;
+        }
+
+        label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+        if (njs_strstr_eq(&name, &label->name)) {
+            ngx_queue_remove(&label->queue);
+            break;
+        }
+
+        q = ngx_queue_next(q);
+    }
+
+    ns = ns - label->time;
+
+    ms = ns / 1000000;
+    ns = ns % 1000000;
+
+    njs_vm_log(vm, "%V: %uL.%06uLms\n", &name, ms, ns);
+
+    njs_value_undefined_set(retval);
+
+    return NJS_OK;
+
+not_found:
+
+    njs_vm_log(vm, "Timer \"%V\" doesn't exist.\n", &name);
 
     njs_value_undefined_set(retval);
 
@@ -1308,3 +1620,26 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *
     return NGX_CONF_OK;
 #endif
 }
+
+
+static uint64_t
+ngx_js_monotonic_time(void)
+{
+#if (NGX_HAVE_CLOCK_MONOTONIC)
+    struct timespec  ts;
+
+#if defined(CLOCK_MONOTONIC_FAST)
+    clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
+#else
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+#endif
+
+    return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec;
+#else
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+
+    return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
+#endif
+}
diff -r cf7e8f006bd8 -r ed935fa4805b nginx/t/js_console.t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nginx/t/js_console.t	Fri Sep 22 13:00:06 2023 -0700
@@ -0,0 +1,149 @@
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) Nginx, Inc.
+
+# Tests for http njs module, console object.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http/)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    js_import test.js;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location /dump {
+            js_content test.dump;
+        }
+
+        location /error {
+            js_content test.error;
+        }
+
+        location /info {
+            js_content test.info;
+        }
+
+        location /log {
+            js_content test.log;
+        }
+
+        location /time {
+            js_content test.time;
+        }
+
+        location /time_test {
+            js_content test.time_test;
+        }
+
+        location /warn {
+            js_content test.warn;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('test.js', <<EOF);
+    function l(r, method) {
+        const data = Buffer.from(r.args.data, 'base64');
+        const object = JSON.parse(data);
+        console[method](object);
+        r.return(200);
+    }
+
+    function dump(r) {
+        l(r, 'dump');
+    }
+
+    function error(r) {
+        l(r, 'error');
+    }
+
+    function info(r) {
+        l(r, 'info');
+    }
+
+    function log(r) {
+        l(r, 'log');
+    }
+
+    function time(r) {
+        console.time(r.args.timer);
+        setTimeout(function() {
+            console.timeEnd(r.args.timer);
+            r.return(200);
+        }, parseInt(r.args.delay));
+    }
+
+    function time_test(r) {
+        console.time();
+        console.time();
+        console.timeEnd();
+        console.timeEnd('test');
+    }
+
+    function warn(r) {
+        l(r, 'warn');
+    }
+
+    export default {dump, error, info, log, time, time_test, warn};
+
+EOF
+
+$t->try_run('no njs console')->plan(7);
+
+###############################################################################
+
+http_get('/dump?data=eyJhIjpbMiwzXX0');
+http_get('/error?data=IldBS0Ei');
+http_get('/info?data=IkJBUiI');
+http_get('/log?data=eyJhIjpbIkIiLCJDIl19');
+http_get('/time?delay=7&timer=foo');
+http_get('/time_test');
+http_get('/warn?data=IkZPTyI');
+
+$t->stop();
+
+like($t->read_file('error.log'), qr/\[error\].*js: WAKA/, 'console.error');
+like($t->read_file('error.log'), qr/\[info\].*js: BAR/, 'console.info');
+like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/,
+	'console.log with object');
+like($t->read_file('error.log'), qr/\[warn\].*js: FOO/, 'console.warn');
+like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/,
+	'console.time foo');
+like($t->read_file('error.log'), qr/\[info\].*js: Timer \"default\" already/,
+	'console.time already started');
+like($t->read_file('error.log'), qr/\[info\].*js: Timer \"test\" doesn't/,
+	'console.time not started');
+
+###############################################################################


More information about the nginx-devel mailing list