cocalc/src / smc-project / node_modules / cliff / node_modules / winston / lib / winston / logger.js
50675 views/*1* logger.js: Core logger object used by winston.2*3* (C) 2010 Charlie Robbins4* MIT LICENCE5*6*/78var events = require('events'),9util = require('util'),10async = require('async'),11config = require('./config'),12common = require('./common'),13exception = require('./exception'),14Stream = require('stream').Stream;1516//17// Time constants18//19var ticksPerMillisecond = 10000;2021//22// ### function Logger (options)23// #### @options {Object} Options for this instance.24// Constructor function for the Logger object responsible25// for persisting log messages and metadata to one or more transports.26//27var Logger = exports.Logger = function (options) {28events.EventEmitter.call(this);29options = options || {};3031var self = this,32handleExceptions = false;3334//35// Set Levels and default logging level36//37this.padLevels = options.padLevels || false;38this.setLevels(options.levels);39if (options.colors) {40config.addColors(options.colors);41}4243//44// Hoist other options onto this instance.45//46this.level = options.level || 'info';47this.emitErrs = options.emitErrs || false;48this.stripColors = options.stripColors || false;49this.exitOnError = typeof options.exitOnError !== 'undefined'50? options.exitOnError51: true;5253//54// Setup other intelligent default settings.55//56this.transports = {};57this.rewriters = [];58this.exceptionHandlers = {};59this.profilers = {};60this._names = [];61this._hnames = [];6263if (options.transports) {64options.transports.forEach(function (transport) {65self.add(transport, null, true);6667if (transport.handleExceptions) {68handleExceptions = true;69}70});71}7273if (options.rewriters) {74options.rewriters.forEach(function (rewriter) {75self.addRewriter(rewriter);76});77}7879if (options.exceptionHandlers) {80handleExceptions = true;81options.exceptionHandlers.forEach(function (handler) {82self._hnames.push(handler.name);83self.exceptionHandlers[handler.name] = handler;84});85}8687if (options.handleExceptions || handleExceptions) {88this.handleExceptions();89}90};9192//93// Inherit from `events.EventEmitter`.94//95util.inherits(Logger, events.EventEmitter);9697//98// ### function extend (target)99// #### @target {Object} Target to extend.100// Extends the target object with a 'log' method101// along with a method for each level in this instance.102//103Logger.prototype.extend = function (target) {104var self = this;105['log', 'profile', 'startTimer'].concat(Object.keys(this.levels)).forEach(function (method) {106target[method] = function () {107return self[method].apply(self, arguments);108};109});110111return this;112};113114//115// ### function log (level, msg, [meta], callback)116// #### @level {string} Level at which to log the message.117// #### @msg {string} Message to log118// #### @meta {Object} **Optional** Additional metadata to attach119// #### @callback {function} Continuation to respond to when complete.120// Core logging method exposed to Winston. Metadata is optional.121//122Logger.prototype.log = function (level, msg) {123var self = this,124callback,125meta;126127if (arguments.length === 3) {128if (typeof arguments[2] === 'function') {129meta = {};130callback = arguments[2];131}132else if (typeof arguments[2] === 'object') {133meta = arguments[2];134}135}136else if (arguments.length === 4) {137meta = arguments[2];138callback = arguments[3];139}140141// If we should pad for levels, do so142if (this.padLevels) {143msg = new Array(this.levelLength - level.length + 1).join(' ') + msg;144}145146function onError (err) {147if (callback) {148callback(err);149}150else if (self.emitErrs) {151self.emit('error', err);152};153}154155if (this.transports.length === 0) {156return onError(new Error('Cannot log with no transports.'));157}158else if (typeof self.levels[level] === 'undefined') {159return onError(new Error('Unknown log level: ' + level));160}161162this.rewriters.forEach(function (rewriter) {163meta = rewriter(level, msg, meta);164});165166//167// For consideration of terminal 'color" programs like colors.js,168// which can add ANSI escape color codes to strings, we destyle the169// ANSI color escape codes when `this.stripColors` is set.170//171// see: http://en.wikipedia.org/wiki/ANSI_escape_code172//173if (this.stripColors) {174var code = /\u001b\[(\d+(;\d+)*)?m/g;175msg = ('' + msg).replace(code, '');176}177178//179// Log for each transport and emit 'logging' event180//181function emit(name, next) {182var transport = self.transports[name];183if ((transport.level && self.levels[transport.level] <= self.levels[level])184|| (!transport.level && self.levels[self.level] <= self.levels[level])) {185transport.log(level, msg, meta, function (err) {186if (err) {187err.transport = transport;188cb(err);189return next();190}191self.emit('logging', transport, level, msg, meta);192next();193});194} else {195next();196}197}198199//200// Respond to the callback201//202function cb(err) {203if (callback) {204if (err) return callback(err);205callback(null, level, msg, meta);206}207callback = null;208}209210async.forEach(this._names, emit, cb);211212return this;213};214215//216// ### function query (options, callback)217// #### @options {Object} Query options for this instance.218// #### @callback {function} Continuation to respond to when complete.219// Queries the all transports for this instance with the specified `options`.220// This will aggregate each transport's results into one object containing221// a property per transport.222//223Logger.prototype.query = function (options, callback) {224if (typeof options === 'function') {225callback = options;226options = {};227}228229var self = this,230options = options || {},231results = {},232query = common.clone(options.query) || {},233transports;234235//236// Helper function to query a single transport237//238function queryTransport(transport, next) {239if (options.query) {240options.query = transport.formatQuery(query);241}242243transport.query(options, function (err, results) {244if (err) {245return next(err);246}247248next(null, transport.formatResults(results, options.format));249});250}251252//253// Helper function to accumulate the results from254// `queryTransport` into the `results`.255//256function addResults (transport, next) {257queryTransport(transport, function (err, result) {258result = err || result;259if (result) {260results[transport.name] = result;261}262next();263});264}265266//267// If an explicit transport is being queried then268// respond with the results from only that transport269//270if (options.transport) {271options.transport = options.transport.toLowerCase();272return queryTransport(this.transports[options.transport], callback);273}274275//276// Create a list of all transports for this instance.277//278transports = this._names.map(function (name) {279return self.transports[name];280}).filter(function (transport) {281return !!transport.query;282});283284//285// Iterate over the transports in parallel setting the286// appropriate key in the `results`287//288async.forEach(transports, addResults, function () {289callback(null, results);290});291};292293//294// ### function stream (options)295// #### @options {Object} Stream options for this instance.296// Returns a log stream for all transports. Options object is optional.297//298Logger.prototype.stream = function (options) {299var self = this,300options = options || {},301out = new Stream,302streams = [],303transports;304305if (options.transport) {306var transport = this.transports[options.transport];307delete options.transport;308if (transport && transport.stream) {309return transport.stream(options);310}311}312313out._streams = streams;314out.destroy = function () {315var i = streams.length;316while (i--) streams[i].destroy();317};318319//320// Create a list of all transports for this instance.321//322transports = this._names.map(function (name) {323return self.transports[name];324}).filter(function (transport) {325return !!transport.stream;326});327328transports.forEach(function (transport) {329var stream = transport.stream(options);330if (!stream) return;331332streams.push(stream);333334stream.on('log', function (log) {335log.transport = log.transport || [];336log.transport.push(transport.name);337out.emit('log', log);338});339340stream.on('error', function (err) {341err.transport = err.transport || [];342err.transport.push(transport.name);343out.emit('error', err);344});345});346347return out;348};349350//351// ### function close ()352// Cleans up resources (streams, event listeners) for all353// transports associated with this instance (if necessary).354//355Logger.prototype.close = function () {356var self = this;357358this._names.forEach(function (name) {359var transport = self.transports[name];360if (transport && transport.close) {361transport.close();362}363});364};365366//367// ### function handleExceptions ()368// Handles `uncaughtException` events for the current process369//370Logger.prototype.handleExceptions = function () {371var args = Array.prototype.slice.call(arguments),372handlers = [],373self = this;374375args.forEach(function (a) {376if (Array.isArray(a)) {377handlers = handlers.concat(a);378}379else {380handlers.push(a);381}382});383384handlers.forEach(function (handler) {385self.exceptionHandlers[handler.name] = handler;386});387388this._hnames = Object.keys(self.exceptionHandlers);389390if (!this.catchExceptions) {391this.catchExceptions = this._uncaughtException.bind(this);392process.on('uncaughtException', this.catchExceptions);393}394};395396//397// ### function unhandleExceptions ()398// Removes any handlers to `uncaughtException` events399// for the current process400//401Logger.prototype.unhandleExceptions = function () {402var self = this;403404if (this.catchExceptions) {405Object.keys(this.exceptionHandlers).forEach(function (name) {406if (handler.close) {407handler.close();408}409});410411this.exceptionHandlers = {};412Object.keys(this.transports).forEach(function (name) {413var transport = self.transports[name];414if (transport.handleExceptions) {415transport.handleExceptions = false;416}417})418419process.removeListener('uncaughtException', this.catchExceptions);420this.catchExceptions = false;421}422};423424//425// ### function add (transport, [options])426// #### @transport {Transport} Prototype of the Transport object to add.427// #### @options {Object} **Optional** Options for the Transport to add.428// #### @instance {Boolean} **Optional** Value indicating if `transport` is already instantiated.429// Adds a transport of the specified type to this instance.430//431Logger.prototype.add = function (transport, options, created) {432var instance = created ? transport : (new (transport)(options));433434if (!instance.name && !instance.log) {435throw new Error('Unknown transport with no log() method');436}437else if (this.transports[instance.name]) {438throw new Error('Transport already attached: ' + instance.name);439}440441this.transports[instance.name] = instance;442this._names = Object.keys(this.transports);443444//445// Listen for the `error` event on the new Transport446//447instance._onError = this._onError.bind(this, instance)448instance.on('error', instance._onError);449450//451// If this transport has `handleExceptions` set to `true`452// and we are not already handling exceptions, do so.453//454if (instance.handleExceptions && !this.catchExceptions) {455this.handleExceptions();456}457458return this;459};460461//462// ### function addRewriter (transport, [options])463// #### @transport {Transport} Prototype of the Transport object to add.464// #### @options {Object} **Optional** Options for the Transport to add.465// #### @instance {Boolean} **Optional** Value indicating if `transport` is already instantiated.466// Adds a transport of the specified type to this instance.467//468Logger.prototype.addRewriter = function (rewriter) {469this.rewriters.push(rewriter);470}471472//473// ### function clear ()474// Remove all transports from this instance475//476Logger.prototype.clear = function () {477for (var name in this.transports) {478this.remove({ name: name });479}480};481482//483// ### function remove (transport)484// #### @transport {Transport} Transport to remove.485// Removes a transport of the specified type from this instance.486//487Logger.prototype.remove = function (transport) {488var name = transport.name || transport.prototype.name;489490if (!this.transports[name]) {491throw new Error('Transport ' + name + ' not attached to this instance');492}493494var instance = this.transports[name];495delete this.transports[name];496this._names = Object.keys(this.transports);497498if (instance.close) {499instance.close();500}501502instance.removeListener('error', instance._onError);503return this;504};505506var ProfileHandler = function (logger) {507this.logger = logger;508509this.start = Date.now();510511this.done = function (msg) {512var args, callback, meta;513args = Array.prototype.slice.call(arguments);514callback = typeof args[args.length - 1] === 'function' ? args.pop() : null;515meta = typeof args[args.length - 1] === 'object' ? args.pop() : {};516517meta.duration = (Date.now()) - this.start + 'ms';518519return this.logger.info(msg, meta, callback);520}521}522523Logger.prototype.startTimer = function () {524return new ProfileHandler(this);525}526527//528// ### function profile (id, [msg, meta, callback])529// #### @id {string} Unique id of the profiler530// #### @msg {string} **Optional** Message to log531// #### @meta {Object} **Optional** Additional metadata to attach532// #### @callback {function} **Optional** Continuation to respond to when complete.533// Tracks the time inbetween subsequent calls to this method534// with the same `id` parameter. The second call to this method535// will log the difference in milliseconds along with the message.536//537Logger.prototype.profile = function (id) {538var now = Date.now(), then, args,539msg, meta, callback;540541if (this.profilers[id]) {542then = this.profilers[id];543delete this.profilers[id];544545// Support variable arguments: msg, meta, callback546args = Array.prototype.slice.call(arguments);547callback = typeof args[args.length - 1] === 'function' ? args.pop() : null;548meta = typeof args[args.length - 1] === 'object' ? args.pop() : {};549msg = args.length === 2 ? args[1] : id;550551// Set the duration property of the metadata552meta.duration = now - then + 'ms';553return this.info(msg, meta, callback);554}555else {556this.profilers[id] = now;557}558559return this;560};561562//563// ### function setLevels (target)564// #### @target {Object} Target levels to use on this instance565// Sets the `target` levels specified on this instance.566//567Logger.prototype.setLevels = function (target) {568return common.setLevels(this, this.levels, target);569};570571//572// ### function cli ()573// Configures this instance to have the default574// settings for command-line interfaces: no timestamp,575// colors enabled, padded output, and additional levels.576//577Logger.prototype.cli = function () {578this.padLevels = true;579this.setLevels(config.cli.levels);580config.addColors(config.cli.colors);581582if (this.transports.console) {583this.transports.console.colorize = true;584this.transports.console.timestamp = false;585}586587return this;588};589590//591// ### @private function _uncaughtException (err)592// #### @err {Error} Error to handle593// Logs all relevant information around the `err` and594// exits the current process.595//596Logger.prototype._uncaughtException = function (err) {597var self = this,598responded = false,599info = exception.getAllInfo(err),600handlers = this._getExceptionHandlers(),601timeout,602doExit;603604//605// Calculate if we should exit on this error606//607doExit = typeof this.exitOnError === 'function'608? this.exitOnError(err)609: this.exitOnError;610611function logAndWait(transport, next) {612transport.logException('uncaughtException', info, next, err);613}614615function gracefulExit() {616if (doExit && !responded) {617//618// Remark: Currently ignoring any exceptions from transports619// when catching uncaught exceptions.620//621clearTimeout(timeout);622responded = true;623process.exit(1);624}625}626627if (!handlers || handlers.length === 0) {628return gracefulExit();629}630631//632// Log to all transports and allow the operation to take633// only up to `3000ms`.634//635async.forEach(handlers, logAndWait, gracefulExit);636if (doExit) {637timeout = setTimeout(gracefulExit, 3000);638}639};640641//642// ### @private function _getExceptionHandlers ()643// Returns the list of transports and exceptionHandlers644// for this instance.645//646Logger.prototype._getExceptionHandlers = function () {647var self = this;648649return this._hnames.map(function (name) {650return self.exceptionHandlers[name];651}).concat(this._names.map(function (name) {652return self.transports[name].handleExceptions && self.transports[name];653})).filter(Boolean);654};655656//657// ### @private function _onError (transport, err)658// #### @transport {Object} Transport on which the error occured659// #### @err {Error} Error that occurred on the transport660// Bubbles the error, `err`, that occured on the specified `transport`661// up from this instance if `emitErrs` has been set.662//663Logger.prototype._onError = function (transport, err) {664if (this.emitErrs) {665this.emit('error', err, transport);666}667};668669670