even-better
Version:
Server and process monitoring plugin
273 lines (191 loc) • 7.75 kB
JavaScript
// Load modules
var Events = require('events');
var Http = require('http');
var Https = require('https');
var Os = require('os');
var Stream = require('stream');
var Hoek = require('hoek');
var Items = require('items');
var Wreck = require('wreck');
var Network = require('./network');
var Package = require('../package.json');
var ProcessMonitor = require('./process');
var Schema = require('./schema');
var System = require('./system');
var Utils = require('./utils');
// Declare internals
var internals = {
host: Os.hostname(),
appVer: Package.version
};
internals.defaults = {
opsInterval: 15000,
responseEvent: 'tail',
requestHeaders: false,
requestPayload: false,
responsePayload: false,
extensions: [],
reporters: [],
filter: {}
};
module.exports = internals.Monitor = function (server, options) {
options = options || {};
Hoek.assert(this.constructor === internals.Monitor, 'Monitor must be instantiated using new');
Hoek.assert(server, 'server required to create monitor');
options = Hoek.applyToDefaultsWithShallow(internals.defaults, options, ['reporters', 'httpAgents', 'httpsAgents']);
// Force them to be arrays
options.httpAgents = [].concat(options.httpAgents || Http.globalAgent);
options.httpsAgents = [].concat(options.httpsAgents || Https.globalAgent);
this.settings = options;
this._state = {
handlers: {},
extensions: {}
};
this._server = server;
this._dataStream = new Stream.Readable({ objectMode: true });
this._dataStream._read = Hoek.ignore;
// Options used to create Great Response
this._responseOptions = {
requestHeaders: this.settings.requestHeaders,
requestPayload: this.settings.requestPayload,
responsePayload: this.settings.responsePayload
};
// Validate settings
Schema.assert('monitorOptions', this.settings);
// Register as event emitter
Events.EventEmitter.call(this);
};
Hoek.inherits(internals.Monitor, Events.EventEmitter);
internals.Monitor.prototype.start = function (callback) {
var self = this;
var setupOpsMonitoring = function () {
var pmonitor = ProcessMonitor;
var os = System;
var network = self.network = new Network.Monitor(self._server);
var asyncOps = {
osload: os.loadavg,
osmem: os.mem,
osup: os.uptime,
psup: pmonitor.uptime,
psmem: pmonitor.memoryUsage,
psdelay: pmonitor.delay,
requests: network.requests.bind(network),
concurrents: network.concurrents.bind(network),
responseTimes: network.responseTimes.bind(network),
sockets: network.sockets.bind(network, self.settings.httpAgents, self.settings.httpsAgents)
};
// Set ops interval timer
return function () {
// Gather operational statistics in parallel
Items.parallel.execute(asyncOps, function (error, results) {
if (error) {
console.error(error);
}
else {
results.host = internals.host;
self.emit('ops', results);
}
network.reset();
});
};
};
Items.serial(this.settings.reporters, function (item, next) {
var reporter;
// If it has a reporter constructor, then create a new one, otherwise, assume it is
// a valid pre-constructed reporter
if (item.reporter) {
// If the supply a path or module node, try to load it
if (typeof item.reporter === 'string') {
item.reporter = require(item.reporter);
}
Hoek.assert(typeof item.reporter === 'function', 'reporter key must be a constructor function');
Hoek.assert(typeof item.events === 'object', 'reporter must specify events to filter on');
var Reporter = item.reporter;
reporter = new Reporter(item.events, item.config);
}
else {
reporter = item;
}
Hoek.assert(reporter.init, 'Every reporter object must have an init method');
reporter.init(self._dataStream, self, next);
}, function (error) {
if (error) {
return callback(error);
}
self._state.opsInterval = setInterval(setupOpsMonitoring(), self.settings.opsInterval);
self._state.opsHandler = self._opsHandler.bind(self);
self._state.wreckHandler = self._wreckHandler.bind(self);
self._state.handlers.log = self._logHandler.bind(self);
self._state.handlers['request-error'] = self._errorHandler.bind(self);
self._state.handlers[self.settings.responseEvent] = self._responseHandler.bind(self);
self._state.handlers.request = self._requestLogHandler.bind(self);
// Initialize Events
internals.iterateOverEventHash(self._state.handlers, function (event, handler) {
self._server.on(event, handler);
});
self.on('ops', self._state.opsHandler);
Wreck.on('response', self._state.wreckHandler);
// Events can not be any of ['log', 'request-error', 'ops', 'request', 'response', 'tail']
for (var i = 0, il = self.settings.extensions.length; i < il; ++i) {
var event = self.settings.extensions[i];
self._state.extensions[event] = self._extensionHandler.bind(self, event);
self._server.on(self.settings.extensions[i], self._state.extensions[event]);
}
return callback();
});
};
internals.Monitor.prototype.stop = function () {
var self = this;
var state = this._state;
clearInterval(state.opsInterval);
this.removeListener('ops', state.opsHandler);
Wreck.removeListener('response', state.wreckHandler);
if (this.network) {
this.network.stop();
this.network = null;
}
internals.iterateOverEventHash(state.handlers, function (event, handler) {
self._server.removeListener(event, handler);
});
internals.iterateOverEventHash(state.extensions, function (event, handler) {
self._server.removeListener(event, handler);
});
this.emit('stop');
};
internals.Monitor.prototype._requestLogHandler = function (request, event) {
this._dataStream.push(Utils.GreatRequest(request, event));
};
internals.Monitor.prototype._logHandler = function (event) {
this._dataStream.push(Utils.GreatLog(event));
};
internals.Monitor.prototype._errorHandler = function (request, error) {
this._dataStream.push(Utils.GreatError(request, error));
};
internals.Monitor.prototype._responseHandler = function (request) {
this._dataStream.push(Utils.GreatResponse(request, this._responseOptions, this.settings.filter));
};
internals.Monitor.prototype._opsHandler = function (results) {
this._dataStream.push(Utils.GreatOps(results));
};
internals.Monitor.prototype._wreckHandler = function (error, request, response, start, uri) {
this._dataStream.push(Utils.GreatWreck(error, request, response, start, uri));
};
internals.Monitor.prototype._extensionHandler = function (eventName) {
var event;
// (eventName, request, event, tags)
if (arguments.length === 4) {
event = arguments[2] || {};
}
else {
event = arguments[1] || {};
}
event.event = eventName;
this._dataStream.push(Object.freeze(event));
};
internals.iterateOverEventHash = function (hash, predicate) {
var keys = Object.keys(hash);
for (var k = 0, kl = keys.length; k < kl; ++k) {
var key = keys[k];
predicate(key, hash[key]);
}
};