UNPKG

elasticsearch

Version:

The official low-level Elasticsearch client for Node.js and the browser.

346 lines (322 loc) 9.51 kB
var _ = require('lodash'); var utils = require('./utils'); var url = require('url'); var EventEmitter = require('events').EventEmitter; /** * Log bridge, which is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) * that sends events to one or more outputs/loggers. Setup these loggers by * specifying their config as the first argument, or by passing it to addOutput(). * * @class Log * @uses Loggers.Stdio * @constructor * @param {object} config * @param {string|Object|ArrayOfStrings|ArrayOfObjects} config.log - Either the level * to setup a single logger, a full config object for a logger, or an array of * config objects to use for creating log outputs. * @param {string|array} config.log.level|config.log.levels - One or more keys in Log.levels (error, warning, etc.) * @param {string} config.log.type - The name of the logger to use for this output */ function Log(config) { config = config || {}; if (!config.log) return; var i; var outputs; if (utils.isArrayOfStrings(config.log)) { outputs = [ { levels: config.log, }, ]; } else { outputs = utils.createArray(config.log, function(val) { if (_.isPlainObject(val)) { return val; } if (typeof val === 'string') { return { level: val, }; } }); } if (!outputs) { throw new TypeError( 'Invalid logging output config. Expected either a log level, array of log levels, ' + 'a logger config object, or an array of logger config objects.' ); } for (i = 0; i < outputs.length; i++) { this.addOutput(outputs[i]); } } utils.inherits(Log, EventEmitter); Log.loggers = require('./loggers'); Log.prototype.close = function() { this.emit('closing'); if (this.listenerCount()) { console.error( 'Something is still listening for log events, but the logger is closing.' ); // eslint-disable-line no-console this.clearAllListeners(); } }; if (EventEmitter.prototype.listenerCount) { // If the event emitter implements it's own listenerCount method // we don't need to (newer nodes do this). Log.prototype.listenerCount = EventEmitter.prototype.listenerCount; } else if (EventEmitter.listenerCount) { // some versions of node expose EventEmitter::listenerCount // which is more efficient the getting all listeners of a // specific type Log.prototype.listenerCount = function(event) { return EventEmitter.listenerCount(this, event); }; } else { // all other versions of node expose a #listeners() method, which returns // and array we have to count Log.prototype.listenerCount = function(event) { return this.listeners(event).length; }; } /** * Levels observed by the loggers, ordered by rank * * @property levels * @type Array * @static */ Log.levels = [ /** * Event fired for error level log entries * @event error * @param {Error} error - The error object to log */ 'error', /** * Event fired for "warning" level log entries, which usually represent things * like correctly formatted error responses from ES (400, ...) and recoverable * errors (one node unresponsive) * * @event warning * @param {String} message - A message to be logged */ 'warning', /** * Event fired for "info" level log entries, which usually describe what a * client is doing (sniffing etc) * * @event info * @param {String} message - A message to be logged */ 'info', /** * Event fired for "debug" level log entries, which will describe requests sent, * including their url (no data, response codes, or exec times) * * @event debug * @param {String} message - A message to be logged */ 'debug', /** * Event fired for "trace" level log entries, which provide detailed information * about each request made from a client, including reponse codes, execution times, * and a full curl command that can be copied and pasted into a terminal * * @event trace * @param {String} method method, , body, responseStatus, responseBody * @param {String} url - The url the request was made to * @param {String} body - The body of the request * @param {Integer} responseStatus - The status code returned from the response * @param {String} responseBody - The body of the response */ 'trace', ]; /** * Converts a log config value (string or array) to an array of level names which * it represents * * @method parseLevels * @static * @private * @param {String|ArrayOfStrings} input - Cound be a string to specify the max * level, or an array of exact levels * @return {Array} - */ Log.parseLevels = function(input) { switch (typeof input) { case 'string': var i = _.indexOf(Log.levels, input); if (i >= 0) { return Log.levels.slice(0, i + 1); } /* fall through */ case 'object': if (_.isArray(input)) { var valid = _.intersection(input, Log.levels); if (valid.length === input.length) { return valid; } } /* fall through */ default: throw new TypeError( 'invalid logging level ' + input + '. Expected zero or more of these options: ' + Log.levels.join(', ') ); } }; /** * Combine the array-like param into a simple string * * @method join * @static * @private * @param {*} arrayish - An array like object that can be itterated by _.each * @return {String} - The final string. */ Log.join = function(arrayish) { return _.map(arrayish, function(item) { if (_.isPlainObject(item)) { return JSON.stringify(item, null, 2) + '\n'; } else { return item.toString(); } }).join(' '); }; /** * Create a new logger, based on the config. * * @method addOutput * @param {object} config - An object with config options for the logger. * @param {String} [config.type=stdio] - The name of an output/logger. Options * can be found in the `src/loggers` directory. * @param {String|ArrayOfStrings} [config.level|config.levels=warning] - The levels to output * to this logger, when an array is specified no levels other than the ones * specified will be listened to. When a string is specified, that and all lower * levels will be logged. * @return {Logger} */ Log.prototype.addOutput = function(config) { config = config || {}; // force "levels" key config.levels = Log.parseLevels(config.levels || config.level || 'warning'); delete config.level; var Logger = utils.funcEnum( config, 'type', Log.loggers, process.browser ? 'console' : 'stdio' ); return new Logger(this, config); }; /** * Log an error * * @method error * @param {Error|String} error The Error to log * @return {Boolean} - True if any outputs accepted the message */ Log.prototype.error = function(e) { if (this.listenerCount('error')) { return this.emit('error', e instanceof Error ? e : new Error(e)); } }; /** * Log a warning message * * @method warning * @param {*} msg* - Any amount of messages that will be joined before logged * @return {Boolean} - True if any outputs accepted the message */ Log.prototype.warning = function(/* ...msg */) { if (this.listenerCount('warning')) { return this.emit('warning', Log.join(arguments)); } }; /** * Log useful info about what's going on * * @method info * @param {*} msg* - Any amount of messages that will be joined before logged * @return {Boolean} - True if any outputs accepted the message */ Log.prototype.info = function(/* ...msg */) { if (this.listenerCount('info')) { return this.emit('info', Log.join(arguments)); } }; /** * Log a debug level message * * @method debug * @param {*} msg* - Any amount of messages that will be joined before logged * @return {Boolean} - True if any outputs accepted the message */ Log.prototype.debug = function(/* ...msg */) { if (this.listenerCount('debug')) { return this.emit('debug', Log.join(arguments)); } }; /** * Log a trace level message * * @method trace * @param {String} method - HTTP request method * @param {String|Object} requestUrl - URL requested. If the value is an object, * it is expected to be the return value of Node's url.parse() * @param {String} body - The request's body * @param {String} responseBody - body returned from ES * @param {String} responseStatus - HTTP status code * @return {Boolean} - True if any outputs accepted the message */ Log.prototype.trace = function( method, requestUrl, body, responseBody, responseStatus ) { if (this.listenerCount('trace')) { return this.emit( 'trace', Log.normalizeTraceArgs( method, requestUrl, body, responseBody, responseStatus ) ); } }; Log.normalizeTraceArgs = function( method, requestUrl, body, responseBody, responseStatus ) { if (typeof requestUrl === 'string') { requestUrl = url.parse(requestUrl, true, true); } else { requestUrl = _.clone(requestUrl); if (requestUrl.path) { requestUrl.query = url.parse(requestUrl.path, true, false).query; } if (!requestUrl.pathname && requestUrl.path) { requestUrl.pathname = requestUrl.path.split('?').shift(); } } delete requestUrl.auth; return { method: method, url: url.format(requestUrl), body: body, status: responseStatus, response: responseBody, }; }; module.exports = Log;