Skip to content

Commit

Permalink
[breaking] winston.defaultTransports is now winston.default.transports
Browse files Browse the repository at this point in the history
[api test] Expose .handleExceptions on default winston logger
[api test] Transports will only log exceptions if `.handleExceptions` is true
  • Loading branch information
indexzero committed Aug 22, 2011
1 parent 120ae09 commit 3111d93
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 94 deletions.
24 changes: 19 additions & 5 deletions lib/winston.js
Expand Up @@ -43,8 +43,18 @@ var defaultLogger = new winston.Logger({
//
// Pass through the target methods onto `winston.
//
var methods = [
'log',
'add',
'remove',
'profile',
'extend',
'cli',
'handleExceptions',
'unhandleExceptions'
];
common.setLevels(winston, null, defaultLogger.levels);
['log', 'add', 'remove', 'profile', 'extend', 'cli'].forEach(function (method) {
methods.forEach(function (method) {
winston[method] = function () {
return defaultLogger[method].apply(defaultLogger, arguments);
};
Expand Down Expand Up @@ -96,11 +106,15 @@ winston.setLevels = function (target) {
});

//
// @transports {Object}
// The default transports for the default winston logger.
// @default {Object}
// The default transports and exceptionHandlers for
// the default winston logger.
//
Object.defineProperty(winston, 'defaultTransports', {
Object.defineProperty(winston, 'default', {
get: function () {
return defaultLogger.transports;
return {
transports: defaultLogger.transports,
exceptionHandlers: defaultLogger.exceptionHandlers
};
}
});
189 changes: 130 additions & 59 deletions lib/winston/logger.js
Expand Up @@ -43,18 +43,26 @@ var Logger = exports.Logger = function (options) {
//
// Setup other intelligent default settings.
//
this.level = options.level || 'info';
this.emitErrs = options.emitErrs || false;
this.stripColors = options.stripColors || false;
this.transports = {};
this.profilers = {};
this._names = [];
this.level = options.level || 'info';
this.emitErrs = options.emitErrs || false;
this.stripColors = options.stripColors || false;
this.transports = {};
this.exceptionHandlers = {};
this.profilers = {};
this._names = [];
this._hnames = [];

if (options.transports) {
options.transports.forEach(function (transport) {
self._names.push(transport.name);
self.transports[transport.name] = transport;
});
self.add(transport, null, true);
});
}

if (options.exceptionHandlers) {
options.exceptionHandlers.forEach(function (handler) {
self._hnames.push(handler.name);
self.exceptionHandlers[handler.name] = handler;
});
}

if (options.handleExceptions) {
Expand Down Expand Up @@ -164,61 +172,73 @@ Logger.prototype.log = function (level, msg) {
return this;
};

//
// ### function handleExceptions ()
// Handles `uncaughtException` events for the current process
//
Logger.prototype.handleExceptions = function () {
var args = Array.prototype.slice.call(arguments),
handlers = [],
self = this;

args.forEach(function (a) {
if (Array.isArray(a)) {
handlers = handlers.concat(a);
}
else {
handlers.push(a);
}
});

handlers.forEach(function (handler) {
self.exceptionHandlers[handler.name] = handler;
});

this._hnames = Object.keys(self.exceptionHandlers);

if (!this.catchExceptions) {
this.catchExceptions = true;
process.on('uncaughtException', this._uncaughtException.bind(this));
this.catchExceptions = this._uncaughtException.bind(this);
process.on('uncaughtException', this.catchExceptions);
}
};

//
// ### function unhandleExceptions ()
// Removes any handlers to `uncaughtException` events
// for the current process
//
Logger.prototype.unhandleExceptions = function () {
if (this.catchExceptions) {
this.catchExceptions = false;
process.removeListener('uncaughtException', this._uncaughtException);
}
};

Logger.prototype._uncaughtException = function (err) {
var self = this,
responded = false,
info = exception.getAllInfo(err),
timeout;

function logAndWait (name, next) {
var transport = self.transports['file'];
transport.logException('uncaughtException', info, next);
}
var self = this;

function gracefulExit () {
if (!responded) {
//
// Remark: Currently ignoring any exceptions from transports
// when catching uncaught exceptions.
//
console.dir(timeout);
clearTimeout(timeout);
responded = true;
process.exit(1);
}
if (this.catchExceptions) {
Object.keys(this.exceptionHandlers).forEach(function (name) {
if (handler.close) {
handler.close();
}
});

this.exceptionHandlers = {};
Object.keys(this.transports).forEach(function (name) {
var transport = self.transports[name];
if (transport.handleExceptions) {
transport.handleExceptions = false;
}
})

process.removeListener('uncaughtException', this.catchExceptions);
this.catchExceptions = false;
}

//
// Log to all transports and allow the operation to take
// only up to `3000ms`.
//
async.forEach(this._names, logAndWait, gracefulExit);
timeout = setTimeout(gracefulExit, 3000);
};


//
// ### function add (transport, [options])
// #### @transport {Transport} Prototype of the Transport object to add.
// #### @options {Object} **Optional** Options for the Transport to add.
// #### @instance {Boolean} **Optional** Value indicating if `transport` is already instantiated.
// Adds a transport of the specified type to this instance.
//
Logger.prototype.add = function (transport, options) {
var instance = (new (transport)(options));
Logger.prototype.add = function (transport, options, created) {
var instance = created ? transport : (new (transport)(options));

if (!instance.name && !instance.log) {
throw new Error('Unknown transport with no log() method');
Expand All @@ -230,13 +250,11 @@ Logger.prototype.add = function (transport, options) {
this.transports[instance.name] = instance;
this._names = Object.keys(this.transports);

if (instance.on) {
//
// If the instance has an `on` method
// then listen for the `'error'` event.
//
instance.on('error', this._onError.bind(this, instance));
}
//
// Listen for the `error` event on the new Transport
//
instance._onError = this._onError.bind(this, instance)
instance.on('error', instance._onError);

return this;
};
Expand All @@ -261,10 +279,7 @@ Logger.prototype.remove = function (transport) {
instance.close();
}

if (instance.removeListener) {
instance.removeListener('error', this._onError);
}

instance.removeListener('error', instance._onError);
return this;
};

Expand Down Expand Up @@ -331,6 +346,62 @@ Logger.prototype.cli = function () {
return this;
};

//
// ### @private function _uncaughtException (err)
// #### @err {Error} Error to handle
// Logs all relevant information around the `err` and
// exits the current process.
//
Logger.prototype._uncaughtException = function (err) {
var self = this,
responded = false,
info = exception.getAllInfo(err),
handlers = this._getExceptionHandlers(),
timeout;

function logAndWait (transport, next) {
transport.logException('uncaughtException', info, next);
}

function gracefulExit () {
if (!responded) {
//
// Remark: Currently ignoring any exceptions from transports
// when catching uncaught exceptions.
//
clearTimeout(timeout);
responded = true;
process.exit(1);
}
}

if (!handlers || handlers.length === 0) {
return gracefulExit();
}

//
// Log to all transports and allow the operation to take
// only up to `3000ms`.
//
async.forEach(handlers, logAndWait, gracefulExit);
timeout = setTimeout(gracefulExit, 3000);
};

//
// ### @private function _getExceptionHandlers ()
// Returns the list of transports and exceptionHandlers
// for this instance.
//
Logger.prototype._getExceptionHandlers = function () {
var self = this;

return this._hnames.map(function (name) {
return self.exceptionHandlers[name];
}).concat(this._names.map(function (name) {
return self.transports[name].handleExceptions && self.transports[name];
})).filter(Boolean);
};

//
// ### @private function _onError (transport, err)
// #### @transport {Object} Transport on which the error occured
Expand Down
7 changes: 4 additions & 3 deletions lib/winston/transports/transport.js
Expand Up @@ -18,9 +18,10 @@ var events = require('events'),
var Transport = exports.Transport = function (options) {
events.EventEmitter.call(this);

options = options || {};
this.level = options.level || 'info';
this.silent = options.silent || false;
options = options || {};
this.level = options.level || 'info';
this.silent = options.silent || false;
this.handleExceptions = options.handleExceptions || false;
};

//
Expand Down
21 changes: 21 additions & 0 deletions test/fixtures/scripts/default-exceptions.js
@@ -0,0 +1,21 @@
/*
* default-exceptions.js: A test fixture for logging exceptions with the default winston logger.
*
* (C) 2011 Charlie Robbins
* MIT LICENCE
*
*/

var path = require('path'),
winston = require('../../../lib/winston');

winston.handleExceptions([
new (winston.transports.File)({
filename: path.join(__dirname, '..', 'logs', 'default-exception.log'),
handleExceptions: true
})
]);

setTimeout(function () {
throw new Error('OH NOES! It failed!');
}, 1000);
Expand Up @@ -7,11 +7,14 @@
*/

var path = require('path'),
winston = require('../../lib/winston');
winston = require('../../../lib/winston');

var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({ filename: path.join(__dirname, 'logs', 'exception.log') })
new (winston.transports.File)({
filename: path.join(__dirname, '..', 'logs', 'exception.log'),
handleExceptions: true
})
]
});

Expand Down
26 changes: 26 additions & 0 deletions test/fixtures/scripts/unhandle-exceptions.js
@@ -0,0 +1,26 @@
/*
* unhandle-exceptions.js: A test fixture for using `.unhandleExceptions()` winston.
*
* (C) 2011 Charlie Robbins
* MIT LICENCE
*
*/

var path = require('path'),
winston = require('../../../lib/winston');

var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({
filename: path.join(__dirname, '..', 'logs', 'unhandle-exception.log'),
handleExceptions: true
})
]
});

logger.handleExceptions();
logger.unhandleExceptions();

setTimeout(function () {
throw new Error('OH NOES! It failed!');
}, 1000);

0 comments on commit 3111d93

Please sign in to comment.