Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50675 views
1
/*
2
* logger.js: Core logger object used by winston.
3
*
4
* (C) 2010 Charlie Robbins
5
* MIT LICENCE
6
*
7
*/
8
9
var events = require('events'),
10
util = require('util'),
11
async = require('async'),
12
config = require('./config'),
13
common = require('./common'),
14
exception = require('./exception'),
15
Stream = require('stream').Stream;
16
17
//
18
// Time constants
19
//
20
var ticksPerMillisecond = 10000;
21
22
//
23
// ### function Logger (options)
24
// #### @options {Object} Options for this instance.
25
// Constructor function for the Logger object responsible
26
// for persisting log messages and metadata to one or more transports.
27
//
28
var Logger = exports.Logger = function (options) {
29
events.EventEmitter.call(this);
30
options = options || {};
31
32
var self = this,
33
handleExceptions = false;
34
35
//
36
// Set Levels and default logging level
37
//
38
this.padLevels = options.padLevels || false;
39
this.setLevels(options.levels);
40
if (options.colors) {
41
config.addColors(options.colors);
42
}
43
44
//
45
// Hoist other options onto this instance.
46
//
47
this.level = options.level || 'info';
48
this.emitErrs = options.emitErrs || false;
49
this.stripColors = options.stripColors || false;
50
this.exitOnError = typeof options.exitOnError !== 'undefined'
51
? options.exitOnError
52
: true;
53
54
//
55
// Setup other intelligent default settings.
56
//
57
this.transports = {};
58
this.rewriters = [];
59
this.exceptionHandlers = {};
60
this.profilers = {};
61
this._names = [];
62
this._hnames = [];
63
64
if (options.transports) {
65
options.transports.forEach(function (transport) {
66
self.add(transport, null, true);
67
68
if (transport.handleExceptions) {
69
handleExceptions = true;
70
}
71
});
72
}
73
74
if (options.rewriters) {
75
options.rewriters.forEach(function (rewriter) {
76
self.addRewriter(rewriter);
77
});
78
}
79
80
if (options.exceptionHandlers) {
81
handleExceptions = true;
82
options.exceptionHandlers.forEach(function (handler) {
83
self._hnames.push(handler.name);
84
self.exceptionHandlers[handler.name] = handler;
85
});
86
}
87
88
if (options.handleExceptions || handleExceptions) {
89
this.handleExceptions();
90
}
91
};
92
93
//
94
// Inherit from `events.EventEmitter`.
95
//
96
util.inherits(Logger, events.EventEmitter);
97
98
//
99
// ### function extend (target)
100
// #### @target {Object} Target to extend.
101
// Extends the target object with a 'log' method
102
// along with a method for each level in this instance.
103
//
104
Logger.prototype.extend = function (target) {
105
var self = this;
106
['log', 'profile', 'startTimer'].concat(Object.keys(this.levels)).forEach(function (method) {
107
target[method] = function () {
108
return self[method].apply(self, arguments);
109
};
110
});
111
112
return this;
113
};
114
115
//
116
// ### function log (level, msg, [meta], callback)
117
// #### @level {string} Level at which to log the message.
118
// #### @msg {string} Message to log
119
// #### @meta {Object} **Optional** Additional metadata to attach
120
// #### @callback {function} Continuation to respond to when complete.
121
// Core logging method exposed to Winston. Metadata is optional.
122
//
123
Logger.prototype.log = function (level, msg) {
124
var self = this,
125
callback,
126
meta;
127
128
if (arguments.length === 3) {
129
if (typeof arguments[2] === 'function') {
130
meta = {};
131
callback = arguments[2];
132
}
133
else if (typeof arguments[2] === 'object') {
134
meta = arguments[2];
135
}
136
}
137
else if (arguments.length === 4) {
138
meta = arguments[2];
139
callback = arguments[3];
140
}
141
142
// If we should pad for levels, do so
143
if (this.padLevels) {
144
msg = new Array(this.levelLength - level.length + 1).join(' ') + msg;
145
}
146
147
function onError (err) {
148
if (callback) {
149
callback(err);
150
}
151
else if (self.emitErrs) {
152
self.emit('error', err);
153
};
154
}
155
156
if (this.transports.length === 0) {
157
return onError(new Error('Cannot log with no transports.'));
158
}
159
else if (typeof self.levels[level] === 'undefined') {
160
return onError(new Error('Unknown log level: ' + level));
161
}
162
163
this.rewriters.forEach(function (rewriter) {
164
meta = rewriter(level, msg, meta);
165
});
166
167
//
168
// For consideration of terminal 'color" programs like colors.js,
169
// which can add ANSI escape color codes to strings, we destyle the
170
// ANSI color escape codes when `this.stripColors` is set.
171
//
172
// see: http://en.wikipedia.org/wiki/ANSI_escape_code
173
//
174
if (this.stripColors) {
175
var code = /\u001b\[(\d+(;\d+)*)?m/g;
176
msg = ('' + msg).replace(code, '');
177
}
178
179
//
180
// Log for each transport and emit 'logging' event
181
//
182
function emit(name, next) {
183
var transport = self.transports[name];
184
if ((transport.level && self.levels[transport.level] <= self.levels[level])
185
|| (!transport.level && self.levels[self.level] <= self.levels[level])) {
186
transport.log(level, msg, meta, function (err) {
187
if (err) {
188
err.transport = transport;
189
cb(err);
190
return next();
191
}
192
self.emit('logging', transport, level, msg, meta);
193
next();
194
});
195
} else {
196
next();
197
}
198
}
199
200
//
201
// Respond to the callback
202
//
203
function cb(err) {
204
if (callback) {
205
if (err) return callback(err);
206
callback(null, level, msg, meta);
207
}
208
callback = null;
209
}
210
211
async.forEach(this._names, emit, cb);
212
213
return this;
214
};
215
216
//
217
// ### function query (options, callback)
218
// #### @options {Object} Query options for this instance.
219
// #### @callback {function} Continuation to respond to when complete.
220
// Queries the all transports for this instance with the specified `options`.
221
// This will aggregate each transport's results into one object containing
222
// a property per transport.
223
//
224
Logger.prototype.query = function (options, callback) {
225
if (typeof options === 'function') {
226
callback = options;
227
options = {};
228
}
229
230
var self = this,
231
options = options || {},
232
results = {},
233
query = common.clone(options.query) || {},
234
transports;
235
236
//
237
// Helper function to query a single transport
238
//
239
function queryTransport(transport, next) {
240
if (options.query) {
241
options.query = transport.formatQuery(query);
242
}
243
244
transport.query(options, function (err, results) {
245
if (err) {
246
return next(err);
247
}
248
249
next(null, transport.formatResults(results, options.format));
250
});
251
}
252
253
//
254
// Helper function to accumulate the results from
255
// `queryTransport` into the `results`.
256
//
257
function addResults (transport, next) {
258
queryTransport(transport, function (err, result) {
259
result = err || result;
260
if (result) {
261
results[transport.name] = result;
262
}
263
next();
264
});
265
}
266
267
//
268
// If an explicit transport is being queried then
269
// respond with the results from only that transport
270
//
271
if (options.transport) {
272
options.transport = options.transport.toLowerCase();
273
return queryTransport(this.transports[options.transport], callback);
274
}
275
276
//
277
// Create a list of all transports for this instance.
278
//
279
transports = this._names.map(function (name) {
280
return self.transports[name];
281
}).filter(function (transport) {
282
return !!transport.query;
283
});
284
285
//
286
// Iterate over the transports in parallel setting the
287
// appropriate key in the `results`
288
//
289
async.forEach(transports, addResults, function () {
290
callback(null, results);
291
});
292
};
293
294
//
295
// ### function stream (options)
296
// #### @options {Object} Stream options for this instance.
297
// Returns a log stream for all transports. Options object is optional.
298
//
299
Logger.prototype.stream = function (options) {
300
var self = this,
301
options = options || {},
302
out = new Stream,
303
streams = [],
304
transports;
305
306
if (options.transport) {
307
var transport = this.transports[options.transport];
308
delete options.transport;
309
if (transport && transport.stream) {
310
return transport.stream(options);
311
}
312
}
313
314
out._streams = streams;
315
out.destroy = function () {
316
var i = streams.length;
317
while (i--) streams[i].destroy();
318
};
319
320
//
321
// Create a list of all transports for this instance.
322
//
323
transports = this._names.map(function (name) {
324
return self.transports[name];
325
}).filter(function (transport) {
326
return !!transport.stream;
327
});
328
329
transports.forEach(function (transport) {
330
var stream = transport.stream(options);
331
if (!stream) return;
332
333
streams.push(stream);
334
335
stream.on('log', function (log) {
336
log.transport = log.transport || [];
337
log.transport.push(transport.name);
338
out.emit('log', log);
339
});
340
341
stream.on('error', function (err) {
342
err.transport = err.transport || [];
343
err.transport.push(transport.name);
344
out.emit('error', err);
345
});
346
});
347
348
return out;
349
};
350
351
//
352
// ### function close ()
353
// Cleans up resources (streams, event listeners) for all
354
// transports associated with this instance (if necessary).
355
//
356
Logger.prototype.close = function () {
357
var self = this;
358
359
this._names.forEach(function (name) {
360
var transport = self.transports[name];
361
if (transport && transport.close) {
362
transport.close();
363
}
364
});
365
};
366
367
//
368
// ### function handleExceptions ()
369
// Handles `uncaughtException` events for the current process
370
//
371
Logger.prototype.handleExceptions = function () {
372
var args = Array.prototype.slice.call(arguments),
373
handlers = [],
374
self = this;
375
376
args.forEach(function (a) {
377
if (Array.isArray(a)) {
378
handlers = handlers.concat(a);
379
}
380
else {
381
handlers.push(a);
382
}
383
});
384
385
handlers.forEach(function (handler) {
386
self.exceptionHandlers[handler.name] = handler;
387
});
388
389
this._hnames = Object.keys(self.exceptionHandlers);
390
391
if (!this.catchExceptions) {
392
this.catchExceptions = this._uncaughtException.bind(this);
393
process.on('uncaughtException', this.catchExceptions);
394
}
395
};
396
397
//
398
// ### function unhandleExceptions ()
399
// Removes any handlers to `uncaughtException` events
400
// for the current process
401
//
402
Logger.prototype.unhandleExceptions = function () {
403
var self = this;
404
405
if (this.catchExceptions) {
406
Object.keys(this.exceptionHandlers).forEach(function (name) {
407
if (handler.close) {
408
handler.close();
409
}
410
});
411
412
this.exceptionHandlers = {};
413
Object.keys(this.transports).forEach(function (name) {
414
var transport = self.transports[name];
415
if (transport.handleExceptions) {
416
transport.handleExceptions = false;
417
}
418
})
419
420
process.removeListener('uncaughtException', this.catchExceptions);
421
this.catchExceptions = false;
422
}
423
};
424
425
//
426
// ### function add (transport, [options])
427
// #### @transport {Transport} Prototype of the Transport object to add.
428
// #### @options {Object} **Optional** Options for the Transport to add.
429
// #### @instance {Boolean} **Optional** Value indicating if `transport` is already instantiated.
430
// Adds a transport of the specified type to this instance.
431
//
432
Logger.prototype.add = function (transport, options, created) {
433
var instance = created ? transport : (new (transport)(options));
434
435
if (!instance.name && !instance.log) {
436
throw new Error('Unknown transport with no log() method');
437
}
438
else if (this.transports[instance.name]) {
439
throw new Error('Transport already attached: ' + instance.name);
440
}
441
442
this.transports[instance.name] = instance;
443
this._names = Object.keys(this.transports);
444
445
//
446
// Listen for the `error` event on the new Transport
447
//
448
instance._onError = this._onError.bind(this, instance)
449
instance.on('error', instance._onError);
450
451
//
452
// If this transport has `handleExceptions` set to `true`
453
// and we are not already handling exceptions, do so.
454
//
455
if (instance.handleExceptions && !this.catchExceptions) {
456
this.handleExceptions();
457
}
458
459
return this;
460
};
461
462
//
463
// ### function addRewriter (transport, [options])
464
// #### @transport {Transport} Prototype of the Transport object to add.
465
// #### @options {Object} **Optional** Options for the Transport to add.
466
// #### @instance {Boolean} **Optional** Value indicating if `transport` is already instantiated.
467
// Adds a transport of the specified type to this instance.
468
//
469
Logger.prototype.addRewriter = function (rewriter) {
470
this.rewriters.push(rewriter);
471
}
472
473
//
474
// ### function clear ()
475
// Remove all transports from this instance
476
//
477
Logger.prototype.clear = function () {
478
for (var name in this.transports) {
479
this.remove({ name: name });
480
}
481
};
482
483
//
484
// ### function remove (transport)
485
// #### @transport {Transport} Transport to remove.
486
// Removes a transport of the specified type from this instance.
487
//
488
Logger.prototype.remove = function (transport) {
489
var name = transport.name || transport.prototype.name;
490
491
if (!this.transports[name]) {
492
throw new Error('Transport ' + name + ' not attached to this instance');
493
}
494
495
var instance = this.transports[name];
496
delete this.transports[name];
497
this._names = Object.keys(this.transports);
498
499
if (instance.close) {
500
instance.close();
501
}
502
503
instance.removeListener('error', instance._onError);
504
return this;
505
};
506
507
var ProfileHandler = function (logger) {
508
this.logger = logger;
509
510
this.start = Date.now();
511
512
this.done = function (msg) {
513
var args, callback, meta;
514
args = Array.prototype.slice.call(arguments);
515
callback = typeof args[args.length - 1] === 'function' ? args.pop() : null;
516
meta = typeof args[args.length - 1] === 'object' ? args.pop() : {};
517
518
meta.duration = (Date.now()) - this.start + 'ms';
519
520
return this.logger.info(msg, meta, callback);
521
}
522
}
523
524
Logger.prototype.startTimer = function () {
525
return new ProfileHandler(this);
526
}
527
528
//
529
// ### function profile (id, [msg, meta, callback])
530
// #### @id {string} Unique id of the profiler
531
// #### @msg {string} **Optional** Message to log
532
// #### @meta {Object} **Optional** Additional metadata to attach
533
// #### @callback {function} **Optional** Continuation to respond to when complete.
534
// Tracks the time inbetween subsequent calls to this method
535
// with the same `id` parameter. The second call to this method
536
// will log the difference in milliseconds along with the message.
537
//
538
Logger.prototype.profile = function (id) {
539
var now = Date.now(), then, args,
540
msg, meta, callback;
541
542
if (this.profilers[id]) {
543
then = this.profilers[id];
544
delete this.profilers[id];
545
546
// Support variable arguments: msg, meta, callback
547
args = Array.prototype.slice.call(arguments);
548
callback = typeof args[args.length - 1] === 'function' ? args.pop() : null;
549
meta = typeof args[args.length - 1] === 'object' ? args.pop() : {};
550
msg = args.length === 2 ? args[1] : id;
551
552
// Set the duration property of the metadata
553
meta.duration = now - then + 'ms';
554
return this.info(msg, meta, callback);
555
}
556
else {
557
this.profilers[id] = now;
558
}
559
560
return this;
561
};
562
563
//
564
// ### function setLevels (target)
565
// #### @target {Object} Target levels to use on this instance
566
// Sets the `target` levels specified on this instance.
567
//
568
Logger.prototype.setLevels = function (target) {
569
return common.setLevels(this, this.levels, target);
570
};
571
572
//
573
// ### function cli ()
574
// Configures this instance to have the default
575
// settings for command-line interfaces: no timestamp,
576
// colors enabled, padded output, and additional levels.
577
//
578
Logger.prototype.cli = function () {
579
this.padLevels = true;
580
this.setLevels(config.cli.levels);
581
config.addColors(config.cli.colors);
582
583
if (this.transports.console) {
584
this.transports.console.colorize = true;
585
this.transports.console.timestamp = false;
586
}
587
588
return this;
589
};
590
591
//
592
// ### @private function _uncaughtException (err)
593
// #### @err {Error} Error to handle
594
// Logs all relevant information around the `err` and
595
// exits the current process.
596
//
597
Logger.prototype._uncaughtException = function (err) {
598
var self = this,
599
responded = false,
600
info = exception.getAllInfo(err),
601
handlers = this._getExceptionHandlers(),
602
timeout,
603
doExit;
604
605
//
606
// Calculate if we should exit on this error
607
//
608
doExit = typeof this.exitOnError === 'function'
609
? this.exitOnError(err)
610
: this.exitOnError;
611
612
function logAndWait(transport, next) {
613
transport.logException('uncaughtException', info, next, err);
614
}
615
616
function gracefulExit() {
617
if (doExit && !responded) {
618
//
619
// Remark: Currently ignoring any exceptions from transports
620
// when catching uncaught exceptions.
621
//
622
clearTimeout(timeout);
623
responded = true;
624
process.exit(1);
625
}
626
}
627
628
if (!handlers || handlers.length === 0) {
629
return gracefulExit();
630
}
631
632
//
633
// Log to all transports and allow the operation to take
634
// only up to `3000ms`.
635
//
636
async.forEach(handlers, logAndWait, gracefulExit);
637
if (doExit) {
638
timeout = setTimeout(gracefulExit, 3000);
639
}
640
};
641
642
//
643
// ### @private function _getExceptionHandlers ()
644
// Returns the list of transports and exceptionHandlers
645
// for this instance.
646
//
647
Logger.prototype._getExceptionHandlers = function () {
648
var self = this;
649
650
return this._hnames.map(function (name) {
651
return self.exceptionHandlers[name];
652
}).concat(this._names.map(function (name) {
653
return self.transports[name].handleExceptions && self.transports[name];
654
})).filter(Boolean);
655
};
656
657
//
658
// ### @private function _onError (transport, err)
659
// #### @transport {Object} Transport on which the error occured
660
// #### @err {Error} Error that occurred on the transport
661
// Bubbles the error, `err`, that occured on the specified `transport`
662
// up from this instance if `emitErrs` has been set.
663
//
664
Logger.prototype._onError = function (transport, err) {
665
if (this.emitErrs) {
666
this.emit('error', err, transport);
667
}
668
};
669
670