<commit_before>/*
 * Copyright (c) 2016-2017 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

var winston = require('winston'); // Logging.
var _ = require('underscore');
var common = require('./common.js');
const EventEmitter = require('events');
const fs = require('fs');
const P = require('bluebird');

P.promisifyAll(fs);

/**
 * Log levels.
 */
var logLevels = {
  Silent: 1,
  Error: 2,
  Warn: 3,
  Info: 4,
  Debug: 5
};

/**
 * Configuration object wrapped into event emitter.
 *
 * @class LoggerConfiguration
 */
class LoggerConfiguration extends EventEmitter {
  /**
   * @param {Object} configuration Configuration object.
   */
  constructor(configuration) {
    super();
    this._setConfiguration(configuration);
    this.setMaxListeners(200);
  }

  /**
   * Creates LoggerConfiguration instance with configuration taken from given files.
   *
   * @param {String[]} paths Paths to logger configuration files.
   * @param {Logger} log Temporary logger to output errors.
   * @returns {(LoggerConfiguration | undefined)} Logger configuration instance.
   */
  static fromFiles(paths = [], log) {
    log.info('Reading logger configuration from file(s):', paths);

    var data = _.reduce(paths, (memo, path) => {
      try {
        var loggerConf = fs.readFileSync(path);

        memo = common.deepExtend(JSON.parse(loggerConf), memo);
      }
      catch (err) {
        log.warn('Unable to read logger configuration, path=' + path + ', error=' + err.message);
      }

      return memo;
    }, {});

    var configuration = new LoggerConfiguration(data);

    paths.forEach(path => {
      // When any of the configuration files changes
      fs.watchFile(path, curStats => {
        if (!curStats.isFile()) return;

        // Read all configuration files and combine them into new configuration object.
        P
          .map(paths, path0 => {
            return fs.readFileAsync(path0)
              .then(data => {
                try {
                  return JSON.parse(data);
                }
                catch (err) {
                  log.warn(
                    `Failed to parse configuration from file (will ignore), path=${path0}, error=${err.message}`);
                }
              })
              .catch(err => {
                console.log(`Unable to read logger configuration (will ignore), path=${path0}, error=${err.message}`);
              });
          })
          .then(configs => {
            var data = _.compact(configs).reduce((memo, cur) => common.deepExtend(cur, memo), {});

            configuration._setConfiguration(data);
          });
      });
    });

    return configuration;
  }

  /**
   * Sets provided configuration object to the current instance.
   *
   * @param {Object} data Configuration object.
   * @private
   * @throws {Error} Validation error.
   */
  _setConfiguration(data) {
    this.current = data;

    // Set default configuration if data is invalid.
    if (!this.current || typeof this.current.categories !== 'object') {
      this.current = _.extend(this.current, { categories: { 'Default': logLevels.Info } });
    }

    this.emit('configChange', this);
  }

  /**
   * Gets log level for the specified logger category.
   *
   * @param {String} category Logger category name.
   * @returns {(String|undefined)} Log level name.
   */
  getLevel(category) {
    return this.current.categories[category] || this.current.categories['Default'];
  }
}

/**
 * Optimized log wrapper over Winston.
 */
class Logger {
  /**
   * @param {Number} [level=logLevels.Info] Initial logging level.
   * @param {String} [category='Default'] Initial category name.
   * @param {LoggerConfiguration|undefined} [configuration] Logger configuration instance.
   */
  constructor(level = logLevels.Info, category = 'Default', configuration) {
    this.log = new (winston.Logger)({<commit_msg>(saymon) logger-configurable: Improved logging.<commit_after>/*
 * Copyright (c) 2016-2017 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

var winston = require('winston'); // Logging.
var _ = require('underscore');
var common = require('./common.js');
const EventEmitter = require('events');
const fs = require('fs');
const P = require('bluebird');

P.promisifyAll(fs);

/**
 * Log levels.
 */
var logLevels = {
  Silent: 1,
  Error: 2,
  Warn: 3,
  Info: 4,
  Debug: 5
};

/**
 * Configuration object wrapped into event emitter.
 *
 * @class LoggerConfiguration
 */
class LoggerConfiguration extends EventEmitter {
  /**
   * @param {Object} configuration Configuration object.
   */
  constructor(configuration) {
    super();
    this._setConfiguration(configuration);
    this.setMaxListeners(200);
  }

  /**
   * Creates LoggerConfiguration instance with configuration taken from given files.
   *
   * @param {String[]} paths Paths to logger configuration files.
   * @param {Logger} log Temporary logger to output errors.
   * @returns {(LoggerConfiguration | undefined)} Logger configuration instance.
   */
  static fromFiles(paths = [], log) {
    log.info('Reading logger configuration from file(s):', paths);

    var data = _.reduce(paths, (memo, path) => {
      try {
        var loggerConf = fs.readFileSync(path);

        memo = common.deepExtend(JSON.parse(loggerConf), memo);
      }
      catch (err) {
        log.warn('Unable to read logger configuration, path=' + path + ', error=' + err.message);
      }

      return memo;
    }, {});

    var configuration = new LoggerConfiguration(data);

    paths.forEach(path => {
      // When any of the configuration files changes
      fs.watchFile(path, curStats => {
        if (!curStats.isFile()) return;

        log.info('Logger configuration file changed, re-reading configuration...');

        // Read all configuration files and combine them into new configuration object.
        P
          .map(paths, path0 => {
            return fs.readFileAsync(path0)
              .then(data => {
                try {
                  return JSON.parse(data);
                }
                catch (err) {
                  log.warn(
                    `Failed to parse configuration from file (will ignore), path=${path0}, error=${err.message}`);
                }
              })
              .catch(err => {
                console.log(`Unable to read logger configuration (will ignore), path=${path0}, error=${err.message}`);
              });
          })
          .then(configs => {
            var data = _.compact(configs).reduce((memo, cur) => common.deepExtend(cur, memo), {});

            configuration._setConfiguration(data);

            log.info('Updated logger configuration:', configuration.toString());
          });
      });
    });

    log.info('Resulting logger configuration:', configuration.toString());

    return configuration;
  }

  /**
   * Sets provided configuration object to the current instance.
   *
   * @param {Object} data Configuration object.
   * @private
   * @throws {Error} Validation error.
   */
  _setConfiguration(data) {
    this.current = data;

    // Set default configuration if data is invalid.
    if (!this.current || typeof this.current.categories !== 'object') {
      this.current = _.extend(this.current, { categories: { 'Default': 'Info' } });
    }

    this.emit('configChange', this);
  }

  /**
   * Gets log level for the specified logger category.
   *
   * @param {String} category Logger category name.
   * @returns {(String|undefined)} Log level name.
   */
  getLevel(category) {
    return this.current.categories[category] || this.current.categories['Default'];
  }

  toString() {
    return this.current && JSON.stringify(this.current, null, 2) || '';
  }
}

/**
 * Optimized log wrapper over Winston.
 */
class Logger {
  /**
   * @param {Number} [level=logLevels.Info] Initial logging level.
   * @param {String} [category='Default'] Initial category name.
   * @param {LoggerConfiguration|undefined} [configuration] Logger configuration instance.
   */
  constructor(level = logLevels.Info, category = 'Default', configuration) {
    this.log = new (winston.Logger)({
{'fraction_non_alphanumeric': 0.1344270034226366, 'fraction_numerical': 0.005783075652071285, 'mean_word_length': 2.61518771331058, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1261607', 'n_tokens_mistral': 2399, 'n_tokens_neox': 2295, 'n_words': 662}