diff --git a/lib/winston-mongodb.js b/lib/winston-mongodb.js index c87fe9c..a9be5c8 100644 --- a/lib/winston-mongodb.js +++ b/lib/winston-mongodb.js @@ -31,18 +31,17 @@ var MongoDB = exports.MongoDB = function (options) { this.silent = options.silent || false; this.username = options.username || null; this.password = options.password || null; - this.timeout = options.timeout || 10000; - if (!(options.keepAlive === true)) { - this.keepAlive = false; - if (!options.timeout && options.keepAlive) { - // Backward compatibility for setting timeout via keepAlive. - this.timeout = options.keepAlive; - } + this.errortimeout = options.errortimeout || 10000; + if (options.keepAlive !== true) { + // Backward compatibility for timeout delivered in keepAlive parameter. + this.timeout = options.timeout || options.keepAlive || 10000; } this.state = 'unopened'; + this.timeoutId = null; this.pending = []; - this.client = new mongodb.Db(this.db, new mongodb.Server(this.host, this.port, {}), { + this.server = new mongodb.Server(this.host, this.port, {}); + this.client = new mongodb.Db(this.db, this.server, { native_parser: false }); }; @@ -69,42 +68,68 @@ winston.transports.MongoDB = MongoDB; MongoDB.prototype.log = function (level, msg, meta, callback) { var self = this; - if (this.silent) { - return callback(null, true); - } + // Avoid reentrancy that can be not assumed by database code. + // If database logs, better not to call database itself in the same call. + process.nextTick(function () { - this.open(function (err) { - if (err) { - self.emit('error', err); - return callback(err, null); + if (self.silent) { + return callback(null, true); } - self._db.collection(self.collection, function (err, col) { + self.open(function (err) { if (err) { self.emit('error', err); return callback(err, null); } - var entry = { - timestamp: new Date(), // RFC3339/ISO8601 format instead of common.timestamp() - level: level, - message: msg, - meta: meta - }; - - col.save(entry, { safe: self.safe }, function (err, doc) { + self._db.collection(self.collection, function (err, col) { if (err) { self.emit('error', err); return callback(err, null); } - self.emit('logged'); - return callback(null, true); + var entry = { + timestamp: new Date(), // RFC3339/ISO8601 format instead of common.timestamp() + level: level, + message: msg, + meta: meta + }; + + col.save(entry, { safe: self.safe }, function (err, doc) { + if (err) { + self.emit('error', err); + return callback(err, null); + } + + self.emit('logged'); + // Delay timeout to start from the last successful log. + self.setTimeout(); + return callback(null, true); + }); }); }); }); }; +MongoDB.prototype.setTimeout = function () { + var self = this; + if (!self.timeout) { + return; + } + if (self.timeoutId) { + clearTimeout(self.timeoutId); + } + // + // Set a timeout to close the client connection unless `self.keepAlive` + // has been set to true in which case it is the responsibility of the + // programmer to close the underlying connection. + // + self.timeoutId = setTimeout(function () { + self.client.close(); + self.state = 'unopened'; + }, self.timeout); +} + // // ### function open (callback) // #### @callback {function} Continuation to respond to when complete @@ -114,25 +139,25 @@ MongoDB.prototype.log = function (level, msg, meta, callback) { MongoDB.prototype.open = function (callback) { var self = this; - if (this.state === 'opening' || this.state === 'unopened') { + if (self.state === 'opening' || self.state === 'unopened') { // // While opening our MongoDB connection, append any callback // to a list that is managed by this instance. // - this.pending.push(callback); + self.pending.push(callback); - if (this.state === 'opening') { + if (self.state === 'opening') { return; } } - else if (this.state === 'opened') { + else if (self.state === 'opened') { return callback(); } - else if (this.state === 'error') { + else if (self.state === 'error') { return callback(self.error); } - function completion(err) { + function flushCallbacks(err) { // // Iterate over all callbacks that have accumulated during // the creation of the TCP socket. @@ -143,37 +168,29 @@ MongoDB.prototype.open = function (callback) { // Quickly truncate the Array (this is more performant). self.pending.length = 0; - - // - // Set a timeout to close the client connection unless `self.keepAlive` - // has been set to true in which case it is the responsibility of the - // programmer to close the underlying connection. - // - if (!self.keepAlive || err) { - setTimeout(function () { - self.state = 'unopened'; - if (self._db) { - // Connection was opened. - self._db.close(); - } - }, self.timeout); - } } function onError(err) { self.state = 'error'; self.error = err; - completion(err); + flushCallbacks(err); + // Close to be able to attempt opening later. + self.client.close(); + // Retry new connection upon following request after error timeout expired. + setTimeout(function () { + self.state = 'unopened'; + }, self.errortimeout); } function onSuccess(db) { self.state = 'opened'; self._db = db; - completion(); + flushCallbacks(); + self.setTimeout(); } - this.state = 'opening'; - this.client.open(function (err, db) { + self.state = 'opening'; + self.client.open(function (err, db) { if (err) { return onError(err); }