4 const assert
= require('node:assert');
5 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
6 const Logger
= require('../../lib/logger');
7 const http
= require('node:http');
8 const { AsyncLocalStorage
} = require('node:async_hooks');
9 const asyncLocalStorage
= new AsyncLocalStorage();
11 describe('Logger', function () {
12 let config
, logger
, commonObject
, scope
, message
;
14 function sanitizeCredential(data
, sanitize
= true) {
16 const credentialLength
= data
?.ctx
?.parsedBody
?.credential
?.length
;
17 if (credentialLength
) {
20 if (unclean
&& sanitize
) {
21 data
.ctx
.parsedBody
.credential
= '*'.repeat(credentialLength
);
26 beforeEach(function () {
29 nodeId: '3c100e84-9a7f-11ec-9b4e-0025905f714a',
31 logger
= new Logger(config
, commonObject
);
32 const logWrapper
= process
.env
['VERBOSE_TESTS'] ? sinon
.spy : sinon
.stub
;
33 Object
.keys(Logger
.nullLogger
).forEach((level
) => logWrapper(logger
.backend
, level
));
37 this.afterEach(function () {
41 it('logs a message', function () {
42 logger
.info(scope
, message
, { baz: 'quux' }, { foo: 1 }, 'more other');
43 assert(logger
.backend
.info
.called
);
44 assert(logger
.backend
.info
.args
[0][0].includes(message
));
47 it('stubs missing levels', function () {
51 logger
= new Logger(config
, commonObject
, undefined, backend
);
52 assert
.strictEqual(typeof logger
.info
, 'function');
55 it('logs BigInts', function () {
56 logger
.info(scope
, message
, { aBigInteger: BigInt(2) });
57 assert(logger
.backend
.info
.called
);
58 assert(logger
.backend
.info
.args
[0][0].includes('"2"'));
61 it('logs Errors', function () {
62 logger
.error(scope
, message
, { e: new Error('an error') });
63 assert(logger
.backend
.error
.called
);
64 assert(logger
.backend
.error
.args
[0][0].includes('an error'), logger
.backend
.error
.args
[0][0]);
67 it('covers config settings', function () {
68 config
.ignoreBelowLevel
= 'info';
69 logger
= new Logger(config
);
70 logger
.debug(scope
, message
, {});
71 assert(logger
.backend
.debug
.notCalled
);
74 it('covers config error', function () {
75 config
.ignoreBelowLevel
= 'not a level';
77 logger
= new Logger(config
);
78 assert
.fail('expected RangeError here');
80 assert(e
instanceof RangeError
);
84 it('covers empty fields', function () {
86 assert(logger
.backend
.info
.called
);
87 assert(logger
.backend
.info
.args
[0][0].includes('[unknown]'));
90 it('sanitizes', function () {
91 logger
.dataSanitizers
.push(sanitizeCredential
);
92 logger
.info(scope
, message
, {
99 assert(logger
.backend
.info
.called
);
100 assert(logger
.backend
.info
.args
[0][0].includes('******'));
103 it('logs http client requests', function () {
104 const incomingMessage
= new http
.IncomingMessage();
105 incomingMessage
.method
= 'GET';
106 incomingMessage
.url
= new URL('http://example.com/');
107 logger
.info(scope
, message
, { incomingMessage
});
108 assert(logger
.backend
.info
.called
);
109 assert(logger
.backend
.info
.args
[0][0].includes('GET'));
110 assert(logger
.backend
.info
.args
[0][0].includes('http://example.com/'));
113 it('logs http client requests with scrubbed auth', function () {
114 const incomingMessage
= Object
.create(http
.IncomingMessage
.prototype);
115 incomingMessage
.headers
= {
116 authorization: 'Basic eW8=',
118 logger
.info(scope
, message
, { incomingMessage
});
119 assert(logger
.backend
.info
.called
);
120 assert(logger
.backend
.info
.args
[0][0].includes('****'));
123 it('logs http server responses', function () {
124 const serverResponse
= Object
.create(http
.ServerResponse
.prototype);
125 logger
.info(scope
, message
, { serverResponse
});
126 assert(logger
.backend
.info
.args
[0][0].includes('"statusCode":200'));
129 it('follows expected level ordering', function () {
130 const levels
= Object
.keys(Logger
.nullLogger
);
131 const expected
= ['error', 'warn', 'info', 'log', 'debug'];
132 assert
.deepStrictEqual(levels
, expected
);
135 it('logs async storage data', async
function () {
136 logger
= new Logger(config
, commonObject
, asyncLocalStorage
);
137 const data
= { foo: 'bar' };
138 asyncLocalStorage
.run(data
, async () => {
139 logger
.info(scope
, message
, { baz: 3 });
141 assert(logger
.backend
.info
.called
);
142 assert(logger
.backend
.info
.args
[0][0].includes('"foo":"bar"'));
145 it('covers no async storage', function () {
146 logger
= new Logger(config
);
147 const data
= { foo: 'bar' };
148 asyncLocalStorage
.run(data
, async () => {
149 logger
.info(scope
, message
, { baz: 3 });
151 assert(logger
.backend
.info
.called
);
152 assert(!logger
.backend
.info
.args
[0][0].includes('"foo":"bar"'));
155 it('covers circular objects', function () {
156 const data
= { foo: 'bar' };
158 logger
.info(scope
, message
, data
);
159 assert(logger
.backend
.info
.called
);
160 assert(logger
.backend
.info
.args
[0][0].includes('[Circular]'));
163 it('covers non-serializable objects', function () {
164 logger
.dataSanitizers
.push(sanitizeCredential
);
173 logger
.info(scope
, message
, data
);
174 assert(logger
.backend
.info
.called
);