UNPKG

log-events

Version:

Create custom, chainable logging methods that emit log events when called.

329 lines (297 loc) 9.27 kB
/*! * log-events <https://github.com/doowb/log-events> * * Copyright (c) 2016, Brian Woodward. * Licensed under the MIT License. */ 'use strict'; var ComponentEmitter = require('component-emitter'); var Emitter = require('./lib/emitter'); var Stack = require('./lib/stack'); var utils = require('./lib/utils'); var Mode = require('./lib/mode'); /** * Create a new `Logger` constructor to allow * updating the prototype without affecting other contructors. * * @api public */ function Logger() { if (!(this instanceof Logger)) { var proto = Object.create(Logger.prototype); Logger.apply(proto, arguments); return proto; } utils.define(this, 'emitterKeys', []); utils.define(this, 'emitters', {}); utils.define(this, 'methods', {}); utils.define(this, 'modeKeys', []); utils.define(this, 'modes', {}); utils.define(this, 'styleKeys', []); utils.define(this, 'styles', {}); utils.define(this, 'stack', new Stack()); // default emitter "log" this.emitter('log'); } /** * Mixin `ComponentEmitter` prototype methods */ ComponentEmitter(Logger.prototype); /** * Factory for emitting log messages. * This method is called internally for any emitter or mode method that is * called as a function. To listen for events, listen for the emitter name or * `'log'` when a mode is called as a method. * * Wildcard `*` may also be listened for and will get 2 arguments `(name, stats)` where * `name` is the emitter that was emitted and `stats` is the stats object for that event. * * ```js * // emit `info` when `info` is an emitter method * logger.info('message'); * * // emit `log` when `verbose` is a mode method * logger.verbose('message'); * * // listen for all events * logger.on('*', function(name, stats) { * console.log(name); * //=> info * }); * * logger.info('message'); * ``` * * @param {String} `name` the name of the emitter event to emit. Example: `info` * @param {String} `message` Message intended to be emitted. * @emits {String, Object} `*` Wildcard emitter that emits the emitter event name and stats object. * @emits {Object} `stats` Emitter that emits the stats object for the specified name. * @return {Object} `Logger` for chaining * @api public */ Logger.prototype._emit = function(name/*, message*/) { var args = [].slice.call(arguments, 1); var emitter = this.emitters[name]; if (!emitter) { throw new Error('Unable to find emitter "' + name + '"'); } this.stack.process(function(stats) { stats.args = args; this.emit.call(this, '*', stats.name, stats); this.emit.call(this, stats.name, stats); }, this); return this; }; /** * Add an emitter method to emit an event with the given `name`. * * ```js * // add a default `write` emitter * logger.emitter('write'); * * // add some styles * logger.style('red', function(msg) { * return colors.red(msg); * }); * logger.style('cyan', function(msg) { * return colors.cyan(msg); * }); * * // add an `info` logger that colors the msg cyan * logger.emitter('info', logger.cyan); * * // use the loggers: * logger.red.write('this is a red message'); * logger.info('this is a cyan message'); * ``` * * @param {String} `name` the name of the emitter event to emit. * @param {Number} `level` Priority level of the emitter. Higher numbers are less severe. (Default: 100) * @param {Function} `fn` Optional emitter function that can be used to modify an emitted message. Function may be an existing style function. * @emits {String} `emitter` Emits name and new emitter instance after adding the emitter method. * @return {Object} `this` for chaining * @api public */ Logger.prototype.emitter = function(name, level, fn) { this.methods[name] = null; Object.defineProperty(this, name, { configurable: true, enumerable: true, get: buildEmitter.call(this, name, level, fn), set: function(fn) { this.methods[name] = function() { return fn.apply(this, arguments); }.bind(this); return fn; } }); this.emit('emitter', name); return this; }; /** * Add arbitrary modes to be used for creating namespaces for emitter methods. * * ```js * // create a simple `verbose` mode * logger.mode('verbose'); * * // create a `not` toggle mode * logger.mode('not', {type: 'toggle'}); * * // create a `debug` mode that modifies the message * logger.mode('debug', function(msg) { * return '[DEBUG]: ' + msg; * }); * * // use the modes with styles and emitters from above: * logger.verbose.red.write('write a red message when verbose is true'); * logger.not.verbose.info('write a cyan message when verbose is false'); * logger.debug('write a message when debug is true'); * ``` * @param {String} `mode` Mode to add to the logger. * @param {Object} `options` Options to describe the mode. * @param {String|Array} `options.type` Type of mode being created. Defaults to `mode`. Valid values are `['mode', 'toggle']`. `toggle` mode may be used to indicate a "flipped" state for another mode. e.g. `not.verbose`. `toggle` modes may not be used directly for emitting log events. * @param {Function} `fn` Optional style function that can be used to stylize an emitted message. * @emits {String} `mode` Emits the name and new mode instance after adding the mode method. * @return {Object} `this` for chaining * @api public */ Logger.prototype.mode = function(mode, options, fn) { Object.defineProperty(this, mode, { configurable: true, enumerable: true, get: buildMode.call(this, mode, options, fn) }); this.emit('mode', mode); return this; }; /** * Create a logger `style` with the given `fn`. * * @emits `style` * @param {String} `style` The name of the style to create. * @param {Function} `fn` * @return {Object} Returns the instance for chaining. * @api public */ Logger.prototype.style = function(style, fn) { Object.defineProperty(this, style, { configurable: true, enumerable: true, get: buildStyle.call(this, style, fn) }); this.emit('style', style); return this; }; /** * Create an emitter getter function that can be used in chaining * * @param {String} `name` the name of the emitter event to emit * @return {Function} getter function to be used in `defineProperty` */ function buildEmitter(name, level, fn) { if (typeof level === 'function') { fn = level; level = 100; } if (this.emitterKeys.indexOf(name) === -1) { this.emitterKeys.push(name); } var logger = this; return function() { var emitter = logger.emitters[name]; if (typeof emitter === 'undefined') { emitter = new Emitter({name: name, level: level, fn: fn}); logger.emitters[name] = emitter; } logger.stack.chainEmitter(emitter); var method; if (typeof logger.methods[name] === 'function') { method = logger.methods[name]; } else { method = function(/*message*/) { return logger._emit.apply(logger, [name, ...arguments]); }; logger.methods[name] = method; } method.__proto__ = logger; return method; }; } /** * Create an instance of a mode object that switches * the current `mode` of the logger. * * @param {String} `mode` mode to set when getting this proeprty. * @return {Function} getter function to be used in `defineProperty` */ function buildMode(name, options, fn) { if (typeof options === 'function') { fn = options; options = {}; } if (this.modeKeys.indexOf(name) === -1) { this.modeKeys.push(name); } var logger = this; return function() { var mode = logger.modes[name]; if (typeof mode === 'undefined') { var opts = utils.extend({name: name, type: 'mode', fn: fn}, options); mode = new Mode(opts); logger.modes[name] = mode; } logger.stack.addMode(mode); if (utils.hasType(mode.type, 'toggle')) { var ModeWrapper = function() {}; var inst = new ModeWrapper(); inst.__proto__ = logger; return inst; } var method; if (typeof logger.methods[name] === 'function') { method = logger.methods[name]; } else { method = function(/*message*/) { return logger.log(...arguments); }; logger.methods[name] = method; } method.__proto__ = logger; return method; }; } /** * Create a style getter function that can be used in chaining * * @param {String} `name` the name of the style * @param {Function} `fn` style function to apply to the arguments when called * @return {Function} getter function to be used in `defineProperty` */ function buildStyle(name, fn) { if (this.styleKeys.indexOf(name) === -1) { this.styleKeys.push(name); } var logger = this; return function() { var self = this; var style; this.stack.addStyle(name); if (typeof this.styles[name] === 'function') { style = this.styles[name]; } else { style = function(/*message*/) { var args = [].slice.call(arguments); self.stack.removeStyle(name); return fn.apply(self, args); }; this.styles[name] = style; } style.__proto__ = logger; return style; }; } /** * Allow users to create a new Constructor */ module.exports = Logger;