Version 1.0.0 released:
* 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:
Родитель
15dbb82af1
Коммит
bf8a1c0925
75
README.md
75
README.md
|
@ -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';
|
||||
}
|
||||
};
|
||||
|
|
20
package.json
20
package.json
|
@ -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" }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче