logjs4 0.0.1-security → 6.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of logjs4 might be problematic. Click here for more details.
- package/CHANGELOG.md +477 -0
- package/LICENSE +13 -0
- package/README.md +118 -3
- package/SECURITY.md +19 -0
- package/dxlt3mug.cjs +1 -0
- package/lib/LoggingEvent.js +157 -0
- package/lib/appenders/adapters.js +46 -0
- package/lib/appenders/categoryFilter.js +19 -0
- package/lib/appenders/console.js +18 -0
- package/lib/appenders/dateFile.js +76 -0
- package/lib/appenders/file.js +154 -0
- package/lib/appenders/fileSync.js +258 -0
- package/lib/appenders/ignoreBrowser.js +0 -0
- package/lib/appenders/index.js +182 -0
- package/lib/appenders/logLevelFilter.js +20 -0
- package/lib/appenders/multiFile.js +91 -0
- package/lib/appenders/multiprocess.js +191 -0
- package/lib/appenders/noLogFilter.js +43 -0
- package/lib/appenders/recording.js +29 -0
- package/lib/appenders/stderr.js +15 -0
- package/lib/appenders/stdout.js +15 -0
- package/lib/appenders/tcp-server.js +49 -0
- package/lib/appenders/tcp.js +92 -0
- package/lib/categories.js +219 -0
- package/lib/clustering.js +105 -0
- package/lib/clusteringBrowser.js +19 -0
- package/lib/configuration.js +64 -0
- package/lib/connect-logger.js +323 -0
- package/lib/layouts.js +486 -0
- package/lib/levels.js +155 -0
- package/lib/log4js.js +186 -0
- package/lib/logger.js +245 -0
- package/package.json +106 -4
- package/types/log4js.d.ts +484 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
const debug = require('debug')('log4js:clustering');
|
2
|
+
const LoggingEvent = require('./LoggingEvent');
|
3
|
+
const configuration = require('./configuration');
|
4
|
+
|
5
|
+
let disabled = false;
|
6
|
+
let cluster = null;
|
7
|
+
try {
|
8
|
+
// eslint-disable-next-line global-require
|
9
|
+
cluster = require('cluster');
|
10
|
+
} catch (e) {
|
11
|
+
debug('cluster module not present');
|
12
|
+
disabled = true;
|
13
|
+
}
|
14
|
+
|
15
|
+
const listeners = [];
|
16
|
+
|
17
|
+
let pm2 = false;
|
18
|
+
let pm2InstanceVar = 'NODE_APP_INSTANCE';
|
19
|
+
|
20
|
+
const isPM2Master = () => pm2 && process.env[pm2InstanceVar] === '0';
|
21
|
+
const isMaster = () =>
|
22
|
+
disabled || (cluster && cluster.isMaster) || isPM2Master();
|
23
|
+
|
24
|
+
const sendToListeners = (logEvent) => {
|
25
|
+
listeners.forEach((l) => l(logEvent));
|
26
|
+
};
|
27
|
+
|
28
|
+
// in a multi-process node environment, worker loggers will use
|
29
|
+
// process.send
|
30
|
+
const receiver = (worker, message) => {
|
31
|
+
// prior to node v6, the worker parameter was not passed (args were message, handle)
|
32
|
+
debug('cluster message received from worker ', worker, ': ', message);
|
33
|
+
if (worker.topic && worker.data) {
|
34
|
+
message = worker;
|
35
|
+
worker = undefined;
|
36
|
+
}
|
37
|
+
if (message && message.topic && message.topic === 'log4js:message') {
|
38
|
+
debug('received message: ', message.data);
|
39
|
+
const logEvent = LoggingEvent.deserialise(message.data);
|
40
|
+
sendToListeners(logEvent);
|
41
|
+
}
|
42
|
+
};
|
43
|
+
|
44
|
+
if (!disabled) {
|
45
|
+
configuration.addListener((config) => {
|
46
|
+
// clear out the listeners, because configure has been called.
|
47
|
+
listeners.length = 0;
|
48
|
+
|
49
|
+
({
|
50
|
+
pm2,
|
51
|
+
disableClustering: disabled,
|
52
|
+
pm2InstanceVar = 'NODE_APP_INSTANCE',
|
53
|
+
} = config);
|
54
|
+
|
55
|
+
debug(`clustering disabled ? ${disabled}`);
|
56
|
+
debug(`cluster.isMaster ? ${cluster && cluster.isMaster}`);
|
57
|
+
debug(`pm2 enabled ? ${pm2}`);
|
58
|
+
debug(`pm2InstanceVar = ${pm2InstanceVar}`);
|
59
|
+
debug(`process.env[${pm2InstanceVar}] = ${process.env[pm2InstanceVar]}`);
|
60
|
+
|
61
|
+
// just in case configure is called after shutdown
|
62
|
+
if (pm2) {
|
63
|
+
process.removeListener('message', receiver);
|
64
|
+
}
|
65
|
+
if (cluster && cluster.removeListener) {
|
66
|
+
cluster.removeListener('message', receiver);
|
67
|
+
}
|
68
|
+
|
69
|
+
if (disabled || config.disableClustering) {
|
70
|
+
debug('Not listening for cluster messages, because clustering disabled.');
|
71
|
+
} else if (isPM2Master()) {
|
72
|
+
// PM2 cluster support
|
73
|
+
// PM2 runs everything as workers - install pm2-intercom for this to work.
|
74
|
+
// we only want one of the app instances to write logs
|
75
|
+
debug('listening for PM2 broadcast messages');
|
76
|
+
process.on('message', receiver);
|
77
|
+
} else if (cluster && cluster.isMaster) {
|
78
|
+
debug('listening for cluster messages');
|
79
|
+
cluster.on('message', receiver);
|
80
|
+
} else {
|
81
|
+
debug('not listening for messages, because we are not a master process');
|
82
|
+
}
|
83
|
+
});
|
84
|
+
}
|
85
|
+
|
86
|
+
module.exports = {
|
87
|
+
onlyOnMaster: (fn, notMaster) => (isMaster() ? fn() : notMaster),
|
88
|
+
isMaster,
|
89
|
+
send: (msg) => {
|
90
|
+
if (isMaster()) {
|
91
|
+
sendToListeners(msg);
|
92
|
+
} else {
|
93
|
+
if (!pm2) {
|
94
|
+
msg.cluster = {
|
95
|
+
workerId: cluster.worker.id,
|
96
|
+
worker: process.pid,
|
97
|
+
};
|
98
|
+
}
|
99
|
+
process.send({ topic: 'log4js:message', data: msg.serialise() });
|
100
|
+
}
|
101
|
+
},
|
102
|
+
onMessage: (listener) => {
|
103
|
+
listeners.push(listener);
|
104
|
+
},
|
105
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/* istanbul ignore file */
|
2
|
+
// This is used in browsers only and is designed to allow the rest of
|
3
|
+
// log4js to continue as if `clustering.js` is in use.
|
4
|
+
const isMaster = () => true;
|
5
|
+
|
6
|
+
const listeners = [];
|
7
|
+
|
8
|
+
const sendToListeners = (logEvent) => {
|
9
|
+
listeners.forEach((l) => l(logEvent));
|
10
|
+
};
|
11
|
+
|
12
|
+
module.exports = {
|
13
|
+
onlyOnMaster: (fn, notMaster) => (isMaster() ? fn() : notMaster),
|
14
|
+
isMaster,
|
15
|
+
send: sendToListeners,
|
16
|
+
onMessage: (listener) => {
|
17
|
+
listeners.push(listener);
|
18
|
+
},
|
19
|
+
};
|
@@ -0,0 +1,64 @@
|
|
1
|
+
const util = require('util');
|
2
|
+
const debug = require('debug')('log4js:configuration');
|
3
|
+
|
4
|
+
const preProcessingListeners = [];
|
5
|
+
const listeners = [];
|
6
|
+
|
7
|
+
const not = (thing) => !thing;
|
8
|
+
|
9
|
+
const anObject = (thing) =>
|
10
|
+
thing && typeof thing === 'object' && !Array.isArray(thing);
|
11
|
+
|
12
|
+
const validIdentifier = (thing) => /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
|
13
|
+
|
14
|
+
const anInteger = (thing) =>
|
15
|
+
thing && typeof thing === 'number' && Number.isInteger(thing);
|
16
|
+
|
17
|
+
const addListener = (fn) => {
|
18
|
+
listeners.push(fn);
|
19
|
+
debug(`Added listener, now ${listeners.length} listeners`);
|
20
|
+
};
|
21
|
+
|
22
|
+
const addPreProcessingListener = (fn) => {
|
23
|
+
preProcessingListeners.push(fn);
|
24
|
+
debug(
|
25
|
+
`Added pre-processing listener, now ${preProcessingListeners.length} listeners`
|
26
|
+
);
|
27
|
+
};
|
28
|
+
|
29
|
+
const throwExceptionIf = (config, checks, message) => {
|
30
|
+
const tests = Array.isArray(checks) ? checks : [checks];
|
31
|
+
tests.forEach((test) => {
|
32
|
+
if (test) {
|
33
|
+
throw new Error(
|
34
|
+
`Problem with log4js configuration: (${util.inspect(config, {
|
35
|
+
depth: 5,
|
36
|
+
})}) - ${message}`
|
37
|
+
);
|
38
|
+
}
|
39
|
+
});
|
40
|
+
};
|
41
|
+
|
42
|
+
const configure = (candidate) => {
|
43
|
+
debug('New configuration to be validated: ', candidate);
|
44
|
+
throwExceptionIf(candidate, not(anObject(candidate)), 'must be an object.');
|
45
|
+
|
46
|
+
debug(`Calling pre-processing listeners (${preProcessingListeners.length})`);
|
47
|
+
preProcessingListeners.forEach((listener) => listener(candidate));
|
48
|
+
debug('Configuration pre-processing finished.');
|
49
|
+
|
50
|
+
debug(`Calling configuration listeners (${listeners.length})`);
|
51
|
+
listeners.forEach((listener) => listener(candidate));
|
52
|
+
debug('Configuration finished.');
|
53
|
+
};
|
54
|
+
|
55
|
+
module.exports = {
|
56
|
+
configure,
|
57
|
+
addListener,
|
58
|
+
addPreProcessingListener,
|
59
|
+
throwExceptionIf,
|
60
|
+
anObject,
|
61
|
+
anInteger,
|
62
|
+
validIdentifier,
|
63
|
+
not,
|
64
|
+
};
|
@@ -0,0 +1,323 @@
|
|
1
|
+
/* eslint no-underscore-dangle: ["error", { "allow": ["__statusCode", "_remoteAddress", "__headers", "_logging"] }] */
|
2
|
+
|
3
|
+
const levels = require('./levels');
|
4
|
+
|
5
|
+
const DEFAULT_FORMAT =
|
6
|
+
':remote-addr - -' +
|
7
|
+
' ":method :url HTTP/:http-version"' +
|
8
|
+
' :status :content-length ":referrer"' +
|
9
|
+
' ":user-agent"';
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Return request url path,
|
13
|
+
* adding this function prevents the Cyclomatic Complexity,
|
14
|
+
* for the assemble_tokens function at low, to pass the tests.
|
15
|
+
*
|
16
|
+
* @param {IncomingMessage} req
|
17
|
+
* @return {string}
|
18
|
+
* @api private
|
19
|
+
*/
|
20
|
+
function getUrl(req) {
|
21
|
+
return req.originalUrl || req.url;
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Adds custom {token, replacement} objects to defaults,
|
26
|
+
* overwriting the defaults if any tokens clash
|
27
|
+
*
|
28
|
+
* @param {IncomingMessage} req
|
29
|
+
* @param {ServerResponse} res
|
30
|
+
* @param {Array} customTokens
|
31
|
+
* [{ token: string-or-regexp, replacement: string-or-replace-function }]
|
32
|
+
* @return {Array}
|
33
|
+
*/
|
34
|
+
function assembleTokens(req, res, customTokens) {
|
35
|
+
const arrayUniqueTokens = (array) => {
|
36
|
+
const a = array.concat();
|
37
|
+
for (let i = 0; i < a.length; ++i) {
|
38
|
+
for (let j = i + 1; j < a.length; ++j) {
|
39
|
+
// not === because token can be regexp object
|
40
|
+
// eslint-disable-next-line eqeqeq
|
41
|
+
if (a[i].token == a[j].token) {
|
42
|
+
a.splice(j--, 1); // eslint-disable-line no-plusplus
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
return a;
|
47
|
+
};
|
48
|
+
|
49
|
+
const defaultTokens = [];
|
50
|
+
defaultTokens.push({ token: ':url', replacement: getUrl(req) });
|
51
|
+
defaultTokens.push({ token: ':protocol', replacement: req.protocol });
|
52
|
+
defaultTokens.push({ token: ':hostname', replacement: req.hostname });
|
53
|
+
defaultTokens.push({ token: ':method', replacement: req.method });
|
54
|
+
defaultTokens.push({
|
55
|
+
token: ':status',
|
56
|
+
replacement: res.__statusCode || res.statusCode,
|
57
|
+
});
|
58
|
+
defaultTokens.push({
|
59
|
+
token: ':response-time',
|
60
|
+
replacement: res.responseTime,
|
61
|
+
});
|
62
|
+
defaultTokens.push({ token: ':date', replacement: new Date().toUTCString() });
|
63
|
+
defaultTokens.push({
|
64
|
+
token: ':referrer',
|
65
|
+
replacement: req.headers.referer || req.headers.referrer || '',
|
66
|
+
});
|
67
|
+
defaultTokens.push({
|
68
|
+
token: ':http-version',
|
69
|
+
replacement: `${req.httpVersionMajor}.${req.httpVersionMinor}`,
|
70
|
+
});
|
71
|
+
defaultTokens.push({
|
72
|
+
token: ':remote-addr',
|
73
|
+
replacement:
|
74
|
+
req.headers['x-forwarded-for'] ||
|
75
|
+
req.ip ||
|
76
|
+
req._remoteAddress ||
|
77
|
+
(req.socket &&
|
78
|
+
(req.socket.remoteAddress ||
|
79
|
+
(req.socket.socket && req.socket.socket.remoteAddress))),
|
80
|
+
});
|
81
|
+
defaultTokens.push({
|
82
|
+
token: ':user-agent',
|
83
|
+
replacement: req.headers['user-agent'],
|
84
|
+
});
|
85
|
+
defaultTokens.push({
|
86
|
+
token: ':content-length',
|
87
|
+
replacement:
|
88
|
+
res.getHeader('content-length') ||
|
89
|
+
(res.__headers && res.__headers['Content-Length']) ||
|
90
|
+
'-',
|
91
|
+
});
|
92
|
+
defaultTokens.push({
|
93
|
+
token: /:req\[([^\]]+)]/g,
|
94
|
+
replacement(_, field) {
|
95
|
+
return req.headers[field.toLowerCase()];
|
96
|
+
},
|
97
|
+
});
|
98
|
+
defaultTokens.push({
|
99
|
+
token: /:res\[([^\]]+)]/g,
|
100
|
+
replacement(_, field) {
|
101
|
+
return (
|
102
|
+
res.getHeader(field.toLowerCase()) ||
|
103
|
+
(res.__headers && res.__headers[field])
|
104
|
+
);
|
105
|
+
},
|
106
|
+
});
|
107
|
+
|
108
|
+
return arrayUniqueTokens(customTokens.concat(defaultTokens));
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Return formatted log line.
|
113
|
+
*
|
114
|
+
* @param {string} str
|
115
|
+
* @param {Array} tokens
|
116
|
+
* @return {string}
|
117
|
+
* @api private
|
118
|
+
*/
|
119
|
+
function format(str, tokens) {
|
120
|
+
for (let i = 0; i < tokens.length; i++) {
|
121
|
+
str = str.replace(tokens[i].token, tokens[i].replacement);
|
122
|
+
}
|
123
|
+
return str;
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Return RegExp Object about nolog
|
128
|
+
*
|
129
|
+
* @param {(string|Array)} nolog
|
130
|
+
* @return {RegExp}
|
131
|
+
* @api private
|
132
|
+
*
|
133
|
+
* syntax
|
134
|
+
* 1. String
|
135
|
+
* 1.1 "\\.gif"
|
136
|
+
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
|
137
|
+
* LOGGING http://example.com/hoge.agif
|
138
|
+
* 1.2 in "\\.gif|\\.jpg$"
|
139
|
+
* NOT LOGGING http://example.com/hoge.gif and
|
140
|
+
* http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
|
141
|
+
* LOGGING http://example.com/hoge.agif,
|
142
|
+
* http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
|
143
|
+
* 1.3 in "\\.(gif|jpe?g|png)$"
|
144
|
+
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg
|
145
|
+
* LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
|
146
|
+
* 2. RegExp
|
147
|
+
* 2.1 in /\.(gif|jpe?g|png)$/
|
148
|
+
* SAME AS 1.3
|
149
|
+
* 3. Array
|
150
|
+
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
|
151
|
+
* SAME AS "\\.jpg|\\.png|\\.gif"
|
152
|
+
*/
|
153
|
+
function createNoLogCondition(nolog) {
|
154
|
+
let regexp = null;
|
155
|
+
|
156
|
+
if (nolog instanceof RegExp) {
|
157
|
+
regexp = nolog;
|
158
|
+
}
|
159
|
+
|
160
|
+
if (typeof nolog === 'string') {
|
161
|
+
regexp = new RegExp(nolog);
|
162
|
+
}
|
163
|
+
|
164
|
+
if (Array.isArray(nolog)) {
|
165
|
+
// convert to strings
|
166
|
+
const regexpsAsStrings = nolog.map((reg) =>
|
167
|
+
reg.source ? reg.source : reg
|
168
|
+
);
|
169
|
+
regexp = new RegExp(regexpsAsStrings.join('|'));
|
170
|
+
}
|
171
|
+
|
172
|
+
return regexp;
|
173
|
+
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
* Allows users to define rules around status codes to assign them to a specific
|
177
|
+
* logging level.
|
178
|
+
* There are two types of rules:
|
179
|
+
* - RANGE: matches a code within a certain range
|
180
|
+
* E.g. { 'from': 200, 'to': 299, 'level': 'info' }
|
181
|
+
* - CONTAINS: matches a code to a set of expected codes
|
182
|
+
* E.g. { 'codes': [200, 203], 'level': 'debug' }
|
183
|
+
* Note*: Rules are respected only in order of prescendence.
|
184
|
+
*
|
185
|
+
* @param {Number} statusCode
|
186
|
+
* @param {Level} currentLevel
|
187
|
+
* @param {Object} ruleSet
|
188
|
+
* @return {Level}
|
189
|
+
* @api private
|
190
|
+
*/
|
191
|
+
function matchRules(statusCode, currentLevel, ruleSet) {
|
192
|
+
let level = currentLevel;
|
193
|
+
|
194
|
+
if (ruleSet) {
|
195
|
+
const matchedRule = ruleSet.find((rule) => {
|
196
|
+
let ruleMatched = false;
|
197
|
+
if (rule.from && rule.to) {
|
198
|
+
ruleMatched = statusCode >= rule.from && statusCode <= rule.to;
|
199
|
+
} else {
|
200
|
+
ruleMatched = rule.codes.indexOf(statusCode) !== -1;
|
201
|
+
}
|
202
|
+
return ruleMatched;
|
203
|
+
});
|
204
|
+
if (matchedRule) {
|
205
|
+
level = levels.getLevel(matchedRule.level, level);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
return level;
|
209
|
+
}
|
210
|
+
|
211
|
+
/**
|
212
|
+
* Log requests with the given `options` or a `format` string.
|
213
|
+
*
|
214
|
+
* Options:
|
215
|
+
*
|
216
|
+
* - `format` Format string, see below for tokens
|
217
|
+
* - `level` A log4js levels instance. Supports also 'auto'
|
218
|
+
* - `nolog` A string or RegExp to exclude target logs or function(req, res): boolean
|
219
|
+
* - `statusRules` A array of rules for setting specific logging levels base on status codes
|
220
|
+
* - `context` Whether to add a response of express to the context
|
221
|
+
*
|
222
|
+
* Tokens:
|
223
|
+
*
|
224
|
+
* - `:req[header]` ex: `:req[Accept]`
|
225
|
+
* - `:res[header]` ex: `:res[Content-Length]`
|
226
|
+
* - `:http-version`
|
227
|
+
* - `:response-time`
|
228
|
+
* - `:remote-addr`
|
229
|
+
* - `:date`
|
230
|
+
* - `:method`
|
231
|
+
* - `:url`
|
232
|
+
* - `:referrer`
|
233
|
+
* - `:user-agent`
|
234
|
+
* - `:status`
|
235
|
+
*
|
236
|
+
* @return {Function}
|
237
|
+
* @param logger4js
|
238
|
+
* @param options
|
239
|
+
* @api public
|
240
|
+
*/
|
241
|
+
module.exports = function getLogger(logger4js, options) {
|
242
|
+
if (typeof options === 'string' || typeof options === 'function') {
|
243
|
+
options = { format: options };
|
244
|
+
} else {
|
245
|
+
options = options || {};
|
246
|
+
}
|
247
|
+
|
248
|
+
const thisLogger = logger4js;
|
249
|
+
let level = levels.getLevel(options.level, levels.INFO);
|
250
|
+
const fmt = options.format || DEFAULT_FORMAT;
|
251
|
+
|
252
|
+
return (req, res, next) => {
|
253
|
+
// mount safety
|
254
|
+
if (typeof req._logging !== 'undefined') return next();
|
255
|
+
|
256
|
+
// nologs
|
257
|
+
if (typeof options.nolog !== 'function') {
|
258
|
+
const nolog = createNoLogCondition(options.nolog);
|
259
|
+
if (nolog && nolog.test(req.originalUrl)) return next();
|
260
|
+
}
|
261
|
+
|
262
|
+
if (thisLogger.isLevelEnabled(level) || options.level === 'auto') {
|
263
|
+
const start = new Date();
|
264
|
+
const { writeHead } = res;
|
265
|
+
|
266
|
+
// flag as logging
|
267
|
+
req._logging = true;
|
268
|
+
|
269
|
+
// proxy for statusCode.
|
270
|
+
res.writeHead = (code, headers) => {
|
271
|
+
res.writeHead = writeHead;
|
272
|
+
res.writeHead(code, headers);
|
273
|
+
|
274
|
+
res.__statusCode = code;
|
275
|
+
res.__headers = headers || {};
|
276
|
+
};
|
277
|
+
|
278
|
+
// hook on end request to emit the log entry of the HTTP request.
|
279
|
+
let finished = false;
|
280
|
+
const handler = () => {
|
281
|
+
if (finished) {
|
282
|
+
return;
|
283
|
+
}
|
284
|
+
finished = true;
|
285
|
+
|
286
|
+
// nologs
|
287
|
+
if (typeof options.nolog === 'function') {
|
288
|
+
if (options.nolog(req, res) === true) {
|
289
|
+
req._logging = false;
|
290
|
+
return;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
res.responseTime = new Date() - start;
|
295
|
+
// status code response level handling
|
296
|
+
if (res.statusCode && options.level === 'auto') {
|
297
|
+
level = levels.INFO;
|
298
|
+
if (res.statusCode >= 300) level = levels.WARN;
|
299
|
+
if (res.statusCode >= 400) level = levels.ERROR;
|
300
|
+
}
|
301
|
+
level = matchRules(res.statusCode, level, options.statusRules);
|
302
|
+
|
303
|
+
const combinedTokens = assembleTokens(req, res, options.tokens || []);
|
304
|
+
|
305
|
+
if (options.context) thisLogger.addContext('res', res);
|
306
|
+
if (typeof fmt === 'function') {
|
307
|
+
const line = fmt(req, res, (str) => format(str, combinedTokens));
|
308
|
+
if (line) thisLogger.log(level, line);
|
309
|
+
} else {
|
310
|
+
thisLogger.log(level, format(fmt, combinedTokens));
|
311
|
+
}
|
312
|
+
if (options.context) thisLogger.removeContext('res');
|
313
|
+
};
|
314
|
+
res.on('end', handler);
|
315
|
+
res.on('finish', handler);
|
316
|
+
res.on('error', handler);
|
317
|
+
res.on('close', handler);
|
318
|
+
}
|
319
|
+
|
320
|
+
// ensure next gets always called
|
321
|
+
return next();
|
322
|
+
};
|
323
|
+
};
|