UNPKG

js-logger

Version:

Lightweight, unobtrusive, configurable JavaScript logger

281 lines (236 loc) 9.23 kB
/*! * js-logger - http://github.com/jonnyreeves/js-logger * Jonny Reeves, http://jonnyreeves.co.uk/ * js-logger may be freely distributed under the MIT license. */ (function (global) { "use strict"; // Top level module for the global, static logger instance. var Logger = { }; // For those that are at home that are keeping score. Logger.VERSION = "1.6.1"; // Function which handles all incoming log messages. var logHandler; // Map of ContextualLogger instances by name; used by Logger.get() to return the same named instance. var contextualLoggersByNameMap = {}; // Polyfill for ES5's Function.bind. var bind = function(scope, func) { return function() { return func.apply(scope, arguments); }; }; // Super exciting object merger-matron 9000 adding another 100 bytes to your download. var merge = function () { var args = arguments, target = args[0], key, i; for (i = 1; i < args.length; i++) { for (key in args[i]) { if (!(key in target) && args[i].hasOwnProperty(key)) { target[key] = args[i][key]; } } } return target; }; // Helper to define a logging level object; helps with optimisation. var defineLogLevel = function(value, name) { return { value: value, name: name }; }; // Predefined logging levels. Logger.TRACE = defineLogLevel(1, 'TRACE'); Logger.DEBUG = defineLogLevel(2, 'DEBUG'); Logger.INFO = defineLogLevel(3, 'INFO'); Logger.TIME = defineLogLevel(4, 'TIME'); Logger.WARN = defineLogLevel(5, 'WARN'); Logger.ERROR = defineLogLevel(8, 'ERROR'); Logger.OFF = defineLogLevel(99, 'OFF'); // Inner class which performs the bulk of the work; ContextualLogger instances can be configured independently // of each other. var ContextualLogger = function(defaultContext) { this.context = defaultContext; this.setLevel(defaultContext.filterLevel); this.log = this.info; // Convenience alias. }; ContextualLogger.prototype = { // Changes the current logging level for the logging instance. setLevel: function (newLevel) { // Ensure the supplied Level object looks valid. if (newLevel && "value" in newLevel) { this.context.filterLevel = newLevel; } }, // Gets the current logging level for the logging instance getLevel: function () { return this.context.filterLevel; }, // Is the logger configured to output messages at the supplied level? enabledFor: function (lvl) { var filterLevel = this.context.filterLevel; return lvl.value >= filterLevel.value; }, trace: function () { this.invoke(Logger.TRACE, arguments); }, debug: function () { this.invoke(Logger.DEBUG, arguments); }, info: function () { this.invoke(Logger.INFO, arguments); }, warn: function () { this.invoke(Logger.WARN, arguments); }, error: function () { this.invoke(Logger.ERROR, arguments); }, time: function (label) { if (typeof label === 'string' && label.length > 0) { this.invoke(Logger.TIME, [ label, 'start' ]); } }, timeEnd: function (label) { if (typeof label === 'string' && label.length > 0) { this.invoke(Logger.TIME, [ label, 'end' ]); } }, // Invokes the logger callback if it's not being filtered. invoke: function (level, msgArgs) { if (logHandler && this.enabledFor(level)) { logHandler(msgArgs, merge({ level: level }, this.context)); } } }; // Protected instance which all calls to the to level `Logger` module will be routed through. var globalLogger = new ContextualLogger({ filterLevel: Logger.OFF }); // Configure the global Logger instance. (function() { // Shortcut for optimisers. var L = Logger; L.enabledFor = bind(globalLogger, globalLogger.enabledFor); L.trace = bind(globalLogger, globalLogger.trace); L.debug = bind(globalLogger, globalLogger.debug); L.time = bind(globalLogger, globalLogger.time); L.timeEnd = bind(globalLogger, globalLogger.timeEnd); L.info = bind(globalLogger, globalLogger.info); L.warn = bind(globalLogger, globalLogger.warn); L.error = bind(globalLogger, globalLogger.error); // Don't forget the convenience alias! L.log = L.info; }()); // Set the global logging handler. The supplied function should expect two arguments, the first being an arguments // object with the supplied log messages and the second being a context object which contains a hash of stateful // parameters which the logging function can consume. Logger.setHandler = function (func) { logHandler = func; }; // Sets the global logging filter level which applies to *all* previously registered, and future Logger instances. // (note that named loggers (retrieved via `Logger.get`) can be configured independently if required). Logger.setLevel = function(level) { // Set the globalLogger's level. globalLogger.setLevel(level); // Apply this level to all registered contextual loggers. for (var key in contextualLoggersByNameMap) { if (contextualLoggersByNameMap.hasOwnProperty(key)) { contextualLoggersByNameMap[key].setLevel(level); } } }; // Gets the global logging filter level Logger.getLevel = function() { return globalLogger.getLevel(); }; // Retrieve a ContextualLogger instance. Note that named loggers automatically inherit the global logger's level, // default context and log handler. Logger.get = function (name) { // All logger instances are cached so they can be configured ahead of use. return contextualLoggersByNameMap[name] || (contextualLoggersByNameMap[name] = new ContextualLogger(merge({ name: name }, globalLogger.context))); }; // CreateDefaultHandler returns a handler function which can be passed to `Logger.setHandler()` which will // write to the window's console object (if present); the optional options object can be used to customise the // formatter used to format each log message. Logger.createDefaultHandler = function (options) { options = options || {}; options.formatter = options.formatter || function defaultMessageFormatter(messages, context) { // Prepend the logger's name to the log message for easy identification. if (context.name) { messages.unshift("[" + context.name + "]"); } }; // Map of timestamps by timer labels used to track `#time` and `#timeEnd()` invocations in environments // that don't offer a native console method. var timerStartTimeByLabelMap = {}; // Support for IE8+ (and other, slightly more sane environments) var invokeConsoleMethod = function (hdlr, messages) { Function.prototype.apply.call(hdlr, console, messages); }; // Check for the presence of a logger. if (typeof console === "undefined") { return function () { /* no console */ }; } return function(messages, context) { // Convert arguments object to Array. messages = Array.prototype.slice.call(messages); var hdlr = console.log; var timerLabel; if (context.level === Logger.TIME) { timerLabel = (context.name ? '[' + context.name + '] ' : '') + messages[0]; if (messages[1] === 'start') { if (console.time) { console.time(timerLabel); } else { timerStartTimeByLabelMap[timerLabel] = new Date().getTime(); } } else { if (console.timeEnd) { console.timeEnd(timerLabel); } else { invokeConsoleMethod(hdlr, [ timerLabel + ': ' + (new Date().getTime() - timerStartTimeByLabelMap[timerLabel]) + 'ms' ]); } } } else { // Delegate through to custom warn/error loggers if present on the console. if (context.level === Logger.WARN && console.warn) { hdlr = console.warn; } else if (context.level === Logger.ERROR && console.error) { hdlr = console.error; } else if (context.level === Logger.INFO && console.info) { hdlr = console.info; } else if (context.level === Logger.DEBUG && console.debug) { hdlr = console.debug; } else if (context.level === Logger.TRACE && console.trace) { hdlr = console.trace; } options.formatter(messages, context); invokeConsoleMethod(hdlr, messages); } }; }; // Configure and example a Default implementation which writes to the `window.console` (if present). The // `options` hash can be used to configure the default logLevel and provide a custom message formatter. Logger.useDefaults = function(options) { Logger.setLevel(options && options.defaultLevel || Logger.DEBUG); Logger.setHandler(Logger.createDefaultHandler(options)); }; // Createa an alias to useDefaults to avoid reaking a react-hooks rule. Logger.setDefaults = Logger.useDefaults; // Export to popular environments boilerplate. if (typeof define === 'function' && define.amd) { define(Logger); } else if (typeof module !== 'undefined' && module.exports) { module.exports = Logger; } else { Logger._prevLogger = global.Logger; Logger.noConflict = function () { global.Logger = Logger._prevLogger; return Logger; }; global.Logger = Logger; } }(this));