Source: composer-common/lib/log/logger.js

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';
// const beautify = require('json-beautify');
// TODO: Will need to do improvement of the formatting with some module.
//
const sprintf = require('sprintf-js').sprintf;
// const config = require('config');
// Moving config to some other location
const Node = require('./node.js');

// Root node of the selection tree
let _root = null;
let _logger = null;
let _clInstances = {};

/**
 * @description Class that provides the API to enable parts of the *Composer*
 * library to diagnostic messages.
 *
 * The aim is to provide a system whereby
 *  - The *Composer* library has a common API to call and formats the essential data
 *    its way
 *  - It's own control of what level of data points are currently being collected
 *    and for what module/class level
 *  - Provide a default console and/or file basic log if user's application doesn't have
 *    any preference
 *  - Provide hook in which application can provide an injected dependancy to route
 *    tracing to its own Logger
 *
 * # Log Levels
 * Standard log levels are in use. In order these are
 *  - silly, debug, verbose, info, warn, error
 * In addition, there are functions that record method entry and method exit. these
 * map down to the debug level.
 *
 * Examples of using each function are included for each API below.
 *
 * At the top of the class (or file if not object style).  issue.
 *
 * ```
 * const log = require('./log/logger.js').getLog(<CLASSNAME>);
 * log.info(.....)
 * ```
 *
 * @todo Confirm the format via iterative use
 * @todo Precrtiptive on how data is uploaded to logmet etc. ??
 *
 *
 *
 * @private
 * @class
 * @memberof module:composer-common
 */
class Logger {

  /**
   * Constructor *THIS SHOULD ONLY BE CALLED INTERNALLY*
   * @param {String} name  Classname or other filename for this logger
   * @private
   *
   */
    constructor(name) {
        this.className = name;
    }

/**
 *
 * @description Do the formatting of the data that *Composer* wishes to have for all
 * logging systems. This method does basic formatting before passing to the
 * log method of the selected logger implementation.
 *
 * Internal method
 *
 * @private
 * @param {String} logLevel log loglevel
 * @param {String} method method name
 * @param {String} msg to log
 * @param {others} arguments parameters are treated as data points to be logged
 */
    intlog(logLevel,method,msg){
      // first we need to make sure that we have logger setup
        this._intLogFirst.apply(this,arguments);
    }



    /**
     * @description Main internal logging method
     *
     * @param {String} loglevel log loglevel
     * @param {String} method method name
     * @param {String} msg to log
     */
    _intLogMain(loglevel,method,msg){
        if (typeof arguments[3] ==='undefined'){
            _logger.log(loglevel,sprintf('%-25s:%-25s', this.className,method+'()'),msg);
        } else {
            let args = [];
            for(let i = 3; i < arguments.length; i++) {
                if (arguments[i] instanceof Error){
                    args.push(  {'stack' : sprintf('{%s}%s %s',arguments[i].name,arguments[i].message,arguments[i].stack,null,' ').match(/[^\r\n]+/g)});
                }else {
                    args.push(arguments[i]);
                }
            }

            _logger.log(loglevel,sprintf('%-25s:%-25s', this.className,method+'()'),msg, args);
        }

    }

    /**
     * @description initial internal log function that setups the logger to use.
     * Then it calls the normal internal log method (and modifies the original
     * function defn)
     *
     * @param {String} logLevel log loglevel
     * @param {String} method method name
     * @param {String} msg to log
     */
    _intLogFirst(logLevel,method,msg){

       // call the setup logger to make sure that things are setup
       // this is done now to be as late as possible
        Logger._setupLog(this);

         //reroute the ingLog method to the main implementation
         // and call
        this.intLog = this._intLogMain;
        // this._intLogMain.apply(this,arguments);
        this._intLogMain.apply(this,arguments);
    }

  /**
   * @description Log a message at the _debug_level
   *
   * @param {String} method calling method
   * @param {String} msg Text Message
   * @param {stuff} data Data to log
   *
   * @private
   */
    debug(method, msg, data) {
        const args = Array.prototype.slice.call(arguments);
        args.unshift('debug');
        this.intlog.apply(this, args);
    }

  /**
   * @description Log a message at the _warn_ level
   *
   * @param {String} method calling method
   * @param {String} msg Text Message
   * @param {stuff} data Data to log at warn level
   *
   * @private
   */
    warn(method, msg, data) {
        const args = Array.prototype.slice.call(arguments);
        args.unshift('warn');
        this.intlog.apply(this, args);
    }

  /**
   * @description Log a message at the  _info_ level
   *
   * @param {String} method calling method
   * @param {String} msg Text Message
   * @param {stuff} data Data to log at an info level
   *
   *  @private
   */
    info(method, msg, data) {
        const args = Array.prototype.slice.call(arguments);
        args.unshift('info');
        this.intlog.apply(this, args);
    }

  /**
   * @description Log a message at the _verbose_ level
   *
   * @param {String} method calling method
   * @param {String} msg Text Message
   * @param {stuff} data Data to log at a verbose level
   *
   * @private
   */
    verbose(method,msg, data) {
        const args = Array.prototype.slice.call(arguments);
        args.unshift('verbose');
        this.intlog.apply(this, args);
    }


