zeddemore-logger 1.2.4 → 2.0.0
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.
- package/README.md +1 -1
- package/lib/ZeddemoreBase.js +283 -0
- package/lib/ZeddemoreLogger.js +814 -0
- package/lib/ZeddemoreLoggerController.js +72 -0
- package/lib/ZeddemoreProgressMultibar.js +776 -0
- package/lib/date.js +24 -1
- package/lib/defaults.js +37 -0
- package/lib/enums.js +15 -0
- package/lib/logging.js +76 -0
- package/lib/typedef.js +33 -0
- package/lib/uuid.js +11 -0
- package/lib/zeddemore-logger.js +5 -579
- package/package.json +4 -2
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const morgan = require('morgan');
|
|
6
|
+
const sizeof = require('object-sizeof');
|
|
7
|
+
const winston = require('winston');
|
|
8
|
+
|
|
9
|
+
const { areHeadersSent, isResponseSuccessful } = require('./response');
|
|
10
|
+
const { chalkHttpStatuses, chalkHttpVerbs, chalkLogLevel, chalkPackage, chalkTarget } = require('./color');
|
|
11
|
+
const { convertBytes } = require('./math');
|
|
12
|
+
const { convertMilliseconds } = require('./date');
|
|
13
|
+
const defaults = require('./defaults');
|
|
14
|
+
const enums = require('./enums');
|
|
15
|
+
const { extractToken, getRequestResponseTime, isRequestOfMethod, parseRequest } = require('./request');
|
|
16
|
+
const { generateUuid } = require('./uuid');
|
|
17
|
+
const { isMorganFormat, logLevelToWinstonLogLevelName, monkeyPatchConsole } = require('./logging');
|
|
18
|
+
require('./typedef');
|
|
19
|
+
const ZeddemoreBase = require('./ZeddemoreBase');
|
|
20
|
+
const ZeddemoreLoggerController = require('./ZeddemoreLoggerController');
|
|
21
|
+
const ZeddemoreProgressMultibar = require('./ZeddemoreProgressMultibar');
|
|
22
|
+
|
|
23
|
+
const { createLogger: createWinstonLogger, format, Logform: Format, transports } = winston;
|
|
24
|
+
const { colorize, combine, label, metadata, printf, timestamp } = format;
|
|
25
|
+
const { morganFormats: mf, morganFormatTokens: mft, httpMethods: http, httpRequestHeaders: rqh, httpResponseHeaders: rsh,
|
|
26
|
+
winstonLogLevelNames: wlln, winstonLogLevels: wll } = enums;
|
|
27
|
+
|
|
28
|
+
class ZeddemoreLogger extends ZeddemoreBase {
|
|
29
|
+
|
|
30
|
+
#apiCallSequelizeLogging = false;
|
|
31
|
+
/** @type {SequelizeModel} */
|
|
32
|
+
#apiCallSequelizeModel = null;
|
|
33
|
+
#apiRouteCategories = [ ];
|
|
34
|
+
#apiRoutes = [ ];
|
|
35
|
+
#apiVersion = null;
|
|
36
|
+
#decodeUrls = true;
|
|
37
|
+
#httpRequestLogging = true;
|
|
38
|
+
/** @type {ZeddemoreLoggerController} */
|
|
39
|
+
#logger = null;
|
|
40
|
+
#morganFormat = mf.ZEDDEMORE;
|
|
41
|
+
#routeSequelizeLogging = false;
|
|
42
|
+
/** @type {SequelizeModel} */
|
|
43
|
+
#routeSequelizeModel = null;
|
|
44
|
+
#suppressNonMutatingRequestApiCallLogging = false;
|
|
45
|
+
#suppressSuccessfulRequestApiCallLogging = false;
|
|
46
|
+
|
|
47
|
+
constructor(options) {
|
|
48
|
+
options = options || { };
|
|
49
|
+
super(options);
|
|
50
|
+
this.apiCallSequelizeLogging = options.apiCallSequelizeLogging;
|
|
51
|
+
if (_.isBoolean(options.disableApiCallLogging)) this.apiCallSequelizeLogging = !options.disableApiCallLogging; // backwards compatibility
|
|
52
|
+
this.apiCallSequelizeModel = options.apiCallSequelizeModel;
|
|
53
|
+
if (this.#isSequelizeModel(options.apiCallModel)) this.apiCallSequelizeModel = options.apiCallModel; // backwards compatibility
|
|
54
|
+
this.apiRouteCategories = options.apiRouteCategories;
|
|
55
|
+
this.apiRoutes = options.apiRoutes;
|
|
56
|
+
this.apiVersion = options.apiVersion;
|
|
57
|
+
this.decodeUrls = options.decodeUrls;
|
|
58
|
+
this.httpRequestLogging = options.httpRequestLogging;
|
|
59
|
+
if (_.isBoolean(options.disableMorganLogging )) this.httpRequestLogging = !options.disableMorganLogging; // backwards compatibility
|
|
60
|
+
this.morganFormat = options.morganFormat;
|
|
61
|
+
this.routeSequelizeLogging = options.routeSequelizeLogging;
|
|
62
|
+
if (_.isBoolean(options.disableRouteLogging)) this.routeSequelizeLogging = !options.disableRouteLogging; // backwards compatibility
|
|
63
|
+
this.routeSequelizeModel = options.routeSequelizeModel;
|
|
64
|
+
if (this.#isSequelizeModel(options.routeModel)) this.routeSequelizeModel = options.routeModel; // backwards compatibility
|
|
65
|
+
this.suppressNonMutatingRequestApiCallLogging = options.suppressNonMutatingRequestApiCallLogging;
|
|
66
|
+
this.suppressSuccessfulRequestApiCallLogging = options.suppressSuccessfulRequestApiCallLogging;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get apiCallSequelizeLogging() {
|
|
70
|
+
return this.#apiCallSequelizeLogging;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
set apiCallSequelizeLogging(enabled) {
|
|
74
|
+
if (_.isBoolean(enabled)) this.#apiCallSequelizeLogging = enabled;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get apiCallSequelizeModel() {
|
|
78
|
+
return this.#apiCallSequelizeModel;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set apiCallSequelizeModel(model) {
|
|
82
|
+
if (this.#isSequelizeModel(model)) this.#apiCallSequelizeModel = model;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get apiRouteCategories() {
|
|
86
|
+
return this.#apiRouteCategories;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set apiRouteCategories(categories) {
|
|
90
|
+
if (_.isArray(categories)) this.#apiRouteCategories = categories;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get apiRoutes() {
|
|
94
|
+
return this.#apiRoutes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set apiRoutes(routes) {
|
|
98
|
+
if (_.isArray(routes)) this.#apiRoutes = routes;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get apiVersion() {
|
|
102
|
+
return this.#apiVersion;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set apiVersion(version) {
|
|
106
|
+
if (_.isString(version) && !!version.length) this.#apiVersion = version;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get canSequelizeLogApiCall() {
|
|
110
|
+
return this.apiCallSequelizeLogging && !!this.apiCallSequelizeModel;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get canSequelizeLogRoute() {
|
|
114
|
+
return this.routeSequelizeLogging && !!this.routeSequelizeModel;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get decodeUrls() {
|
|
118
|
+
return this.#decodeUrls;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set decodeUrls(doDecode) {
|
|
122
|
+
if (_.isBoolean(doDecode)) this.#decodeUrls = doDecode;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get httpRequestLogging() {
|
|
126
|
+
return this.#httpRequestLogging;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
set httpRequestLogging(enabled) {
|
|
130
|
+
if (_.isBoolean(enabled)) this.#httpRequestLogging = enabled;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get logger() {
|
|
134
|
+
if (!(this.#logger instanceof ZeddemoreLoggerController)) this.#logger = this.createLogger();
|
|
135
|
+
return this.#logger;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get morganFormat() {
|
|
139
|
+
return this.#morganFormat;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
set morganFormat(format) {
|
|
143
|
+
if (isMorganFormat(format)) this.#morganFormat = format;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get routeSequelizeLogging() {
|
|
147
|
+
return this.#routeSequelizeLogging;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
set routeSequelizeLogging(enabled) {
|
|
151
|
+
if (_.isBoolean(enabled)) this.#routeSequelizeLogging = enabled;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get routeSequelizeModel() {
|
|
155
|
+
return this.#routeSequelizeModel;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
set routeSequelizeModel(model) {
|
|
159
|
+
if (this.#isSequelizeModel(model)) this.#routeSequelizeModel = model;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get suppressNonMutatingRequestApiCallLogging() {
|
|
163
|
+
return this.#suppressNonMutatingRequestApiCallLogging;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
set suppressNonMutatingRequestApiCallLogging(suppress) {
|
|
167
|
+
if (_.isBoolean(suppress)) this.#suppressNonMutatingRequestApiCallLogging = suppress;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get suppressSuccessfulRequestApiCallLogging() {
|
|
171
|
+
return this.#suppressSuccessfulRequestApiCallLogging;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
set suppressSuccessfulRequestApiCallLogging(suppress) {
|
|
175
|
+
if (_.isBoolean(suppress)) this.#suppressSuccessfulRequestApiCallLogging = suppress;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Build log message
|
|
180
|
+
* @param {String|[String]|Object} [message]
|
|
181
|
+
* @param {String} [level]
|
|
182
|
+
* @param {String} [error]
|
|
183
|
+
* @param {Metadata} [metadata]
|
|
184
|
+
* @param {Boolean} [colorize]
|
|
185
|
+
* @returns {string}
|
|
186
|
+
*/
|
|
187
|
+
#buildLogMessage = (message, level, error, metadata, colorize = this.colorize) => {
|
|
188
|
+
metadata = this._structureMetadata(metadata);
|
|
189
|
+
const isError = !!level && (level.indexOf(wlln.ERROR) > -1);
|
|
190
|
+
const leaveUncompressed = isError || !!metadata.raw;
|
|
191
|
+
const parts = [ this._buildLogMessagePrefix(level, metadata, colorize) ];
|
|
192
|
+
let msg = Array.isArray(message) ? message.join('\n') : message;
|
|
193
|
+
msg = _.isObject(message) ? JSON.stringify(msg) : msg;
|
|
194
|
+
parts.push(leaveUncompressed ? msg : msg.replace(/(\n?\s+)/g, ' '));
|
|
195
|
+
if (!!error) parts.push(error);
|
|
196
|
+
const logMessage = parts.join(' ').trim();
|
|
197
|
+
return (isError && colorize) ? chalk.red(logMessage) : logMessage;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build a Morgan format
|
|
202
|
+
* @param {[String]} morganTokens
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
#buildMorganFormat = (morganTokens = [ ]) => {
|
|
206
|
+
function addArgument(token, argument) {
|
|
207
|
+
if (hasArgument(token)) return token;
|
|
208
|
+
const openBracketIndex = token.indexOf('[');
|
|
209
|
+
const _token = (openBracketIndex > -1) ? token.substring(0, openBracketIndex) : token;
|
|
210
|
+
return `${ _token }[${ argument }]`;
|
|
211
|
+
}
|
|
212
|
+
function hasArgument(token) {
|
|
213
|
+
const openBracketIndex = token.indexOf('[');
|
|
214
|
+
const closeBracketIndex = token.indexOf(']');
|
|
215
|
+
return ((openBracketIndex > -1) && (closeBracketIndex > -1) && (closeBracketIndex > (openBracketIndex + 1)));
|
|
216
|
+
}
|
|
217
|
+
if (!morganTokens || !_.isArray(morganTokens) || !morganTokens.length) return mf.DEV;
|
|
218
|
+
const _morganTokens = _.map(morganTokens, (token) => {
|
|
219
|
+
if (!_.isString(token) || !token.length) return null;
|
|
220
|
+
let _token = token.trim();
|
|
221
|
+
if (_token[ 0 ] !== ':') _token = `:${ _token }`;
|
|
222
|
+
switch (_token) {
|
|
223
|
+
case mft.CONTENT_LENGTH_FORMAT: return hasArgument(_token) ? _token : addArgument(_token, this.contentLengthDigits);
|
|
224
|
+
case mft.RESPONSE_TIME:
|
|
225
|
+
case mft.RESPONSE_TIME_FORMAT: return hasArgument(_token) ? _token : addArgument(_token, this.responseTimeDigits);
|
|
226
|
+
default: return _token;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
return (_.compact(_morganTokens)).join(' ');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Build a ZeddemoreProgressBars info
|
|
234
|
+
* @param {Object} options
|
|
235
|
+
* @returns {Object}
|
|
236
|
+
*/
|
|
237
|
+
#buildProgressInfo = (options) => {
|
|
238
|
+
options = options || { };
|
|
239
|
+
const label = options.label || this.defaultLabel;
|
|
240
|
+
return {
|
|
241
|
+
...options,
|
|
242
|
+
colorize: this.colorize,
|
|
243
|
+
label,
|
|
244
|
+
timestampFormat: this.luxonTimestampFormat,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Build a Winston format
|
|
250
|
+
* @param {Object} options
|
|
251
|
+
* @param {[Format]} [additionalFormats]
|
|
252
|
+
* @returns {Format}
|
|
253
|
+
*/
|
|
254
|
+
#buildWinstonFormat = (options, additionalFormats = [ ]) => {
|
|
255
|
+
options = options || { };
|
|
256
|
+
const formats = [
|
|
257
|
+
format((info) => {
|
|
258
|
+
info.id = info.id || options.id || null;
|
|
259
|
+
info.ip = info.ip || options.ip || null;
|
|
260
|
+
info.user = info.user || options.user || null;
|
|
261
|
+
return info;
|
|
262
|
+
})(),
|
|
263
|
+
colorize(),
|
|
264
|
+
label({ label: options.label || this.defaultLabel }),
|
|
265
|
+
timestamp({ format: this.winstonTimestampFormat }),
|
|
266
|
+
metadata(),
|
|
267
|
+
printf(this.#buildWinstonTemplate),
|
|
268
|
+
];
|
|
269
|
+
return combine(...additionalFormats.concat(formats));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Build Winston log template
|
|
274
|
+
* @param {Object} info
|
|
275
|
+
* @param {String} [info.err]
|
|
276
|
+
* @param {String} [info.level]
|
|
277
|
+
* @param {String|[String]|Object} [info.message]
|
|
278
|
+
* @param {Object} [info.metadata]
|
|
279
|
+
* @param {Boolean} [colorize]
|
|
280
|
+
* @returns {string}
|
|
281
|
+
*/
|
|
282
|
+
#buildWinstonTemplate = (info, colorize = this.colorize) => {
|
|
283
|
+
info = info || { };
|
|
284
|
+
return this.#buildLogMessage(info.message, info.level, info.err, info.metadata, colorize);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build a Winston Console transport
|
|
289
|
+
* @param {number|String} [logLevel]
|
|
290
|
+
* @returns {winston.ConsoleTransportInstance}
|
|
291
|
+
*/
|
|
292
|
+
#buildWinstonTransport = (logLevel = this.logLevel) => {
|
|
293
|
+
logLevel = logLevel || this.logLevel;
|
|
294
|
+
const level = logLevelToWinstonLogLevelName(logLevel);
|
|
295
|
+
return new transports.Console({ level });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Build Winston transports
|
|
300
|
+
* @param {number|String} [logLevel]
|
|
301
|
+
* @param {[Object]} [additionalTransports]
|
|
302
|
+
* @returns {Object[]}
|
|
303
|
+
*/
|
|
304
|
+
#buildWinstonTransports = (logLevel = this.logLevel, additionalTransports = [ ]) => {
|
|
305
|
+
return additionalTransports.concat([ this.#buildWinstonTransport(logLevel) ]);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#colorizeResponse(message) {
|
|
309
|
+
if (!_.isString(message) || !message.length) return message;
|
|
310
|
+
const splitMessage = message.split(' ');
|
|
311
|
+
if (splitMessage.length < 2) return message;
|
|
312
|
+
const verb = message.split(' ')[ 0 ];
|
|
313
|
+
return chalkHttpVerbs(verb, message);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create a ZeddemoreLoggerController
|
|
318
|
+
* @param {Object} [options]
|
|
319
|
+
* @param {[Format]} [options.formats]
|
|
320
|
+
* @param {String} [options.level]
|
|
321
|
+
* @param {[Object]} [options.transports]
|
|
322
|
+
* @returns {ZeddemoreLoggerController}
|
|
323
|
+
*/
|
|
324
|
+
createLogger = (options) => {
|
|
325
|
+
options = options || { };
|
|
326
|
+
const winstonLogger = this.#createWinstonLogger(options);
|
|
327
|
+
const multiBar = this.#createZeddemoreProgressMultibar(options);
|
|
328
|
+
this.#logger = new ZeddemoreLoggerController({ logger: winstonLogger, progress: multiBar });
|
|
329
|
+
return this.#logger;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Create a Winston logger with request information
|
|
334
|
+
* @param {String} [requestId]
|
|
335
|
+
* @param {String} [ipAddress]
|
|
336
|
+
* @returns {ZeddemoreLoggerController}
|
|
337
|
+
*/
|
|
338
|
+
createRequestLogger = (requestId = null, ipAddress = null) => {
|
|
339
|
+
requestId = requestId || generateUuid();
|
|
340
|
+
/** @type {Object} */
|
|
341
|
+
const loggerOptions = { id: requestId };
|
|
342
|
+
if (ipAddress) loggerOptions.ip = ipAddress;
|
|
343
|
+
return this.createLogger(loggerOptions);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Create a Winston logger with user information
|
|
348
|
+
* @param {String} [requestId]
|
|
349
|
+
* @param {String} [username]
|
|
350
|
+
* @param {number|String} [logLevel]
|
|
351
|
+
* @param {String} [ipAddress]
|
|
352
|
+
* @returns {ZeddemoreLoggerController}
|
|
353
|
+
*/
|
|
354
|
+
createUserLogger = (requestId = null, username = null, logLevel = this.defaultUserLogLevel, ipAddress = null) => {
|
|
355
|
+
requestId = requestId || generateUuid();
|
|
356
|
+
logLevel = logLevel || this.defaultUserLogLevel;
|
|
357
|
+
const loggerOptions = { id: requestId, level: logLevel, user: username };
|
|
358
|
+
if (ipAddress) loggerOptions.ip = ipAddress;
|
|
359
|
+
return this.createLogger(loggerOptions);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create a Winston Logger
|
|
364
|
+
* @param {Object} [options]
|
|
365
|
+
* @param {[Format]} [options.formats]
|
|
366
|
+
* @param {String} [options.level]
|
|
367
|
+
* @param {[Object]} [options.transports]
|
|
368
|
+
* @returns {winston.Logger}
|
|
369
|
+
*/
|
|
370
|
+
#createWinstonLogger = (options) => {
|
|
371
|
+
options = options || { };
|
|
372
|
+
return createWinstonLogger({
|
|
373
|
+
format: this.#buildWinstonFormat(options, options.formats),
|
|
374
|
+
transports: this.#buildWinstonTransports(options.level, options.transports),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create a progress MultiBar
|
|
380
|
+
* @param {Object} [options]
|
|
381
|
+
* @returns {ZeddemoreProgressMultibar}
|
|
382
|
+
*/
|
|
383
|
+
#createZeddemoreProgressMultibar = (options) => {
|
|
384
|
+
return new ZeddemoreProgressMultibar(this.#buildProgressInfo(options));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Enable console.dir extension
|
|
389
|
+
* @deprecated
|
|
390
|
+
*/
|
|
391
|
+
enableConsoleDirp(...args) {
|
|
392
|
+
return this.enableConsoleDirExtension(...args);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Enable console.dir extension
|
|
397
|
+
* @param {[string]} [methodNames]
|
|
398
|
+
* @param {Boolean} [colors]
|
|
399
|
+
* @param {number|null} [depth]
|
|
400
|
+
* @param {Boolean} [showHidden]
|
|
401
|
+
* @returns {void}
|
|
402
|
+
*/
|
|
403
|
+
enableConsoleDirExtension(methodNames = [ defaults.consoleDirExtensionMethodName ], colors = defaults.consoleDirExtensionColors, depth = defaults.consoleDirExtensionDepth, showHidden = defaults.consoleDirExtensionShowHidden) {
|
|
404
|
+
monkeyPatchConsole(methodNames, colors, depth, showHidden);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get a Zeddemore Logger wrapper instance
|
|
409
|
+
* @param {Object} options
|
|
410
|
+
* @param {[Format]} [options.formats]
|
|
411
|
+
* @param {String} [options.level]
|
|
412
|
+
* @param {ZeddemoreLoggerController} [options.logger]
|
|
413
|
+
* @param {[Object]} [options.transports]
|
|
414
|
+
* @param {Object} [options.user]
|
|
415
|
+
* @returns {ZeddemoreLoggerController}
|
|
416
|
+
*/
|
|
417
|
+
static getLogger(options) {
|
|
418
|
+
options = options || { };
|
|
419
|
+
/** @type {app} app */
|
|
420
|
+
const logger = options.logger || (options.user ? options.user.logger : null) || ((app && app.locals) ? app.locals.logger : null);
|
|
421
|
+
if (!!logger) return logger;
|
|
422
|
+
const zeddemoreLogger = new ZeddemoreLogger(options);
|
|
423
|
+
return zeddemoreLogger.createLogger(options);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get a Winston Logger instance
|
|
428
|
+
* @param {Object} options
|
|
429
|
+
* @param {ZeddemoreProgressBars} [options.progress]
|
|
430
|
+
* @param {Object} [options.user]
|
|
431
|
+
* @returns {ZeddemoreProgressBars}
|
|
432
|
+
*/
|
|
433
|
+
static getProgress(options) {
|
|
434
|
+
options = options || { };
|
|
435
|
+
/** @type {app} */
|
|
436
|
+
const progress = options.progress || (options.user ? options.user.progress : null) || ((app && app.locals) ? app.locals.progress : null);
|
|
437
|
+
if (!!progress) return progress;
|
|
438
|
+
const zeddemoreLogger = new ZeddemoreLogger();
|
|
439
|
+
return zeddemoreLogger.#createZeddemoreProgressMultibar(options);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Initialize Morgan logging
|
|
444
|
+
* @param {Boolean} [colorize]
|
|
445
|
+
*/
|
|
446
|
+
#initializeMorgan(colorize = this.colorize) {
|
|
447
|
+
function buildContentLengthFormatToken(res, decimals = defaults.contentLengthDigits) {
|
|
448
|
+
const contentLength = convertBytes(res.get('content-length'), { decimals });
|
|
449
|
+
return contentLength ? ` - ${ contentLength }` : '';
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* @param {Object} req
|
|
453
|
+
* @param {String} [req.originalUrl]
|
|
454
|
+
* @param {String} [req.url]
|
|
455
|
+
* @returns {string}
|
|
456
|
+
*/
|
|
457
|
+
function buildDecodedUrl(req) {
|
|
458
|
+
const url = req.originalUrl || req.url;
|
|
459
|
+
return decodeURI(url);
|
|
460
|
+
}
|
|
461
|
+
function buildResponseTimeFormatToken(req, res, digits = defaults.responseTimeDigits) {
|
|
462
|
+
const responseTime = getRequestResponseTime(req, res);
|
|
463
|
+
return responseTime ? convertMilliseconds(responseTime, { digits }) : '-';
|
|
464
|
+
}
|
|
465
|
+
function buildStatusColoredToken(res) {
|
|
466
|
+
const statusCode = areHeadersSent(res) ? res.statusCode : null;
|
|
467
|
+
if (!statusCode) return '-';
|
|
468
|
+
return chalkHttpStatuses(statusCode);
|
|
469
|
+
}
|
|
470
|
+
morgan.token(mft.CONTENT_LENGTH_FORMAT, (req, res, decimals) => { return buildContentLengthFormatToken(res, decimals); });
|
|
471
|
+
morgan.token(mft.DECODED_URL, (req, res) => { return buildDecodedUrl(req); });
|
|
472
|
+
morgan.token(mft.RESPONSE_TIME_FORMAT, (req, res, digits) => { return buildResponseTimeFormatToken(req, res, digits); });
|
|
473
|
+
morgan.token(mft.STATUS_COLORED, (req, res) => { return buildStatusColoredToken(res); });
|
|
474
|
+
const morganTokens = colorize ? defaults.morganFormatTokensColorized : defaults.morganFormatTokens;
|
|
475
|
+
morgan.format(mf.ZEDDEMORE, this.#buildMorganFormat(morganTokens));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
#isSequelizeModel(model) {
|
|
479
|
+
return _.isObject(model) && Object.hasOwn(model, 'create') && Object.hasOwn(model, 'findOne');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Log a message for the specified log level
|
|
484
|
+
* @param {number|String} logLevel
|
|
485
|
+
* @returns {Function}
|
|
486
|
+
*/
|
|
487
|
+
log = (logLevel) => {
|
|
488
|
+
logLevel = logLevel || this.logLevel;
|
|
489
|
+
const logLevelName = logLevelToWinstonLogLevelName(logLevel);
|
|
490
|
+
return this.logger[ logLevelName ];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Log server information
|
|
495
|
+
* @param {Object} options
|
|
496
|
+
* @param {Boolean} [options.showLogLevel=true]
|
|
497
|
+
* @param {String} [options.url]
|
|
498
|
+
* @param {number|String} [logLevel=wll.INFO]
|
|
499
|
+
* @returns {void}
|
|
500
|
+
*/
|
|
501
|
+
#logServerInfo = (options, logLevel = wll.INFO) => {
|
|
502
|
+
options = options || { };
|
|
503
|
+
const showLogLevel = _.isBoolean(options.showLogLevel) ? options.showLogLevel : true;
|
|
504
|
+
const logLevelLabel = chalkLogLevel(this.logLevel, this.logLevelName.toUpperCase());
|
|
505
|
+
const msgParts = [ `Server is ${ chalk.bold(chalk.yellowBright(defaults.liveMessage)) }` ];
|
|
506
|
+
if (showLogLevel) msgParts.push(`with log level ${ logLevelLabel }${ this.forceLogLevel ? ` (${ chalk.italic('forced') })` : '' }`);
|
|
507
|
+
if (options.url) msgParts.push(`at ${ chalk.underline(options.url) }`);
|
|
508
|
+
this.log(logLevel)(msgParts.join(' '));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Log server listening information
|
|
513
|
+
* @param {Object} options
|
|
514
|
+
* @param {String} [options.environment]
|
|
515
|
+
* @param {String} [options.instance]
|
|
516
|
+
* @param {String} [options.name]
|
|
517
|
+
* @param {number} [options.port]
|
|
518
|
+
* @param {number|String} [logLevel=wll.INFO]
|
|
519
|
+
* @returns {void}
|
|
520
|
+
*/
|
|
521
|
+
#logServerListening = (options, logLevel = wll.INFO) => {
|
|
522
|
+
const serverEnvironment = options.environment || process.env.NODE_ENV || 'development';
|
|
523
|
+
const serverInstance = options.instance || (process.env.NODE_APP_INSTANCE && (process.env.NODE_APP_INSTANCE.length > 1)) ? process.env.NODE_APP_INSTANCE : null; // handles goofy PM2 NODE_APP_INSTANCE of '0'
|
|
524
|
+
const serverName = options.name || this.defaultLabel;
|
|
525
|
+
const serverPort = options.port || process.env.PORT || 3000;
|
|
526
|
+
const msgParts = [ ];
|
|
527
|
+
const serverParts = [ serverName ];
|
|
528
|
+
if (serverEnvironment) serverParts.push(serverEnvironment);
|
|
529
|
+
if (serverInstance) serverParts.push(serverInstance);
|
|
530
|
+
msgParts.push(`[${ serverParts.join(':') }]`);
|
|
531
|
+
msgParts.push(`Node.js Express server listening on port ${ chalk.bold(serverPort) }`);
|
|
532
|
+
this.log(logLevel)(msgParts.join(' '));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Log server packages information
|
|
537
|
+
* @param {[Object]} packages
|
|
538
|
+
* @param {number|String} [logLevel=wll.INFO]
|
|
539
|
+
* @returns {void}
|
|
540
|
+
*/
|
|
541
|
+
#logServerPackagesInfo = (packages, logLevel = wll.INFO) => {
|
|
542
|
+
function buildVersionLabel(_package) {
|
|
543
|
+
if (!_package || !_package.name || !_package.version) return null;
|
|
544
|
+
const packageName = _package.style ? chalkTarget(_package.name, _package.style) : chalkPackage(_package.name);
|
|
545
|
+
return `${ packageName }@${ _package.version }`;
|
|
546
|
+
}
|
|
547
|
+
const versionParts = _.compact(_.sortBy(packages, (_package) => _package.name.toUpperCase()).map(buildVersionLabel));
|
|
548
|
+
this.log(logLevel)(`Packages: ${ versionParts.join(' | ') }`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Log server routes added
|
|
553
|
+
* @param {Object} options
|
|
554
|
+
* @param {[String]} [options.categories]
|
|
555
|
+
* @param {[String]} [options.routes]
|
|
556
|
+
* @param {Boolean} [options.showCategories=true]
|
|
557
|
+
* @param {number|String} [logLevel=wll.INFO]
|
|
558
|
+
* @returns {void}
|
|
559
|
+
*/
|
|
560
|
+
#logServerRoutesAdded = (options, logLevel = wll.INFO) => {
|
|
561
|
+
options = options || { };
|
|
562
|
+
const showCategories = _.isBoolean(options.showCategories) ? options.showCategories : true;
|
|
563
|
+
const routes = options.routes || this.apiRoutes || [ ];
|
|
564
|
+
const routesLabel = (routes.length === 1) ? 'route' : 'routes';
|
|
565
|
+
const addedLabel = chalk.green('added');
|
|
566
|
+
const msgParts = [ `${ routes.length } ${ routesLabel } ${ addedLabel }` ];
|
|
567
|
+
if (showCategories) {
|
|
568
|
+
const categories = options.categories || this.apiRouteCategories || [];
|
|
569
|
+
msgParts.push(`in: ${ categories.sort().join('/') }`);
|
|
570
|
+
}
|
|
571
|
+
this.log(logLevel)(msgParts.join(', '));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Log server startup information
|
|
576
|
+
* @param {Object} options
|
|
577
|
+
* @param {[String]} [options.categories]
|
|
578
|
+
* @param {String} [options.environment]
|
|
579
|
+
* @param {String} [options.instance]
|
|
580
|
+
* @param {String} [options.name]
|
|
581
|
+
* @param {number} [options.port]
|
|
582
|
+
* @param {[Object]} [options.packages]
|
|
583
|
+
* @param {[String]} [options.routes]
|
|
584
|
+
* @param {Boolean} [options.showCategories=true]
|
|
585
|
+
* @param {Boolean} [options.showInfo=true]
|
|
586
|
+
* @param {Boolean} [options.showListening=true]
|
|
587
|
+
* @param {Boolean} [options.showLogLevel=true]
|
|
588
|
+
* @param {Boolean} [options.showPackages=true]
|
|
589
|
+
* @param {Boolean} [options.showRoutes=true]
|
|
590
|
+
* @param {number|String} [logLevel=wll.INFO]
|
|
591
|
+
* @returns {void}
|
|
592
|
+
*/
|
|
593
|
+
logServerStartup = (options, logLevel = wll.INFO) => {
|
|
594
|
+
options = options || { };
|
|
595
|
+
const showListening = _.isBoolean(options.showListening) ? options.showListening : true;
|
|
596
|
+
const showPackages = _.isBoolean(options.showPackages) ? options.showPackages : true;
|
|
597
|
+
const showRoutes = _.isBoolean(options.showRoutes) ? options.showRoutes : true;
|
|
598
|
+
const showInfo = _.isBoolean(options.showInfo) ? options.showInfo : true;
|
|
599
|
+
if (showListening) this.#logServerListening(options, logLevel);
|
|
600
|
+
if (showPackages && options.packages) this.#logServerPackagesInfo(options.packages, logLevel);
|
|
601
|
+
if (showRoutes) this.#logServerRoutesAdded(options, logLevel);
|
|
602
|
+
if (showInfo) this.#logServerInfo(options, logLevel);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
#matchPathToRoute = (path) => {
|
|
606
|
+
if (!path) return null;
|
|
607
|
+
let matchedRoute = null;
|
|
608
|
+
for (const route of this.apiRoutes) {
|
|
609
|
+
if (path.match(route.regexp)) {
|
|
610
|
+
matchedRoute = route.path;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return matchedRoute;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Create Morgan middleware for logging HTTP requests
|
|
619
|
+
* @param {Object} options
|
|
620
|
+
* @param {Boolean} [options.colorize]
|
|
621
|
+
* @param {Boolean} [options.disableMorganLogging]
|
|
622
|
+
* @param {String} [options.morganFormat]
|
|
623
|
+
* @returns {function(*, *, *): void}
|
|
624
|
+
*/
|
|
625
|
+
morganMiddleware = (options) => {
|
|
626
|
+
options = options || { };
|
|
627
|
+
const colorize = _.isBoolean(options.colorize) ? options.colorize : this.colorize;
|
|
628
|
+
const disableMorganLogging = _.isBoolean(options.disableMorganLogging) ? options.disableMorganLogging : !this.httpRequestLogging;
|
|
629
|
+
const morganFormat = options.morganFormat || this.#buildMorganFormat(defaults.morganFormatTokens);
|
|
630
|
+
return (req, res, next) => {
|
|
631
|
+
this.#initializeMorgan(colorize);
|
|
632
|
+
return morgan(morganFormat, {
|
|
633
|
+
skip: () => disableMorganLogging,
|
|
634
|
+
stream: {
|
|
635
|
+
write: (message) => {
|
|
636
|
+
if (this.canSequelizeLogApiCall) this.#writeApiCallToDatabase(req, res, options);
|
|
637
|
+
const logOptions = { };
|
|
638
|
+
const user = this.userGetFn(req, res);
|
|
639
|
+
if (user) logOptions.user = user;
|
|
640
|
+
const response = colorize ? this.#colorizeResponse(message.trim()) : message.trim();
|
|
641
|
+
ZeddemoreLogger.getLogger(logOptions).http(response);
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
})(req, res, next);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
requestLoggerMiddleware = (req, res, next) => {
|
|
649
|
+
const requestValues = parseRequest(req);
|
|
650
|
+
const requestId = generateUuid();
|
|
651
|
+
req[ this.requestIdAttribute ] = requestId;
|
|
652
|
+
res.header(rqh.REQUEST_ID, requestId);
|
|
653
|
+
const ipAddress = requestValues.ipAddress;
|
|
654
|
+
res.locals = res.locals || { };
|
|
655
|
+
res.locals.user = res.locals.user || { };
|
|
656
|
+
res.locals.user.logger = this.createRequestLogger(requestId, ipAddress);
|
|
657
|
+
return next();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
routeLoggingMiddleware = (req, res, next) => {
|
|
661
|
+
if (!this.canSequelizeLogRoute) return next();
|
|
662
|
+
try {
|
|
663
|
+
const routeValues = {
|
|
664
|
+
route: req.path,
|
|
665
|
+
method: req.method,
|
|
666
|
+
lastCalled: new Date().toISOString(),
|
|
667
|
+
};
|
|
668
|
+
this.#writeRouteToDatabase(routeValues);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
} finally {
|
|
671
|
+
next();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
userLoggerMiddleware = (req, res, next) => {
|
|
676
|
+
const requestId = this._getRequestId(req);
|
|
677
|
+
/** @type {user} */
|
|
678
|
+
const user = this.userGetFn(req, res) || { };
|
|
679
|
+
if (!requestId) return next();
|
|
680
|
+
const username = user.username || user.login || null;
|
|
681
|
+
let ipAddress = user.ipAddress || null;
|
|
682
|
+
if (!ipAddress) {
|
|
683
|
+
const requestValues = parseRequest(req);
|
|
684
|
+
ipAddress = requestValues.ipAddress;
|
|
685
|
+
}
|
|
686
|
+
let userLogLevel = !_.isNil(user.logLevel) ? user.logLevel : this.defaultUserLogLevel;
|
|
687
|
+
if (userLogLevel < 0) userLogLevel = this.logLevel;
|
|
688
|
+
const level = this.forceLogLevel ? this.logLevel : userLogLevel;
|
|
689
|
+
res.locals = res.locals || { };
|
|
690
|
+
res.locals.user = res.locals.user || user;
|
|
691
|
+
res.locals.user.logger = this.createUserLogger(requestId, username, level, ipAddress);
|
|
692
|
+
return next();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
#writeApiCallToDatabase = async (req, res, options) => {
|
|
696
|
+
options = options || { };
|
|
697
|
+
if (!this.canSequelizeLogApiCall || !req) return;
|
|
698
|
+
const user = this.userGetFn(req, res) || { };
|
|
699
|
+
const statusCode = options.statusCode || (res ? res.statusCode : null);
|
|
700
|
+
if (isResponseSuccessful(statusCode) && this.suppressSuccessfulRequestApiCallLogging) return;
|
|
701
|
+
if (!isRequestOfMethod(req, [ http.DELETE, http.PUT ]) && this.suppressNonMutatingRequestApiCallLogging) return;
|
|
702
|
+
const matchedRoute = this.#matchPathToRoute(req.path);
|
|
703
|
+
let metadataDefaults = Object.assign({ }, user);
|
|
704
|
+
delete(metadataDefaults.id);
|
|
705
|
+
delete(metadataDefaults.logger);
|
|
706
|
+
const _metadata = _.defaults({ }, (options.metadata || { }), metadataDefaults);
|
|
707
|
+
if (matchedRoute) {
|
|
708
|
+
try {
|
|
709
|
+
const appCallValues = {
|
|
710
|
+
apiVersion: this.apiVersion,
|
|
711
|
+
client: user ? user.clientId : null,
|
|
712
|
+
clientVersion: req.header(rqh.APP_VERSION),
|
|
713
|
+
httpStatusCode: statusCode,
|
|
714
|
+
ipAddress: req.header(rqh.FORWARDED_FOR) || req.socket.remoteAddress,
|
|
715
|
+
metadata: _metadata,
|
|
716
|
+
method: req.method,
|
|
717
|
+
route: matchedRoute,
|
|
718
|
+
token: extractToken(req),
|
|
719
|
+
userAgent: req.header(rqh.USER_AGENT),
|
|
720
|
+
userId: user ? user.id : null,
|
|
721
|
+
};
|
|
722
|
+
await this.apiCallSequelizeModel.create(appCallValues);
|
|
723
|
+
} catch (e) {
|
|
724
|
+
console.error(e);
|
|
725
|
+
ZeddemoreLogger.getLogger({ user }).error('Api call logging failed, moving on');
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
#writeRouteToDatabase = async (routeValues) => {
|
|
731
|
+
if (!this.canSequelizeLogRoute || !routeValues) return;
|
|
732
|
+
const matchedRoute = this.#matchPathToRoute(routeValues.route);
|
|
733
|
+
if (matchedRoute) {
|
|
734
|
+
try {
|
|
735
|
+
const route = await this.routeSequelizeModel.findOne({ where: { route: matchedRoute }, logging: false });
|
|
736
|
+
if (route) {
|
|
737
|
+
route.count++;
|
|
738
|
+
route.lastCalledAt = new Date();
|
|
739
|
+
route.save();
|
|
740
|
+
} else {
|
|
741
|
+
this.routeSequelizeModel.create({
|
|
742
|
+
count: 1,
|
|
743
|
+
lastCalledAt: new Date(),
|
|
744
|
+
method: routeValues.method,
|
|
745
|
+
route: matchedRoute,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
} catch (e) {
|
|
749
|
+
console.error(e);
|
|
750
|
+
console.error('Route logging failed, moving on');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
static writeAxiosErrorLog(axiosError, logOptions, options) {
|
|
756
|
+
logOptions = logOptions || { };
|
|
757
|
+
options = options || { };
|
|
758
|
+
if (!axiosError || !axiosError.response) return;
|
|
759
|
+
const _response = axiosError.response;
|
|
760
|
+
if (!_response.request) return;
|
|
761
|
+
const _request = _response.request;
|
|
762
|
+
const logger = options.logger || ZeddemoreLogger.getLogger(options);
|
|
763
|
+
if (!logger) return;
|
|
764
|
+
const _method = _request.method || null;
|
|
765
|
+
const method = _method ? chalkHttpVerbs(_method) : null;
|
|
766
|
+
const _config = _response.config || null;
|
|
767
|
+
const _headers = _response.headers || null;
|
|
768
|
+
const _data = _response.data || null;
|
|
769
|
+
const path = _config ? _config.url : '';
|
|
770
|
+
const prefix = logOptions.prefix ? `[${ logOptions.prefix }]` : null;
|
|
771
|
+
const status = chalkHttpStatuses(_response.status);
|
|
772
|
+
const _duration = axiosError.duration || _response.duration;
|
|
773
|
+
const duration = _duration ? convertMilliseconds(_duration, logger.responseTimeDigits) : '-';
|
|
774
|
+
let _contentLength = _headers ? _headers[ rsh.CONTENT_LENGTH ] : null;
|
|
775
|
+
if (!_contentLength) _contentLength = _data ? sizeof(_data) : null;
|
|
776
|
+
const contentLength = _contentLength ? `- ${ convertBytes(_contentLength, logger.contentLengthDigits) }` : null;
|
|
777
|
+
const data = _data ? JSON.stringify(_data) : null;
|
|
778
|
+
const parts = [ prefix, method, path, status, duration, contentLength, data ];
|
|
779
|
+
const message = _.compact(parts).join(' ');
|
|
780
|
+
logger.error(message);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
static writeAxiosResponseLog(axiosResponse, logOptions, options) {
|
|
784
|
+
logOptions = logOptions || { };
|
|
785
|
+
options = options || { };
|
|
786
|
+
if (!axiosResponse || !axiosResponse.request) return;
|
|
787
|
+
const _request = axiosResponse.request;
|
|
788
|
+
const axiosConfig = logOptions.axios || { };
|
|
789
|
+
const configData = (logOptions.data && _.isObject(axiosConfig.data)) ? JSON.stringify(axiosConfig.data) : null;
|
|
790
|
+
const logLevel = logLevelToWinstonLogLevelName(logOptions.logLevel);
|
|
791
|
+
const logger = options.logger || ZeddemoreLogger.getLogger(options);
|
|
792
|
+
if (!logger) return;
|
|
793
|
+
const _method = _request.method || null;
|
|
794
|
+
const method = _method ? chalkHttpVerbs(_method) : null;
|
|
795
|
+
const _headers = axiosResponse.headers || null;
|
|
796
|
+
const _data = axiosResponse.data || null;
|
|
797
|
+
const path = _request.path || '';
|
|
798
|
+
const prefix = logOptions.prefix ? `[${ logOptions.prefix }]` : null;
|
|
799
|
+
const responseData = (logOptions.response && _.isObject(_data)) ? JSON.stringify(_data) : null;
|
|
800
|
+
const status = chalkHttpStatuses(axiosResponse.status);
|
|
801
|
+
const _duration = axiosResponse.duration;
|
|
802
|
+
const duration = _duration ? convertMilliseconds(_duration, logger.responseTimeDigits) : '-';
|
|
803
|
+
let _contentLength = _headers ? _headers[ rsh.CONTENT_LENGTH ] : null;
|
|
804
|
+
if (!_contentLength) _contentLength = _data ? sizeof(_data) : null;
|
|
805
|
+
const contentLengthDigits = logOptions.contentLengthDigits || logger.contentLengthDigits || defaults.contentLengthDigits;
|
|
806
|
+
const contentLength = _contentLength ? `- ${ convertBytes(_contentLength, contentLengthDigits) }` : null;
|
|
807
|
+
const parts = [ prefix, method, path, configData, status, responseData, duration, contentLength ];
|
|
808
|
+
const message = _.compact(parts).join(' ');
|
|
809
|
+
logger[ logLevel ](message);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
module.exports = ZeddemoreLogger;
|