* migrated to mongodb 2.x driver;
* changed configuration format to MongoDB uri string;
* added support of passing preconnected db object instead of MongoDB uri string;
* added support of passing MongoDB connection parameters in options property;
* added support of replica sets through new options and db properties;
* migrated to [Semantic Versioning](http://semver.org/) in package versions names;
* changed comments format to JSDoc;
* removed authDb from configuration options (it's impossible to handle all
possible authorization scenarios, so, if you need to use complicated
authorization pattern, please provide winston-mongodb with already prepared
db connection object).
This commit is contained in:
Yurij Mikhalevich 2015-02-28 15:47:57 +03:00
Родитель 15dbb82af1
Коммит bf8a1c0925
8 изменённых файлов: 391 добавлений и 541 удалений

Просмотреть файл

@ -3,6 +3,9 @@
A MongoDB transport for [winston][0].
Current version supports only mongodb driver version 2.x. If you want to use
winston-mongodb with mongodb version 1.4.x use winston-mongodb <1.x.
## Motivation
`tldr;?`: To break the [winston][0] codebase into small modules that work
together.
@ -16,10 +19,10 @@ and a File is overkill.
``` js
var winston = require('winston');
//
// Requiring `winston-mongodb` will expose
// `winston.transports.MongoDB`
//
/**
* Requiring `winston-mongodb` will expose
* `winston.transports.MongoDB`
*/
require('winston-mongodb').MongoDB;
winston.add(winston.transports.MongoDB, options);
@ -31,43 +34,25 @@ The MongoDB transport takes the following options. 'db' is required:
'info'.
* __silent:__ Boolean flag indicating whether to suppress output, defaults to
false.
* __db:__ The name of the database you want to log to.
* __db:__ MongoDB connection uri or preconnected db object.
* __options:__ MongoDB connection parameters (optional, defaults to
`{db: {native_parser: true}, server: {poolSize: 2, socketOptions: {autoReconnect: true}}}`).
* __collection__: The name of the collection you want to store log messages in,
defaults to 'logs'.
* __safe:__ Boolean indicating if you want eventual consistency on your log
messages, if set to true it requires an extra round trip to the server to ensure the write was committed, defaults to true.
* __nativeParser:__ Boolean indicating if you want the driver to use native
parser feature or not.
* __host:__ The host running MongoDB, defaults to localhost.
* __port:__ The port on the host that MongoDB is running on, defaults to
MongoDB's default port.
* __username:__ The username to use when logging into MongoDB.
* __password:__ The password to use when logging into MongoDB. If you don't
supply a username and password it will not use MongoDB authentication.
* __errorTimeout:__ Reconnect timeout upon connection error from Mongo,
defaults to 10 seconds (10000).
* __timeout:__ Timeout for keeping idle connection to Mongo alive, defaults to
10 seconds (10000).
* __storeHost:__ Boolean indicating if you want to store machine hostname in
logs entry, if set to true it populates MongoDB entry with 'hostname' field,
which stores os.hostname() value.
* __username:__ The username to use when logging into MongoDB.
* __password:__ The password to use when logging into MongoDB. If you don't
supply a username and password it will not use MongoDB authentication.
* __label:__ Label stored with entry object if defined.
* __ssl:__ Boolean indicating if you want to use SSL connections or not.
* __authDb:__ Authentication database object.
* __replSet:__ Replica set name.
* __hosts:__ Array of replica set hosts (in format
`{host: 'string', port: 'number'}`)
* __dbUri:__ Alternative way of specifying database connection data. Supported
specifying database, host, port, username, password and replica sets.
* __name:__ Transport instance identifier. Useful if you need to create multiple MongoDB transports.
* __name:__ Transport instance identifier. Useful if you need to create multiple
MongoDB transports.
* __capped:__ In case this property is true, winston-mongodb will try to create
new log collection as capped, defaults to false.
* __cappedSize:__ Size of logs capped collection in bytes, defaults to 10000000.
*Notice:* __db__ is required. You should specify it directly or in __dbUri__.
*ReplicaSet Notice:* If you use replica set, __db__, __replSet__ and __hosts__
are required. They may also be specified in __dbUri__.
*Metadata:* Logged as a native JSON object in meta property.
*Metadata:* Logged as a native JSON object in 'meta' property.
*Logging unhandled exceptions:* For logging unhandled exceptions specify
winston-mongodb as `handleExceptions` logger according to winston documentation.
@ -82,14 +67,6 @@ settled by mongodb, defaults to `false`.
## Installation
### Installing npm (node package manager)
``` bash
$ curl http://npmjs.org/install.sh | sh
```
### Installing winston-mongodb
``` bash
$ npm install winston
$ npm install winston-mongodb
@ -97,6 +74,20 @@ settled by mongodb, defaults to `false`.
## Changelog
### Brief 1.0.0 changelog
* migrated to mongodb 2.x driver;
* changed configuration format to MongoDB uri string;
* added support of passing preconnected db object instead of MongoDB uri string;
* added support of passing MongoDB connection parameters in options property;
* added support of replica sets through new options and db properties;
* migrated to [Semantic Versioning](http://semver.org/) in package versions names;
* changed comments format to JSDoc;
* removed authDb from configuration options (it's impossible to handle all
possible authorization scenarios, so, if you need to use complicated
authorization pattern, please provide winston-mongodb with already prepared
db connection object).
### Brief 0.5 changelog
* metadata is now stored into separate property `meta`; so, there is no risk

Просмотреть файл

@ -1,19 +1,18 @@
/*
* mongodb.js: Transport for outputting to a MongoDB database
*
* (C) 2014 Yurij Mikhalevich
* MIT LICENCE
*
/**
* @module helpers
* @fileoverview Helpers for winston-mongodb
* @license MIT
* @author 0@39.yt (Yurij Mikhalevich)
*/
var common = require('winston/lib/winston/common');
//
// ### function prepareMetaData (meta)
// #### @meta {*} Metadata
// Prepares metadata to store into database.
//
exports.prepareMetaData = function (meta) {
/**
* Prepares metadata to store into database.
* @param {*} meta Metadata
* @return {*}
*/
exports.prepareMetaData = function(meta) {
if (typeof meta === 'object' && meta !== null) {
makeObjectNonCircular(meta);
cleanFieldNames(meta);
@ -31,11 +30,10 @@ exports.prepareMetaData = function (meta) {
};
//
// ### function cleanFieldNames (object)
// #### @object {Object} Object to clean
// Removes unexpected characters from metadata field names.
//
/**
* Removes unexpected characters from metadata field names.
* @param {Object} object Object to clean
*/
function cleanFieldNames(object) {
for (var field in object) {
if (!object.hasOwnProperty(field)) {
@ -59,27 +57,26 @@ function cleanFieldNames(object) {
}
//
// ### function makeObjectNonCircular (node, [parents])
// #### @node {Object} Current object or its leaf
// #### @parents {array} Object's parents
// Cleans object from circular references, replaces them with string
// "[Circular]"
//
function makeObjectNonCircular(node, parents) {
parents = parents || [];
/**
* Cleans object from circular references, replaces them with string
* "[Circular]"
* @param {Object} node Current object or its leaf
* @param {Array=} opt_parents Object's parents
*/
function makeObjectNonCircular(node, opt_parents) {
opt_parents = opt_parents || [];
var keys = Object.keys(node);
var i;
var value;
parents.push(node); // add self to current path
opt_parents.push(node); // add self to current path
for (i = keys.length - 1; i >= 0; --i) {
value = node[keys[i]];
if (value && typeof value === 'object') {
if (parents.indexOf(value) === -1) {
if (opt_parents.indexOf(value) === -1) {
// check child nodes
arguments.callee(value, parents);
arguments.callee(value, opt_parents);
} else {
// circularity detected!
node[keys[i]] = '[Circular]';
@ -87,5 +84,5 @@ function makeObjectNonCircular(node, parents) {
}
}
parents.pop();
opt_parents.pop();
}

Просмотреть файл

@ -1,277 +1,302 @@
/*
* mongodb.js: Transport for outputting to a MongoDB database
*
* (C) 2010 Charlie Robbins, Yurij Mikhalevich, Kendrick Taylor
* MIT LICENCE
*
/**
* @module winston-mongodb
* @fileoverview Transport for outputting to a MongoDB database
* @license MIT
* @author charlie@nodejitsu.com (Charlie Robbins)
* @author 0@39.yt (Yurij Mikhalevich)
*/
var util = require('util');
var os = require('os');
var muri = require('muri');
var mongodb = require('mongodb');
var winston = require('winston');
var Stream = require('stream').Stream;
var helpers = require('./helpers');
//
// ### function MongoDB (options)
// Constructor for the MongoDB transport object.
//
var MongoDB = exports.MongoDB = function (options) {
/**
* Constructor for the MongoDB transport object.
* @constructor
* @param {Object} options
* @param {string=info} options.level Level of messages that this transport
* should log.
* @param {boolean=false} options.silent Boolean flag indicating whether to
* suppress output.
* @param {string|Object} options.db MongoDB connection uri or preconnected db
* object.
* @param {Object} options.options MongoDB connection parameters
* (optional, defaults to `{db: {native_parser: true},
* server: {poolSize: 2, socketOptions: {autoReconnect: true}}}`).
* @param {string=logs} options.collection The name of the collection you want
* to store log messages in.
* @param {boolean=false} options.storeHost Boolean indicating if you want to
* store machine hostname in logs entry, if set to true it populates MongoDB
* entry with 'hostname' field, which stores os.hostname() value.
* @param {string} options.username The username to use when logging into
* MongoDB.
* @param {string} options.password The password to use when logging into
* MongoDB. If you don't supply a username and password it will not use MongoDB
* authentication.
* @param {string} options.label Label stored with entry object if defined.
* @param {string} options.name Transport instance identifier. Useful if you
* need to create multiple MongoDB transports.
*/
var MongoDB = exports.MongoDB = function(options) {
winston.Transport.call(this, options);
options = (options || {});
if (options.dbUri) {
var uriOptions = muri(options.dbUri);
options.db = uriOptions.db;
if (uriOptions.options.replicaSet) {
options.replSet = uriOptions.options.replicaSet;
options.hosts = uriOptions.hosts;
} else if (uriOptions.hosts && uriOptions.hosts[0]) {
options.host = uriOptions.hosts[0].host;
options.port = uriOptions.hosts[0].port;
}
if (uriOptions.auth) {
options.username = uriOptions.auth.user;
options.password = uriOptions.auth.pass;
}
}
if (!options.db) {
throw new Error('Cannot log to MongoDB without database name.');
throw new Error('You should provide db to log to.');
}
var self = this;
this.name = options.name || 'mongodb';
this.db = options.db;
this.host = (options.host || 'localhost');
this.port = (options.port || mongodb.Connection.DEFAULT_PORT);
this.replSet = (options.replSet || null);
this.hosts = (options.hosts || null);
this.collection = (options.collection || 'logs');
this.safe = ((undefined === options.safe) ? true : options.safe);
this.level = (options.level || 'info');
this.silent = (options.silent || false);
this.username = (options.username || null);
this.password = (options.password || null);
this.errorTimeout = (options.errorTimeout || 10000);
this.capped = options.capped;
this.cappedSize = (options.cappedSize || 10000000);
this.nativeParser = (options.nativeParser || false);
this.storeHost = options.storeHost;
this.ssl = (options.ssl || false);
this.authDb = (options.authDb || null);
this.label = options.label;
// TODO: possibly go by docs (`max`) instead
// this.length = options.length || 200;
this.name = options.name || 'mongodb';
this.db = options.db;
this.options = options.options;
if (!this.options) {
this.options = {
db: {
native_parser: true
},
server: {
poolSize: 2,
socketOptions: {autoReconnect: true}
}
};
}
this.collection = (options.collection || 'logs');
this.level = (options.level || 'info');
this.silent = options.silent;
this.username = options.username;
this.password = options.password;
this.storeHost = options.storeHost;
this.label = options.label;
this.capped = options.capped;
this.cappedSize = (options.cappedSize || 10000000);
if (this.storeHost) {
this.hostname = os.hostname();
}
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._opQuery = [];
if (this.replSet) {
var servers = [];
this.hosts.forEach(function(host) {
servers.push(new mongodb.Server(host.host || 'localhost',
host.port || mongodb.Connection.DEFAULT_PORT));
});
this.server = new mongodb.ReplSet(servers, {
rs_name: this.replSet,
ssl: this.ssl
});
} else {
this.server = new mongodb.Server(this.host, this.port, {
ssl: this.ssl
});
}
this.client = new mongodb.MongoClient(this.server, {
native_parser: this.nativeParser,
safe: this.safe
});
this.server.on('error', function (err) {
// Close session. Next log will reopen.
self.close();
});
this.client.on('error', function (err) {
// Close session. Next log will reopen.
self.close();
});
};
//
// Inherit from `winston.Transport`.
//
util.inherits(MongoDB, winston.Transport);
//
// Define a getter so that `winston.transports.MongoDB`
// is available and thus backwards compatible.
//
winston.transports.MongoDB = MongoDB;
//
// ### function log (level, msg, [meta], callback)
// #### @level {string} Level at which to log the message.
// #### @msg {string} Message to log
// #### @meta {Object} **Optional** Additional metadata to attach
// #### @callback {function} Continuation to respond to when complete.
// Core logging method exposed to Winston. Metadata is optional.
//
MongoDB.prototype.log = function (level, msg, meta, callback) {
var self = this;
//
// 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 () {
function processOpQuery() {
self._opQuery.forEach(function(operation) {
self[operation.method].apply(self, operation.args);
});
delete self._opQuery;
}
function createCollection(cb) {
var opts = {};
if (self.capped) {
opts = {capped: true, size: self.cappedSize};
}
self.mainDb.createCollection(self.collection, opts, function() {
cb(null);
});
}
function authorizeDb(cb) {
if (self.username && self.password) {
self.mainDb.authenticate(self.username, self.password,
function(err, result) {
if (err) {
console.error('winston-mongodb: error initialising logger', err);
self.mainDb.close();
return;
}
if (!result) {
console.error('winston-mongodb: invalid username or password');
self.mainDb.close();
return;
}
cb(null);
}
);
}
cb(null);
}
if ('string' === typeof this.db) {
mongodb.MongoClient.connect(this.db, this.options, function(err, db) {
if (err) {
console.error('winston-mongodb: error initialising logger', err);
return;
}
self.mainDb = db;
authorizeDb(function() {
createCollection(processOpQuery);
});
});
} else {
this.mainDb = this.db;
authorizeDb(function() {
createCollection(processOpQuery);
});
}
};
/**
* Inherit from `winston.Transport`.
*/
util.inherits(MongoDB, winston.Transport);
/**
* Define a getter so that `winston.transports.MongoDB`
* is available and thus backwards compatible.
*/
winston.transports.MongoDB = MongoDB;
/**
* Core logging method exposed to Winston. Metadata is optional.
* @param {string} level Level at which to log the message.
* @param {string} msg Message to log
* @param {Object=} opt_meta Additional metadata to attach
* @param {Function} callback Continuation to respond to when complete.
*/
MongoDB.prototype.log = function(level, msg, opt_meta, callback) {
if (!this.mainDb) {
this._opQuery.push({
method: 'log',
args: arguments
});
return;
}
var self = this;
/**
* 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() {
if (self.silent) {
return callback(null, true);
callback(null, true);
return;
}
self.open(function (err) {
function onError(err) {
self.emit('error', err);
callback(err, null);
}
self.mainDb.collection(self.collection, function(err, col) {
if (err) {
return onError(err);
onError(err);
return;
}
// 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.timeout) {
if (self.timeoutId) {
clearTimeout(self.timeoutId);
}
self.timeoutId = setTimeout(function () {
// The session is idle. Closing it.
self.close();
}, self.timeout);
var entry = {};
entry.message = msg;
entry.timestamp = new Date();
entry.level = level;
entry.meta = helpers.prepareMetaData(opt_meta);
if (self.storeHost) {
entry.hostname = self.hostname;
}
if (self.label) {
entry.label = self.label;
}
function onError(err) {
self.close();
self.emit('error', err);
callback(err, null);
}
self._db.collection(self.collection, function (err, col) {
col.insertOne(entry, function(err) {
if (err) {
return onError(err);
onError(err);
return;
}
var entry = {};
entry.message = msg;
entry.timestamp = new Date;
entry.level = level;
entry.meta = helpers.prepareMetaData(meta);
if (self.storeHost) {
entry.hostname = self.hostname;
}
if (self.label) {
entry.label = self.label;
}
col.save(entry, { safe: self.safe }, function (err) {
if (err) {
return onError(err);
}
self.emit('logged');
callback(null, true);
});
self.emit('logged');
callback(null, true);
});
});
});
};
//
// ### function query (options, callback)
// #### @options {Object} Loggly-like query options for this instance.
// #### @callback {function} Continuation to respond to when complete.
// Query the transport. Options object is optional.
//
MongoDB.prototype.query = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (this.state !== 'opened') {
var self = this;
return this.open(function () {
return self.query(options, callback);
/**
* Query the transport. Options object is optional.
* @param {Object=} opt_options Loggly-like query options for this instance.
* @param {Function} callback Continuation to respond to when complete.
* @return {*}
*/
MongoDB.prototype.query = function(opt_options, callback) {
if (!this.mainDb) {
this._opQuery.push({
method: 'query',
args: arguments
});
return;
}
var self = this,
options = this.normalizeQuery(options),
query,
opt,
fields;
if ('function' === typeof opt_options) {
callback = opt_options;
opt_options = {};
}
query = {
var options = this.normalizeQuery(opt_options);
var query = {
timestamp: {
$gte: options.from,
$lte: options.until
}
};
opt = {
var opt = {
skip: options.start,
limit: options.rows,
sort: { timestamp: options.order === 'desc' ? -1 : 1 }
sort: {timestamp: options.order === 'desc' ? -1 : 1}
};
if (options.fields) {
opt.fields = options.fields;
}
this._db.collection(this.collection, function (err, col) {
if (err) return callback(err);
col.find(query, opt).toArray(function (err, docs) {
if (err) return callback(err);
this.mainDb.collection(this.collection, function(err, col) {
if (err) {
callback(err);
return;
}
col.find(query, opt).toArray(function(err, docs) {
if (err) {
callback(err);
return;
}
if (!options.includeIds) {
docs.forEach(function (log) {
docs.forEach(function(log) {
delete log._id;
});
}
if (callback) callback(null, docs);
if (callback) {
callback(null, docs);
}
});
});
};
//
// ### function stream (options)
// #### @options {Object} Stream options for this instance.
// #### @stream {Object} Pass in a pre-existing stream.
// Returns a log stream for this transport. Options object is optional.
// This will only work with a capped collection.
//
MongoDB.prototype.stream = function (options, stream) {
var self = this,
options = options || {},
stream = stream || new Stream,
start = options.start;
if (this.state !== 'opened') {
this.open(function () {
self.stream(options, stream);
/**
* Returns a log stream for this transport. Options object is optional.
* This will only work with a capped collection.
* @param {Object} options Stream options for this instance.
* @param {Stream} stream Pass in a pre-existing stream.
* @return {Stream}
*/
MongoDB.prototype.stream = function(options, stream) {
options = options || {};
stream = stream || new Stream;
var self = this;
var start = options.start;
if (!this.mainDb) {
this._opQuery.push({
method: 'stream',
args: [options, stream]
});
return stream;
}
@ -285,11 +310,17 @@ MongoDB.prototype.stream = function (options, stream) {
}
if (start != null) {
this._db.collection(this.collection, function (err, col) {
if (err) return stream.emit('error', err);
col.find({}, { skip: start }).toArray(function (err, docs) {
if (err) return stream.emit('error', err);
docs.forEach(function (doc) {
this.mainDb.collection(this.collection, function(err, col) {
if (err) {
stream.emit('error', err);
return;
}
col.find({}, {skip: start}).toArray(function(err, docs) {
if (err) {
stream.emit('error', err);
return;
}
docs.forEach(function(doc) {
if (!options.includeIds) {
delete doc._id;
}
@ -303,65 +334,63 @@ MongoDB.prototype.stream = function (options, stream) {
return stream;
}
this._db.collection(this.collection, function (err, col) {
if (err) return stream.emit('error', err);
this.mainDb.collection(this.collection, function(err, col) {
if (err) {
stream.emit('error', err);
return;
}
if (stream.destroyed) return;
if (stream.destroyed) {
return;
}
var cursor = col.find({}, { tailable: true });
// tail cursor
var tail = cursor.stream();
stream.destroy = function() {
this.destroyed = true;
tail.destroy();
};
tail.on('data', function (doc) {
if (!options.includeIds) {
delete doc._id;
}
stream.emit('log', doc);
});
tail.on('error', function (err) {
if (typeof err !== 'object') {
err = new Error(err);
}
// hack because isCapped doesn't work
var notCappedError = 'tailable cursor requested on non capped collection';
if (err.message.indexOf(notCappedError,
err.message.length - notCappedError.length) !== -1) {
tail.destroy();
self.streamPoll(options, stream);
col.isCapped(function(err, capped) {
if (err) {
stream.emit('error', err);
return;
}
stream.emit('error', err);
if (capped) {
var cursor = col.find({}, {tailable: true});
stream.destroy = function() {
this.destroyed = true;
cursor.destroy();
};
cursor.on('data', function(doc) {
if (!options.includeIds) {
delete doc._id;
}
stream.emit('log', doc);
});
cursor.on('error', function(err) {
stream.emit('error', err);
});
} else {
self.streamPoll(options, stream);
}
});
});
return stream;
};
//
// ### function streamPoll (options)
// #### @options {Object} Stream options for this instance.
// #### @stream {Object} Pass in a pre-existing stream.
// Returns a log stream for this transport. Options object is optional.
//
MongoDB.prototype.streamPoll = function (options, stream) {
var self = this,
options = options || {},
stream = stream || new Stream,
start = options.start,
last;
if (this.state !== 'opened') {
this.open(function () {
self.streamPoll(options, stream);
/**
* Returns a log stream for this transport. Options object is optional.
* @param {Object} options Stream options for this instance.
* @param {Stream} stream Pass in a pre-existing stream.
* @return {Stream}
*/
MongoDB.prototype.streamPoll = function(options, stream) {
options = options || {};
stream = stream || new Stream;
var self = this;
var start = options.start;
var last;
if (!this.mainDb) {
this._opQuery.push({
method: 'streamPoll',
args: [options, stream]
});
return stream;
}
@ -379,15 +408,18 @@ MongoDB.prototype.streamPoll = function (options, stream) {
};
(function check() {
self._db.collection(self.collection, function (err, col) {
if (err) return stream.emit('error', err);
self.mainDb.collection(self.collection, function(err, col) {
if (err) {
stream.emit('error', err);
return;
}
var query = last
? { timestamp: { $gte: last } }
: {};
var query = last ? {timestamp: {$gte: last}} : {};
col.find(query).toArray(function (err, docs) {
if (stream.destroyed) return;
col.find(query).toArray(function(err, docs) {
if (stream.destroyed) {
return;
}
if (err) {
next();
@ -395,24 +427,26 @@ MongoDB.prototype.streamPoll = function (options, stream) {
return;
}
if (!docs.length) return next();
if (!docs.length) {
return next();
}
if (start == null) {
docs.forEach(function (doc) {
docs.forEach(function(doc) {
if (!options.includeIds) {
delete doc._id;
}
stream.emit('log', doc);
});
} else {
docs.forEach(function (doc) {
docs.forEach(function(doc) {
if (!options.includeIds) {
delete doc._id;
}
if (!start) {
stream.emit('log', doc);
} else {
start--;
start -= 1;
}
});
}
@ -430,139 +464,3 @@ MongoDB.prototype.streamPoll = function (options, stream) {
return stream;
};
//
// ### function _ensureCollection (callback)
// #### @db {Object} The database to create the collection in.
// #### @callback {function} Continuation to respond to when complete
// Attempt to create a capped collection if possible.
//
MongoDB.prototype._ensureCollection = function (db, callback) {
if (!this.capped) return callback(null);
var opt = { capped: true, size: this.cappedSize };
db.createCollection(this.collection, opt, function (err, col) {
callback(null);
});
};
//
// ### function open (callback)
// #### @callback {function} Continuation to respond to when complete
// Attempts to open a new connection to MongoDB. If one has not opened yet
// then the callback is enqueued for later flushing.
//
MongoDB.prototype.open = function (callback) {
var self = this;
if (this.state === 'opening' || this.state === 'unopened') {
//
// While opening our MongoDB connection, append any callback
// to a list that is managed by this instance.
//
this.pending.push(callback);
if (this.state === 'opening') {
return;
}
}
else if (this.state === 'opened') {
return callback();
}
else if (this.state === 'error') {
return callback(this.error);
}
//
// Flushes any pending log messages to MongoDB.
//
function flushPending(err) {
//
// Iterate over all callbacks that have accumulated during
// the creation of the TCP socket.
//
for (var i = 0; i < self.pending.length; i++) {
self.pending[i](err);
}
//
// Quickly truncate the Array (this is more performant).
//
self.pending.length = 0;
}
//
// Helper function which executes if there is an error
// establishing the connection.
//
function onError(err) {
self.state = 'error';
self.error = err;
flushPending(err);
//
// Close to be able to attempt opening later.
//
self.client.close();
//
// Retry new connection upon following request after error timeout expired.
//
setTimeout(function () {
//
// This is the only exit from error state.
//
self.state = 'unopened';
}, self.errorTimeout);
}
//
// Helper function which executes if the connection
// is established.
//
function onSuccess(client) {
self.state = 'opened';
self._db = client.db(self.db);
self._ensureCollection(self._db, function() {
flushPending();
});
}
this.state = 'opening';
this.client.open(function (err, client) {
if (err) {
return onError(err);
}
if (self.username && self.password) {
var authDb = self.authDb ? self.client.db(self.authDb) : client.db(self.db);
return authDb.authenticate(self.username, self.password, function (err) {
return err ? onError(err) : onSuccess(client);
});
}
onSuccess(client);
});
};
//
// ### function close ()
// Cleans up resources (streams, event listeners) for
// this instance (if necessary).
//
MongoDB.prototype.close = function () {
//
// Reset session if it is opened.
//
if (this.state === 'opened') {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
//
// Next time try to open new session.
//
this.client.close();
this.state = 'unopened';
}
};

Просмотреть файл

@ -1,28 +1,26 @@
{
"name": "winston-mongodb",
"version": "0.5.3",
"license": "MIT",
"version": "1.0.0",
"description": "A MongoDB transport for winston",
"author": "Charlie Robbins <charlie.robbins@gmail.com>",
"contributors": [
{ "name": "Yurij Mikhalevich", "email": "0@39.yt", "url": "https://39.yt/" },
{ "name": "Kendrick Taylor", "email": [ "sktayloriii@gmail.com", "yosefd@microsoft.com" ] },
{ "name": "Steve Dalby", "email": "steve@stevedalby.co.uk" }
{ "name": "Steve Dalby", "email": "steve@stevedalby.co.uk" }
],
"repository": {
"type": "git",
"url": "http://github.com/indexzero/winston-mongodb.git"
},
"keywords": [ "logging", "sysadmin", "tools", "winston", "mongodb", "log", "logger" ],
"keywords": ["logging", "sysadmin", "tools", "winston", "mongodb", "log", "logger"],
"dependencies": {
"mongodb": "1.4.x",
"muri": "0.3.x"
"mongodb": "2.0.x"
},
"devDependencies": {
"winston": "0.8.x",
"vows": "0.7.x"
"winston": "0.9.x",
"vows": "0.8.x"
},
"main": "./lib/winston-mongodb",
"scripts": { "test": "vows test/*-test.js --spec" },
"engines": { "node": ">= 0.4.0" },
"license": "MIT"
"mainDb": "./lib/winston-mongodb",
"scripts": { "test": "vows test/*-test.js --spec" }
}

0
test/fixtures/.gitkeep поставляемый
Просмотреть файл

8
test/fixtures/test-config.json поставляемый
Просмотреть файл

@ -1,8 +0,0 @@
{
"transports": {
"mongodb": {
"db": "winston",
"keepAlive": 1000
}
}
}

Просмотреть файл

@ -1,30 +0,0 @@
/*
* replica-set-test.js: Tests for instances of the MongoDB transport, configured
* for replica sets. Make sure, that replica set is properly configured.
*
* (C) 2014 Yurij Mikhalevich
*
*/
var vows = require('vows'),
winston = require('winston'),
transport = require('winston/test/transports/transport'),
MongoDB = require('../lib/winston-mongodb').MongoDB;
vows.describe('winston-mongodb').addBatch({
'With replica set, configured via object': transport(MongoDB, {
db: 'winston',
keepAlive: 1000,
replSet: 'rs0',
hosts: [
{port: 27018},
{port: 27019},
{port: 27020}
]
}),
'With replica set, configured via connection string': transport(MongoDB, {
keepAlive: 1000,
dbUri: 'mongodb://localhost:27018,localhost:27019,localhost:27020/winston?replicaSet=rs0'
})
}).export(module);

Просмотреть файл

@ -1,19 +1,23 @@
/*
* winston-mongodb-test.js: Tests for instances of the MongoDB transport
*
* (C) 2011 Charlie Robbins, Kendrick Taylor, Yurij Mikhalevich
* MIT LICENSE
*
/**
* @module winston-mongodb-test
* @fileoverview Tests for instances of the MongoDB transport
* @license MIT
* @author charlie@nodejitsu.com (Charlie Robbins)
* @author 0@39.yt (Yurij Mikhalevich)
*/
var vows = require('vows'),
winston = require('winston'),
transport = require('winston/test/transports/transport'),
MongoDB = require('../lib/winston-mongodb').MongoDB;
var vows = require('vows');
var transport = require('winston/test/transports/transport');
var MongoDB = require('../lib/winston-mongodb').MongoDB;
vows.describe('winston-mongodb').addBatch({
'An instance of the MongoDB Transport': transport(MongoDB, {
db: 'winston',
keepAlive: 1000
})
db: 'mongodb://localhost/winston'
}),
'And instance of the MongoDB Transport on capped collection':
transport(MongoDB, {
db: 'mongodb://localhost/winston',
capped: true,
collection: 'cappedLogs'
})
}).export(module);