UNPKG

even-better

Version:

Server and process monitoring plugin

273 lines (191 loc) 7.75 kB
// 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]); } };