/* * Logging support */ #include #include #include #include "duktape.h" #include "duk_logging.h" /* XXX: uses stderr always for now, configurable? */ #define DUK_LOGGING_FLUSH /* Duktape 1.x: flush stderr */ /* 3-letter log level strings. */ static const char duk__log_level_strings[] = { 'T', 'R', 'C', 'D', 'B', 'G', 'I', 'N', 'F', 'W', 'R', 'N', 'E', 'R', 'R', 'F', 'T', 'L' }; /* Log method names. */ static const char *duk__log_method_names[] = { "trace", "debug", "info", "warn", "error", "fatal" }; /* Constructor. */ static duk_ret_t duk__logger_constructor(duk_context *ctx) { duk_idx_t nargs; /* Calling as a non-constructor is not meaningful. */ if (!duk_is_constructor_call(ctx)) { return DUK_RET_TYPE_ERROR; } nargs = duk_get_top(ctx); duk_set_top(ctx, 1); duk_push_this(ctx); /* [ name this ] */ if (nargs == 0) { /* Automatic defaulting of logger name from caller. This * would work poorly with tail calls, but constructor calls * are currently never tail calls, so tail calls are not an * issue now. */ duk_inspect_callstack_entry(ctx, -2); if (duk_is_object(ctx, -1)) { if (duk_get_prop_string(ctx, -1, "function")) { if (duk_get_prop_string(ctx, -1, "fileName")) { if (duk_is_string(ctx, -1)) { duk_replace(ctx, 0); } } } } /* Leave values on stack on purpose, ignored below. */ /* Stripping the filename might be a good idea * ("/foo/bar/quux.js" -> logger name "quux"), * but now used verbatim. */ } /* The stack is unbalanced here on purpose; we only rely on the * initial two values: [ name this ]. */ if (duk_is_string(ctx, 0)) { duk_dup(ctx, 0); duk_put_prop_string(ctx, 1, "n"); } else { /* don't set 'n' at all, inherited value is used as name */ } duk_compact(ctx, 1); return 0; /* keep default instance */ } /* Default function to format objects. Tries to use toLogString() but falls * back to toString(). Any errors are propagated out without catching. */ static duk_ret_t duk__logger_prototype_fmt(duk_context *ctx) { if (duk_get_prop_string(ctx, 0, "toLogString")) { /* [ arg toLogString ] */ duk_dup(ctx, 0); duk_call_method(ctx, 0); /* [ arg result ] */ return 1; } /* [ arg undefined ] */ duk_pop(ctx); duk_to_string(ctx, 0); return 1; } /* Default function to write a formatted log line. Writes to stderr, * appending a newline to the log line. * * The argument is a buffer; avoid coercing the buffer to a string to * avoid string table traffic. */ static duk_ret_t duk__logger_prototype_raw(duk_context *ctx) { const char *data; duk_size_t data_len; data = (const char *) duk_require_buffer_data(ctx, 0, &data_len); fwrite((const void *) data, 1, data_len, stderr); fputc((int) '\n', stderr); #if defined(DUK_LOGGING_FLUSH) fflush(stderr); #endif return 0; } /* Log frontend shared helper, magic value indicates log level. Provides * frontend functions: trace(), debug(), info(), warn(), error(), fatal(). * This needs to have small footprint, reasonable performance, minimal * memory churn, etc. */ static duk_ret_t duk__logger_prototype_log_shared(duk_context *ctx) { duk_double_t now; duk_time_components comp; duk_small_int_t entry_lev; duk_small_int_t logger_lev; duk_int_t nargs; duk_int_t i; duk_size_t tot_len; const duk_uint8_t *arg_str; duk_size_t arg_len; duk_uint8_t *buf, *p; const duk_uint8_t *q; duk_uint8_t date_buf[32]; /* maximum format length is 24+1 (NUL), round up. */ duk_size_t date_len; duk_small_int_t rc; /* XXX: sanitize to printable (and maybe ASCII) */ /* XXX: better multiline */ /* * Logger arguments are: * * magic: log level (0-5) * this: logger * stack: plain log args * * We want to minimize memory churn so a two-pass approach * is used: first pass formats arguments and computes final * string length, second pass copies strings into a buffer * allocated directly with the correct size. If the backend * function plays nice, it won't coerce the buffer to a string * (and thus intern it). */ entry_lev = duk_get_current_magic(ctx); if (entry_lev < DUK_LOG_TRACE || entry_lev > DUK_LOG_FATAL) { /* Should never happen, check just in case. */ return 0; } nargs = duk_get_top(ctx); /* [ arg1 ... argN this ] */ /* * Log level check */ duk_push_this(ctx); duk_get_prop_string(ctx, -1, "l"); logger_lev = (duk_small_int_t) duk_get_int(ctx, -1); if (entry_lev < logger_lev) { return 0; } /* log level could be popped but that's not necessary */ now = duk_get_now(ctx); duk_time_to_components(ctx, now, &comp); sprintf((char *) date_buf, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", (int) comp.year, (int) comp.month + 1, (int) comp.day, (int) comp.hours, (int) comp.minutes, (int) comp.seconds, (int) comp.milliseconds); date_len = strlen((const char *) date_buf); duk_get_prop_string(ctx, -2, "n"); duk_to_string(ctx, -1); /* [ arg1 ... argN this loggerLevel loggerName ] */ /* * Pass 1 */ /* Line format: