logger.js 8.72 KB
/*
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2016 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */
(function(window) {
    'use strict';

    var MODULE_NAME = 'screens/player/log/logger';

    /**
     * @typedef LEVEL
     */
    var LEVEL = Object.freeze({
        DISABLED: 0,
        DEBUG: 1,
        INFO: 2,
        WARN: 3,
        ERROR: 4
    });

    var key;

    /**
     * @typedef LEVEL_MAP
     */
    var LEVEL_MAP = {};
    for (key in LEVEL) {
        LEVEL_MAP[LEVEL[key]] = key;
    }

    /**
     * console function mapping for levels
     * @private
     * @type {Object}
     */
    var CONSOLE_FN = {};
    for (key in LEVEL) {
        CONSOLE_FN[LEVEL[key]] = key.toLowerCase();
    }

    /**
     * @type {Logger}
     * @private
     */
    var _catchExceptionsLogger = null;

    /**
     * @type {Logger}
     * @private
     */
    var _patchConsoleLogger = null;

    /**
     * Indicates if the console object was patched
     * @type {boolean}
     * @private
     */
    var _isPatched = false;

    /**
     * Indicates if global exceptions are caught
     * @type {boolean}
     * @private
     */
    var _catchExceptions = false;

    /**
     * A potentially existing window.onerror handler
     * @type {boolean}
     * @private
     */
    var _catchExceptionsOnErrorHandler = null;

    /**
     * The global catch exception error handler
     * @param {Event} ev The event
     * @private
     */
     var _catchExceptionErrorHandler = function(ev) {
        var message = ev.message ? ev.message : 'Error during loading ' + ev.srcElement.src;
        _catchExceptionsLogger.error(message, ev.source || '', ev.lineno || 0, ev.colno || 0, ev);
    };

    /**
     *
     * @type {object}
     * @private
     */
    var _originalConsoleFn = null;

    /**
     * Logger
     *
     * @classdesc Logger
     * @class Logger
     * @param {String} [name] of the logger (visible in the output)
     * @param {Logger.Writer} [writer] writer to process logs
     */
    function Logger(name, writer) {
        this.name = name;

        this._logLevel = LEVEL.DEBUG;
        this._print = false;

        this._writer = writer || {
            write: function(date, level, message) {
                // nothing to do
            }
        };
    }

    /**
     * Retrieves the console object
     * @returns {Object} The console object
     * @private
     */
    function getConsole() {
        if (_originalConsoleFn) {
            return _originalConsoleFn;
        }

        return window.console;
    }

    /**
     * Log the specified message.
     * @param {Logger} logger The logger
     * @param {Logger.LEVEL} level The log level
     * @param {Object} args data to log
     * @private
     */
    function log(logger, level, args) {
        var argsArr = Array.prototype.slice.call(args);
        var message = logger.name ? '[' + logger.name + '] ' : '';
        message += argsArr.join(', '); // @todo sprintf

        if (logger._logLevel !== LEVEL.DISABLED && level >= logger._logLevel) {
            logger._writer.write(Date.now(), level, message);
            if (logger._print) {
                var console = getConsole();
                var fn = console[CONSOLE_FN[level]];

                fn = fn || console.log;
                fn.apply(window.console, args);
            }
        }
    }

    /**
     * setting this to true will override
     * the functions of the default console object
     * which means all code using console.log(), console.debug(),
     * console.info(), console.warn(), console.error(), console.trace()
     * will automatically end up writing into the logs
     * @param {Logger|false} [logger] Logger to write the console logs or false to deactivate
     * @static
     */
    Logger.patchConsole = function(logger) {
        var willBePatched = logger === false ? false : true;

        if (!_isPatched && willBePatched) { // enable console patch
            _patchConsoleLogger = logger;

            _originalConsoleFn = {
                log: console.log,
                debug: console.debug,
                info: console.info,
                warn: console.warn,
                error: console.error,
                trace: console.trace
            };

            // output is now disabled if print() is not set
            console.log = _patchConsoleLogger.log.bind(_patchConsoleLogger);
            console.debug = _patchConsoleLogger.debug.bind(_patchConsoleLogger);
            console.info = _patchConsoleLogger.info.bind(_patchConsoleLogger);
            console.warn = _patchConsoleLogger.warn.bind(_patchConsoleLogger);
            console.error = _patchConsoleLogger.error.bind(_patchConsoleLogger);
            console.trace = _patchConsoleLogger.trace.bind(_patchConsoleLogger);

        } else if (_isPatched && !willBePatched) { // disable the previous patching
            _patchConsoleLogger = null;

            console.log = _originalConsoleFn.log;
            console.debug = _originalConsoleFn.debug;
            console.info = _originalConsoleFn.info;
            console.warn = _originalConsoleFn.warn;
            console.error = _originalConsoleFn.error;
            console.trace = _originalConsoleFn.trace;

            _originalConsoleFn = null;
        }

        _isPatched = willBePatched;
    };

    /**
     * setting this to true will catch all uncaught exceptions
     * and add them to the logs as error.
     * @param {Logger|false} [logger] Logger to write the exceptions logs
     * @static
     */
    Logger.catchExceptions = function(logger) { // considered as LEVEL.ERROR
        var willCatchExceptions = logger === false ? false : true;

        if (!_catchExceptions && willCatchExceptions) {
            _catchExceptionsLogger = logger;
            window.addEventListener('error', _catchExceptionErrorHandler, true);
            // Overwrite potential existing error handler
            _catchExceptionsOnErrorHandler = window.onerror;
            window.onerror = null;
        } else {
            _catchExceptionsLogger = null;
            window.removeEventListener('error', _catchExceptionErrorHandler);
            window.onerror = _catchExceptionsOnErrorHandler;
        }
        _catchExceptions = willCatchExceptions;
    };

    /**
     * Sets the writer
     * @param {Object} writer the writer to use
     * @returns {Logger} the logger to allow for easy chaining
     */
    Logger.prototype.setWriter = function(writer) {
        this._writer = writer;
        return this;
    };

    /**
     * Sets the log level
     * @param {Logger.LEVEL} level the log level
     * @returns {Logger} the logger to allow for easy chaining
     */
    Logger.prototype.setLevel = function(level) {
        this._logLevel = level;
        return this;
    };

    /**
     * Setting this true will output the set log level
     * messages into the browser's console
     * @param {boolean} [cond] whether to print the logs or not
     */
    Logger.prototype.print = function(cond) {
        this._print = cond === false ? false : true;
    };

    Logger.prototype.log = function() {
        log(this, LEVEL.DEBUG, arguments);
    };

    Logger.prototype.debug = function() {
        log(this, LEVEL.DEBUG, arguments);
    };

    Logger.prototype.info = function() {
        log(this, LEVEL.INFO, arguments);
    };

    Logger.prototype.warn = function() {
        log(this, LEVEL.WARN, arguments);
    };

    Logger.prototype.error = function() {
        log(this, LEVEL.ERROR, arguments);
    };

    Logger.prototype.trace = function() {
        var e = new Error('tracer');
        var stack;

        if (e.stack) {
            stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
                .replace(/^\s+at\s+/gm, '')
                .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
                .split('\n');
        }

        log(this, LEVEL.DEBUG, [stack]);
    };

    // expose constants
    Logger.LEVEL = LEVEL;
    Logger.LEVEL_MAP = LEVEL_MAP;

    // register as a requirejs module if available
    /* istanbul ignore next  */
    if (typeof define === 'function') {
        define(MODULE_NAME, function() {
            return Logger;
        });
    }

    // expose on window for non require-js frames
    // e.g. channel frame
    window.screens = window.screens || {};
    window.screens.logger = Logger;

}(this));