  /**
   * @description Log a message at the _error_ level
   *
   * @param {String} method calling method
   * @param {String} msg Text Message
   * @param {stuff} data Data to log at an error level
   *
   * @private
   */
    error(method, msg,data) {
        const args = Array.prototype.slice.call(arguments);
        args.unshift('error');
        this.intlog.apply(this, args);
    }

  /**
   * @description Logs the entry to a method at the _debug_ level
   *
   * @param {String} method Text Message.
   * @param {stuff} data Data to log at an info level
   *
   * @private
   */
    entry(method, data) {
        const args = Array.prototype.slice.call(arguments);
        args.shift();
        args.unshift('debug', method, '>');
        this.intlog.apply(this, args);
    }

  /**
   * @description Logs the entry to a method at the _debug_ level
   * @param {String} method Method name
   * @param {objects} data Data to log
   *
   * @private
   */
    exit(method, data) {
        const args = Array.prototype.slice.call(arguments);
        args.shift();
        args.unshift('debug', method, '<');
        this.intlog.apply(this, args);
    }

    /**
     * @description Method to call passing an instance of an object that has the
     * method definition
     *
     * log(level,msg,data...)
     *
     * @param {Object} newlogger sets a new log processor to the one of your choice
     *
     * @private
     */
    static setFunctionalLogger(newlogger){
        _logger = newlogger;
    }


     /**
      * @descrption what is the debug environment variable set to
     * Note that the _envDebug property of this object is for debugging and
     * emergency use ONLY
     *
     *
     *  @return {String} String of the DEBUG env variable
     *
     */
    static getDebugEnv(){
        return process.env.DEBUG || this._envDebug || '';
    }

    /** get the configuration for the logging
     * @return {Object} with the config iformation
     *
     **/
    static getLoggerConfig(){
        try {
            // This weird code is needed to trick browserify.

            const mod = 'config';
            const req = require;
            const config = req(mod);
            if (config.has('ConcertoConfig.debug')){
                return config.get('ConcertoConfig.debug');
            }
        } catch (e) {
            // We don't care if we can't find the config module, it won't be
            // there when the code is running inside a browser/chaincode.
        }

        return {
            'logger': './winstonInjector.js',
            'config': {
                'console': {
                    'enabledLevel': 'info',
                    'alwaysLevel': 'none'

                },
                'file': {

                    'filename': 'trace_PID.log',
                    'enabledLevel': 'silly',
                    'alwaysLevel': 'info'
                }
            }};

    }

  /**
   * @description Get the logger instance to be used for this class or file.
   *
   * @param {String} classname The classname (or filename if not a class) to get the logger for
   * @return {ConcertoLog} instance of a concertoLog to use
   *
   * @private
   */
    static getLog(classname) {
        if(typeof _clInstances[classname] === 'undefined') {
            _clInstances[classname] = new Logger(classname);
            _clInstances[classname].log = Logger._intLogFirst;
        }
        return _clInstances[classname];
    }

    /** @description gets the configuration that has been passed in to this node.js runtime
     * to control the tracing. This will update the concertLogger instance that
     * is passed in to match the settings
     *
     * @param {Logger} concertoLogger the instance of the Logger class to update
     */
    static _setupLog(concertoLogger){

        let concertoConfigElements = [];

        if (_root === null){
        // need to do the filtering to see if this shold be enabled or not
            let string = this.getDebugEnv();
            let details = string.split(/[\s,]+/);
            _root = new Node('root',false);

            const regex = /(-?)concerto:(.*)?/;
        // now we have an array of the elements that we might need to be enabled
        //
            for (let i=0; i< details.length;i++){
                let e = details[i];
                if (e === '*' || e ==='concerto:*'){
                    _root.include = true;
                }
            // determine if the element is for concerto or not
                let machResult = e.match(regex);
                if (machResult!==null){
                // got a result that we need to trace therefore setup the child node correctly

                    let newNode = new Node(machResult[2] ,(machResult[1]==='') );
                    _root.addChildNodeAtStart(newNode);

                    // make a note of the debug settings that permit the config elements
                    concertoConfigElements.push(machResult[2]);
                }

            }

        }


        // need to check the config to determine what exactly we need to be using here
        if(_logger === null) {
            let localConfig = this.getLoggerConfig();

            // use the config package to get conifguration to see what we should be doing.
            // and pass the restul fo the data to the logger indicated along with the
            // array of the data that might have been passed on the DBEUG variable.
            let loggerToUse = localConfig.logger;
            let myLogger = require(loggerToUse);

            _logger = myLogger.getLogger(localConfig.config,concertoConfigElements);

        }

        // now we need to check if the name that has come in and should be traced
        let n = _root.findChild(concertoLogger.classname);

        if ( typeof n ==='undefined'){
            concertoLogger.include = _root.isIncluded();
        } else {
            concertoLogger.include = n.isIncluded();
        }

        return ;
    }

    /**
     * @description clean up the logger; required if anything is dynamically changed
     */
    static reset(){
        _root=null;
        _logger=null;
        _clInstances=[];
    }


}


module.exports = Logger;