From f9ddc61d070a35e5df82a5a3d700474e7c336176 Mon Sep 17 00:00:00 2001 From: leak4mk0 Date: Fri, 26 Apr 2019 00:02:51 +0900 Subject: [PATCH] feat(connectLogger): add response to context --- docs/connect-logger.md | 20 ++++++ lib/connect-logger.js | 3 + test/tap/connect-context-test.js | 112 +++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 test/tap/connect-context-test.js diff --git a/docs/connect-logger.md b/docs/connect-logger.md index 7ab4329..a906805 100644 --- a/docs/connect-logger.md +++ b/docs/connect-logger.md @@ -73,6 +73,26 @@ The log4js.connectLogger also supports a nolog option where you can specify a st app.use(log4js.connectLogger(logger, { level: 'auto', format: ':method :url', nolog: '\\.gif|\\.jpg$' })); ``` +The log4js.connectLogger can add a response of express to context if `context` flag is set to `true`. +Application can use it in layouts or appenders. + +In application: + +```javascript +app.use(log4js.connectLogger(logger, { context: true })); +``` + +In layout: + +```javascript +log4js.addLayout('customLayout', () => { + return (loggingEvent) => { + const res = loggingEvent.context.res; + return util.format(...loggingEvent.data, res ? `status: ${res.statusCode}` : ''); + }; +}); +``` + ## Example nolog values | nolog value | Will Not Log | Will Log | diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 328ce5b..1cc4de0 100755 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -205,6 +205,7 @@ function matchRules(statusCode, currentLevel, ruleSet) { * - `level` A log4js levels instance. Supports also 'auto' * - `nolog` A string or RegExp to exclude target logs * - `statusRules` A array of rules for setting specific logging levels base on status codes + * - `context` Whether to add a response of express to the context * * Tokens: * @@ -274,12 +275,14 @@ module.exports = function getLogger(logger4js, options) { const combinedTokens = assembleTokens(req, res, options.tokens || []); + if (options.context) thisLogger.addContext('res', res); if (typeof fmt === 'function') { const line = fmt(req, res, str => format(str, combinedTokens)); if (line) thisLogger.log(level, line); } else { thisLogger.log(level, format(fmt, combinedTokens)); } + if (options.context) thisLogger.removeContext('res'); }); } diff --git a/test/tap/connect-context-test.js b/test/tap/connect-context-test.js new file mode 100644 index 0000000..2b52661 --- /dev/null +++ b/test/tap/connect-context-test.js @@ -0,0 +1,112 @@ +'use strict'; + +const test = require('tap').test; +const EE = require('events').EventEmitter; +const levels = require('../../lib/levels'); + +class MockLogger { + constructor() { + this.level = levels.TRACE; + this.context = {}; + this.contexts = []; + } + + log() { + this.contexts.push(Object.assign({}, this.context)); + } + + isLevelEnabled(level) { + return level.isGreaterThanOrEqualTo(this.level); + } + + addContext(key, value) { + this.context[key] = value; + } + + removeContext(key) { + delete this.context[key]; + } +} + +function MockRequest(remoteAddr, method, originalUrl) { + this.socket = { remoteAddress: remoteAddr }; + this.originalUrl = originalUrl; + this.method = method; + this.httpVersionMajor = '5'; + this.httpVersionMinor = '0'; + this.headers = {}; +} + +class MockResponse extends EE { + constructor(code) { + super(); + this.statusCode = code; + this.cachedHeaders = {}; + } + + end() { + this.emit('finish'); + } + + setHeader(key, value) { + this.cachedHeaders[key.toLowerCase()] = value; + } + + getHeader(key) { + return this.cachedHeaders[key.toLowerCase()]; + } + + writeHead(code /* , headers */) { + this.statusCode = code; + } +} + +test('log4js connect logger', (batch) => { + const clm = require('../../lib/connect-logger'); + + batch.test('with context config', (t) => { + const ml = new MockLogger(); + const cl = clm(ml, { context: true }); + + t.beforeEach((done) => { ml.contexts = []; done(); }); + + t.test('response should be included in context', (assert) => { + const contexts = ml.contexts; + const req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif + const res = new MockResponse(200); + cl(req, res, () => { }); + res.end('chunk', 'encoding'); + + assert.type(contexts, 'Array'); + assert.equal(contexts.length, 1); + assert.type(contexts[0].res, MockResponse); + assert.end(); + }); + + t.end(); + }); + + batch.test('without context config', (t) => { + const ml = new MockLogger(); + const cl = clm(ml, { }); + + t.beforeEach((done) => { ml.contexts = []; done(); }); + + t.test('response should not be included in context', (assert) => { + const contexts = ml.contexts; + const req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif + const res = new MockResponse(200); + cl(req, res, () => { }); + res.end('chunk', 'encoding'); + + assert.type(contexts, 'Array'); + assert.equal(contexts.length, 1); + assert.type(contexts[0].res, undefined); + assert.end(); + }); + + t.end(); + }); + + batch.end(); +});