diff --git a/docs/connect-logger.md b/docs/connect-logger.md index 88d2ad0..7ab4329 100644 --- a/docs/connect-logger.md +++ b/docs/connect-logger.md @@ -30,6 +30,7 @@ The log4js.connectLogger supports the passing of an options object that can be u - log level - log format string or function (the same as the connect/express logger) - nolog expressions (represented as a string, regexp, or array) +- status code rulesets For example: @@ -57,6 +58,15 @@ Added automatic level detection to connect-logger, depends on http status respon app.use(log4js.connectLogger(logger, { level: 'auto' })); ``` +The levels of returned status codes can be configured via status code rulesets. + +```javascript +app.use(log4js.connectLogger(logger, { level: 'auto', statusRules: [ + { from: 200, to: 299, level: 'debug' }, + { codes: [303, 304], level: 'info' } +]})); +``` + The log4js.connectLogger also supports a nolog option where you can specify a string, regexp, or array to omit certain log messages. Example of 1.2 below. ```javascript diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 3a314de..c250ac0 100755 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -165,6 +165,42 @@ function createNoLogCondition(nolog) { return regexp; } +/** + * Allows users to define rules around status codes to assign them to a specific + * logging level. + * There are two types of rules: + * - RANGE: matches a code within a certain range + * E.g. { 'from': 200, 'to': 299, 'level': 'info' } + * - CONTAINS: matches a code to a set of expected codes + * E.g. { 'codes': [200, 203], 'level': 'debug' } + * Note*: Rules are respected only in order of prescendence. + * + * @param {Number} statusCode + * @param {Level} currentLevel + * @param {Object} ruleSet + * @return {Level} + * @api private + */ +function matchRules(statusCode, currentLevel, ruleSet) { + let level = currentLevel; + + if (ruleSet) { + const matchedRule = ruleSet.find((rule) => { + let ruleMatched = false; + if (rule.from && rule.to) { + ruleMatched = statusCode >= rule.from && statusCode <= rule.to; + } else if (rule.codes) { + ruleMatched = rule.codes.indexOf(statusCode) !== -1; + } + return ruleMatched; + }); + if (matchedRule) { + level = levels.getLevel(matchedRule.level, level); + } + } + return level; +} + /** * Log requests with the given `options` or a `format` string. * @@ -173,6 +209,7 @@ function createNoLogCondition(nolog) { * - `format` Format string, see below for tokens * - `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 * * Tokens: * @@ -238,6 +275,7 @@ module.exports = function getLogger(logger4js, options) { } else { level = levels.getLevel(options.level, levels.INFO); } + level = matchRules(code, level, options.statusRules); }; // hook on end request to emit the log entry of the HTTP request. @@ -249,6 +287,7 @@ module.exports = function getLogger(logger4js, options) { if (res.statusCode >= 300) level = levels.WARN; if (res.statusCode >= 400) level = levels.ERROR; } + level = matchRules(res.statusCode, level, options.statusRules); if (thisLogger.isLevelEnabled(level)) { const combinedTokens = assembleTokens(req, res, options.tokens || []); diff --git a/test/tap/connect-logger-test.js b/test/tap/connect-logger-test.js index ead71de..611a7f5 100644 --- a/test/tap/connect-logger-test.js +++ b/test/tap/connect-logger-test.js @@ -158,6 +158,47 @@ test('log4js connect logger', (batch) => { t.end(); }); + batch.test('logger with status code rules applied', (t) => { + const ml = new MockLogger(); + ml.level = levels.DEBUG; + const clr = [ + { codes: [201, 304], level: levels.DEBUG.toString() }, + { from: 200, to: 299, level: levels.DEBUG.toString() }, + { from: 300, to: 399, level: levels.INFO.toString() } + ]; + const cl = clm(ml, { level: 'auto', format: ':method :url', statusRules: clr }); + request(cl, 'GET', 'http://meh', 200); + request(cl, 'GET', 'http://meh', 201); + request(cl, 'GET', 'http://meh', 302); + request(cl, 'GET', 'http://meh', 304); + request(cl, 'GET', 'http://meh', 404); + request(cl, 'GET', 'http://meh', 500); + + const messages = ml.messages; + t.test('should use DEBUG for 2xx', (assert) => { + assert.ok(levels.DEBUG.isEqualTo(messages[0].level)); + assert.ok(levels.DEBUG.isEqualTo(messages[1].level)); + assert.end(); + }); + + t.test('should use WARN for 3xx, DEBUG for 304', (assert) => { + assert.ok(levels.INFO.isEqualTo(messages[2].level)); + assert.ok(levels.DEBUG.isEqualTo(messages[3].level)); + assert.end(); + }); + + t.test('should use ERROR for 4xx', (assert) => { + assert.ok(levels.ERROR.isEqualTo(messages[4].level)); + assert.end(); + }); + + t.test('should use ERROR for 5xx', (assert) => { + assert.ok(levels.ERROR.isEqualTo(messages[5].level)); + assert.end(); + }); + t.end(); + }); + batch.test('format using a function', (t) => { const ml = new MockLogger(); ml.level = levels.INFO; diff --git a/types/log4js.d.ts b/types/log4js.d.ts index 83f9a21..7ae36f5 100644 --- a/types/log4js.d.ts +++ b/types/log4js.d.ts @@ -19,7 +19,7 @@ export function configure(config: Configuration): Log4js; export function addLayout(name: string, config: (a: any) => (logEvent: LoggingEvent) => string): void; -export function connectLogger(logger: Logger, options: { format?: Format; level?: string; nolog?: any; }): any; // express.Handler; +export function connectLogger(logger: Logger, options: { format?: Format; level?: string; nolog?: any; statusRules?: any[] }): any; // express.Handler; export const levels: Levels;