duk_logging.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. * Logging support
  3. */
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <stdarg.h>
  7. #include "duktape.h"
  8. #include "duk_logging.h"
  9. /* XXX: uses stderr always for now, configurable? */
  10. #define DUK_LOGGING_FLUSH /* Duktape 1.x: flush stderr */
  11. /* 3-letter log level strings. */
  12. static const char duk__log_level_strings[] = {
  13. 'T', 'R', 'C', 'D', 'B', 'G', 'I', 'N', 'F',
  14. 'W', 'R', 'N', 'E', 'R', 'R', 'F', 'T', 'L'
  15. };
  16. /* Log method names. */
  17. static const char *duk__log_method_names[] = {
  18. "trace", "debug", "info", "warn", "error", "fatal"
  19. };
  20. /* Constructor. */
  21. static duk_ret_t duk__logger_constructor(duk_context *ctx) {
  22. duk_idx_t nargs;
  23. /* Calling as a non-constructor is not meaningful. */
  24. if (!duk_is_constructor_call(ctx)) {
  25. return DUK_RET_TYPE_ERROR;
  26. }
  27. nargs = duk_get_top(ctx);
  28. duk_set_top(ctx, 1);
  29. duk_push_this(ctx);
  30. /* [ name this ] */
  31. if (nargs == 0) {
  32. /* Automatic defaulting of logger name from caller. This
  33. * would work poorly with tail calls, but constructor calls
  34. * are currently never tail calls, so tail calls are not an
  35. * issue now.
  36. */
  37. duk_inspect_callstack_entry(ctx, -2);
  38. if (duk_is_object(ctx, -1)) {
  39. if (duk_get_prop_string(ctx, -1, "function")) {
  40. if (duk_get_prop_string(ctx, -1, "fileName")) {
  41. if (duk_is_string(ctx, -1)) {
  42. duk_replace(ctx, 0);
  43. }
  44. }
  45. }
  46. }
  47. /* Leave values on stack on purpose, ignored below. */
  48. /* Stripping the filename might be a good idea
  49. * ("/foo/bar/quux.js" -> logger name "quux"),
  50. * but now used verbatim.
  51. */
  52. }
  53. /* The stack is unbalanced here on purpose; we only rely on the
  54. * initial two values: [ name this ].
  55. */
  56. if (duk_is_string(ctx, 0)) {
  57. duk_dup(ctx, 0);
  58. duk_put_prop_string(ctx, 1, "n");
  59. } else {
  60. /* don't set 'n' at all, inherited value is used as name */
  61. }
  62. duk_compact(ctx, 1);
  63. return 0; /* keep default instance */
  64. }
  65. /* Default function to format objects. Tries to use toLogString() but falls
  66. * back to toString(). Any errors are propagated out without catching.
  67. */
  68. static duk_ret_t duk__logger_prototype_fmt(duk_context *ctx) {
  69. if (duk_get_prop_string(ctx, 0, "toLogString")) {
  70. /* [ arg toLogString ] */
  71. duk_dup(ctx, 0);
  72. duk_call_method(ctx, 0);
  73. /* [ arg result ] */
  74. return 1;
  75. }
  76. /* [ arg undefined ] */
  77. duk_pop(ctx);
  78. duk_to_string(ctx, 0);
  79. return 1;
  80. }
  81. /* Default function to write a formatted log line. Writes to stderr,
  82. * appending a newline to the log line.
  83. *
  84. * The argument is a buffer; avoid coercing the buffer to a string to
  85. * avoid string table traffic.
  86. */
  87. static duk_ret_t duk__logger_prototype_raw(duk_context *ctx) {
  88. const char *data;
  89. duk_size_t data_len;
  90. data = (const char *) duk_require_buffer(ctx, 0, &data_len);
  91. fwrite((const void *) data, 1, data_len, stderr);
  92. fputc((int) '\n', stderr);
  93. #if defined(DUK_LOGGING_FLUSH)
  94. fflush(stderr);
  95. #endif
  96. return 0;
  97. }
  98. /* Log frontend shared helper, magic value indicates log level. Provides
  99. * frontend functions: trace(), debug(), info(), warn(), error(), fatal().
  100. * This needs to have small footprint, reasonable performance, minimal
  101. * memory churn, etc.
  102. */
  103. static duk_ret_t duk__logger_prototype_log_shared(duk_context *ctx) {
  104. duk_double_t now;
  105. duk_time_components comp;
  106. duk_small_int_t entry_lev;
  107. duk_small_int_t logger_lev;
  108. duk_int_t nargs;
  109. duk_int_t i;
  110. duk_size_t tot_len;
  111. const duk_uint8_t *arg_str;
  112. duk_size_t arg_len;
  113. duk_uint8_t *buf, *p;
  114. const duk_uint8_t *q;
  115. duk_uint8_t date_buf[32]; /* maximum format length is 24+1 (NUL), round up. */
  116. duk_size_t date_len;
  117. duk_small_int_t rc;
  118. /* XXX: sanitize to printable (and maybe ASCII) */
  119. /* XXX: better multiline */
  120. /*
  121. * Logger arguments are:
  122. *
  123. * magic: log level (0-5)
  124. * this: logger
  125. * stack: plain log args
  126. *
  127. * We want to minimize memory churn so a two-pass approach
  128. * is used: first pass formats arguments and computes final
  129. * string length, second pass copies strings into a buffer
  130. * allocated directly with the correct size. If the backend
  131. * function plays nice, it won't coerce the buffer to a string
  132. * (and thus intern it).
  133. */
  134. entry_lev = duk_get_current_magic(ctx);
  135. if (entry_lev < DUK_LOG_TRACE || entry_lev > DUK_LOG_FATAL) {
  136. /* Should never happen, check just in case. */
  137. return 0;
  138. }
  139. nargs = duk_get_top(ctx);
  140. /* [ arg1 ... argN this ] */
  141. /*
  142. * Log level check
  143. */
  144. duk_push_this(ctx);
  145. duk_get_prop_string(ctx, -1, "l");
  146. logger_lev = (duk_small_int_t) duk_get_int(ctx, -1);
  147. if (entry_lev < logger_lev) {
  148. return 0;
  149. }
  150. /* log level could be popped but that's not necessary */
  151. now = duk_get_now(ctx);
  152. duk_time_to_components(ctx, now, &comp);
  153. sprintf((char *) date_buf, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
  154. (int) comp.year, (int) comp.month + 1, (int) comp.day,
  155. (int) comp.hours, (int) comp.minutes, (int) comp.seconds,
  156. (int) comp.milliseconds);
  157. date_len = strlen((const char *) date_buf);
  158. duk_get_prop_string(ctx, -2, "n");
  159. duk_to_string(ctx, -1);
  160. /* [ arg1 ... argN this loggerLevel loggerName ] */
  161. /*
  162. * Pass 1
  163. */
  164. /* Line format: <time> <entryLev> <loggerName>: <msg> */
  165. tot_len = 0;
  166. tot_len += 3 + /* separators: space, space, colon */
  167. 3 + /* level string */
  168. date_len + /* time */
  169. duk_get_length(ctx, -1); /* loggerName */
  170. for (i = 0; i < nargs; i++) {
  171. /* When formatting an argument to a string, errors may happen from multiple
  172. * causes. In general we want to catch obvious errors like a toLogString()
  173. * throwing an error, but we don't currently try to catch every possible
  174. * error. In particular, internal errors (like out of memory or stack) are
  175. * not caught. Also, we expect Error toString() to not throw an error.
  176. */
  177. if (duk_is_object(ctx, i)) {
  178. /* duk_pcall_prop() may itself throw an error, but we're content
  179. * in catching the obvious errors (like toLogString() throwing an
  180. * error).
  181. */
  182. duk_push_string(ctx, "fmt");
  183. duk_dup(ctx, i);
  184. /* [ arg1 ... argN this loggerLevel loggerName 'fmt' arg ] */
  185. /* call: this.fmt(arg) */
  186. rc = duk_pcall_prop(ctx, -5 /*obj_index*/, 1 /*nargs*/);
  187. if (rc) {
  188. /* Keep the error as the result (coercing it might fail below,
  189. * but we don't catch that now).
  190. */
  191. ;
  192. }
  193. duk_replace(ctx, i);
  194. }
  195. (void) duk_to_lstring(ctx, i, &arg_len);
  196. tot_len++; /* sep (even before first one) */
  197. tot_len += arg_len;
  198. }
  199. /*
  200. * Pass 2
  201. */
  202. /* XXX: Here it'd be nice if we didn't need to allocate a new fixed
  203. * buffer for every write. This would be possible if raw() took a
  204. * buffer and a length. We could then use a preallocated buffer for
  205. * most log writes and request raw() to write a partial buffer.
  206. */
  207. buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, tot_len);
  208. p = buf;
  209. memcpy((void *) p, (const void *) date_buf, (size_t) date_len);
  210. p += date_len;
  211. *p++ = (duk_uint8_t) ' ';
  212. q = (const duk_uint8_t *) duk__log_level_strings + (entry_lev * 3);
  213. memcpy((void *) p, (const void *) q, (size_t) 3);
  214. p += 3;
  215. *p++ = (duk_uint8_t) ' ';
  216. arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, -2, &arg_len);
  217. memcpy((void *) p, (const void *) arg_str, (size_t) arg_len);
  218. p += arg_len;
  219. *p++ = (duk_uint8_t) ':';
  220. for (i = 0; i < nargs; i++) {
  221. *p++ = (duk_uint8_t) ' ';
  222. arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, i, &arg_len);
  223. memcpy((void *) p, (const void *) arg_str, (size_t) arg_len);
  224. p += arg_len;
  225. }
  226. /* [ arg1 ... argN this loggerLevel loggerName buffer ] */
  227. /* Call this.raw(msg); look up through the instance allows user to override
  228. * the raw() function in the instance or in the prototype for maximum
  229. * flexibility.
  230. */
  231. duk_push_string(ctx, "raw");
  232. duk_dup(ctx, -2);
  233. /* [ arg1 ... argN this loggerLevel loggerName buffer 'raw' buffer ] */
  234. duk_call_prop(ctx, -6, 1); /* this.raw(buffer) */
  235. return 0;
  236. }
  237. void duk_log_va(duk_context *ctx, duk_int_t level, const char *fmt, va_list ap) {
  238. if (level < 0) {
  239. level = 0;
  240. } else if (level > (int) (sizeof(duk__log_method_names) / sizeof(const char *)) - 1) {
  241. level = (int) (sizeof(duk__log_method_names) / sizeof(const char *)) - 1;
  242. }
  243. duk_push_global_stash(ctx);
  244. duk_get_prop_string(ctx, -1, "\xff" "logger:constructor"); /* fixed at init time */
  245. duk_get_prop_string(ctx, -1, "clog");
  246. duk_get_prop_string(ctx, -1, duk__log_method_names[level]);
  247. duk_dup(ctx, -2);
  248. duk_push_vsprintf(ctx, fmt, ap);
  249. /* [ ... stash Logger clog logfunc clog(=this) msg ] */
  250. duk_call_method(ctx, 1 /*nargs*/);
  251. /* [ ... stash Logger clog res ] */
  252. duk_pop_n(ctx, 4);
  253. }
  254. void duk_log(duk_context *ctx, duk_int_t level, const char *fmt, ...) {
  255. va_list ap;
  256. va_start(ap, fmt);
  257. duk_log_va(ctx, level, fmt, ap);
  258. va_end(ap);
  259. }
  260. void duk_logging_init(duk_context *ctx, duk_uint_t flags) {
  261. /* XXX: Add .name property for logger functions (useful for stack traces if they throw). */
  262. (void) flags;
  263. duk_eval_string(ctx,
  264. "(function(cons,prot){"
  265. "Object.defineProperty(Duktape,'Logger',{value:cons,writable:true,configurable:true});"
  266. "Object.defineProperty(cons,'prototype',{value:prot});"
  267. "Object.defineProperty(cons,'clog',{value:new Duktape.Logger('C'),writable:true,configurable:true});"
  268. "});");
  269. duk_push_c_function(ctx, duk__logger_constructor, DUK_VARARGS /*nargs*/); /* Duktape.Logger */
  270. duk_push_object(ctx); /* Duktape.Logger.prototype */
  271. /* [ ... func Duktape.Logger Duktape.Logger.prototype ] */
  272. duk_push_string(ctx, "name");
  273. duk_push_string(ctx, "Logger");
  274. duk_def_prop(ctx, -4, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
  275. duk_dup_top(ctx);
  276. duk_put_prop_string(ctx, -2, "constructor");
  277. duk_push_int(ctx, 2);
  278. duk_put_prop_string(ctx, -2, "l");
  279. duk_push_string(ctx, "anon");
  280. duk_put_prop_string(ctx, -2, "n");
  281. duk_push_c_function(ctx, duk__logger_prototype_fmt, 1 /*nargs*/);
  282. duk_put_prop_string(ctx, -2, "fmt");
  283. duk_push_c_function(ctx, duk__logger_prototype_raw, 1 /*nargs*/);
  284. duk_put_prop_string(ctx, -2, "raw");
  285. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  286. duk_set_magic(ctx, -1, 0); /* magic=0: trace */
  287. duk_put_prop_string(ctx, -2, "trace");
  288. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  289. duk_set_magic(ctx, -1, 1); /* magic=1: debug */
  290. duk_put_prop_string(ctx, -2, "debug");
  291. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  292. duk_set_magic(ctx, -1, 2); /* magic=2: info */
  293. duk_put_prop_string(ctx, -2, "info");
  294. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  295. duk_set_magic(ctx, -1, 3); /* magic=3: warn */
  296. duk_put_prop_string(ctx, -2, "warn");
  297. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  298. duk_set_magic(ctx, -1, 4); /* magic=4: error */
  299. duk_put_prop_string(ctx, -2, "error");
  300. duk_push_c_function(ctx, duk__logger_prototype_log_shared, DUK_VARARGS /*nargs*/);
  301. duk_set_magic(ctx, -1, 5); /* magic=5: fatal */
  302. duk_put_prop_string(ctx, -2, "fatal");
  303. /* [ ... func Duktape.Logger Duktape.Logger.prototype ] */
  304. /* XXX: when using ROM built-ins, "Duktape" is read-only by default so
  305. * setting Duktape.Logger will now fail.
  306. */
  307. /* [ ... func Duktape.Logger Duktape.Logger.prototype ] */
  308. duk_call(ctx, 2);
  309. duk_pop(ctx);
  310. }