Merge branch 'v1.3-dev' as v1.3.6
[websub-hub] / src / logger.js
1 'use strict';
2
3 /**
4 * Log as JSON to stdout/stderr.
5 */
6
7 const common = require('./common');
8
9 // This is uncomfortable, but is the simplest way to let logging work for BigInts.
10 // TODO: revisit with better solution
11 BigInt.prototype.toJSON = function() {
12 return this.toString();
13 }
14
15 // Also uncomfortable, but let us log Errors reasonably.
16 Object.defineProperty(Error.prototype, 'toJSON', {
17 configurable: true,
18 writable: true, // Required to let Axios override on its own Errors
19 value: function () {
20 const result = {};
21 const dupKey = function (key) {
22 // eslint-disable-next-line security/detect-object-injection
23 result[key] = this[key];
24 };
25 Object.getOwnPropertyNames(this)
26 // .filter((prop) => !(prop in []))
27 .forEach(dupKey, this);
28 return result;
29 },
30 });
31
32 class Logger {
33 /**
34 * Wrap backend calls with payload normalization.
35 * @param {Object} options
36 * @param {*} backend Console style interface
37 * @param {Object} options.logger
38 * @param {String} options.logger.ignoreBelowLevel minimum level to log
39 * @param {String} options.nodeId
40 */
41 constructor(options, backend = console) {
42 const logLevels = Object.keys(common.nullLogger);
43 const ignoreBelowLevel = options && options.logger && options.logger.ignoreBelowLevel || 'debug';
44 this.nodeId = options.nodeId;
45
46 if (!logLevels.includes(ignoreBelowLevel)) {
47 throw new RangeError(`unrecognized minimum log level '${ignoreBelowLevel}'`);
48 }
49 const ignoreLevelIdx = logLevels.indexOf(ignoreBelowLevel);
50 logLevels.forEach((level) => {
51 // eslint-disable-next-line security/detect-object-injection
52 this[level] = (logLevels.indexOf(level) > ignoreLevelIdx) ?
53 common.nop :
54 this.levelTemplateFn(backend, level);
55 });
56 }
57
58 levelTemplateFn(backend, level) {
59 // eslint-disable-next-line security/detect-object-injection
60 if (!(level in backend) || typeof backend[level] !== 'function') {
61 return common.nop;
62 }
63
64 // eslint-disable-next-line security/detect-object-injection
65 return (...args) => backend[level](this.payload(level, ...args));
66 }
67
68 payload(level, scope, message, data, ...other) {
69 // Try to keep credentials out of logs.
70 // This approach feels sort of jank, but it's better than nothing, for now.
71 if (data && data.ctx && data.ctx.parsedBody && data.ctx.parsedBody.credential) {
72 // Create copy of data
73 data = JSON.parse(JSON.stringify(data));
74 data.ctx.parsedBody.credential = '*'.repeat(data.ctx.parsedBody.credential.length);
75 }
76
77 const now = new Date();
78 return JSON.stringify({
79 nodeId: this.nodeId,
80 timestamp: now.toISOString(),
81 timestampMs: now.getTime(),
82 level: level,
83 scope: scope || '[unknown]',
84 message: message || '',
85 data: data || {},
86 ...(other.length && { other }),
87 });
88 }
89 }
90
91 module.exports = Logger;