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.

@@ -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
+ };