/**
 * exception-handler.js: Object for handling uncaughtException events.
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENCE
 */
'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var os = require('os');

var asyncForEach = require('async/forEach');

var debug = require('@dabh/diagnostics')('winston:exception');

var once = require('one-time');

var stackTrace = require('stack-trace');

var ExceptionStream = require('./exception-stream');
/**
 * Object for handling uncaughtException events.
 * @type {ExceptionHandler}
 */


module.exports = /*#__PURE__*/function () {
  /**
   * TODO: add contructor description
   * @param {!Logger} logger - TODO: add param description
   */
  function ExceptionHandler(logger) {
    _classCallCheck(this, ExceptionHandler);

    if (!logger) {
      throw new Error('Logger is required to handle exceptions');
    }

    this.logger = logger;
    this.handlers = new Map();
  }
  /**
   * Handles `uncaughtException` events for the current process by adding any
   * handlers passed in.
   * @returns {undefined}
   */


  _createClass(ExceptionHandler, [{
    key: "handle",
    value: function handle() {
      var _this = this;

      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      args.forEach(function (arg) {
        if (Array.isArray(arg)) {
          return arg.forEach(function (handler) {
            return _this._addHandler(handler);
          });
        }

        _this._addHandler(arg);
      });

      if (!this.catcher) {
        this.catcher = this._uncaughtException.bind(this);
        process.on('uncaughtException', this.catcher);
      }
    }
    /**
     * Removes any handlers to `uncaughtException` events for the current
     * process. This does not modify the state of the `this.handlers` set.
     * @returns {undefined}
     */

  }, {
    key: "unhandle",
    value: function unhandle() {
      var _this2 = this;

      if (this.catcher) {
        process.removeListener('uncaughtException', this.catcher);
        this.catcher = false;
        Array.from(this.handlers.values()).forEach(function (wrapper) {
          return _this2.logger.unpipe(wrapper);
        });
      }
    }
    /**
     * TODO: add method description
     * @param {Error} err - Error to get information about.
     * @returns {mixed} - TODO: add return description.
     */

  }, {
    key: "getAllInfo",
    value: function getAllInfo(err) {
      var message = err.message;

      if (!message && typeof err === 'string') {
        message = err;
      }

      return {
        error: err,
        // TODO (indexzero): how do we configure this?
        level: 'error',
        message: ["uncaughtException: ".concat(message || '(no error message)'), err.stack || '  No stack trace'].join('\n'),
        stack: err.stack,
        exception: true,
        date: new Date().toString(),
        process: this.getProcessInfo(),
        os: this.getOsInfo(),
        trace: this.getTrace(err)
      };
    }
    /**
     * Gets all relevant process information for the currently running process.
     * @returns {mixed} - TODO: add return description.
     */

  }, {
    key: "getProcessInfo",
    value: function getProcessInfo() {
      return {
        pid: process.pid,
        uid: process.getuid ? process.getuid() : null,
        gid: process.getgid ? process.getgid() : null,
        cwd: process.cwd(),
        execPath: process.execPath,
        version: process.version,
        argv: process.argv,
        memoryUsage: process.memoryUsage()
      };
    }
    /**
     * Gets all relevant OS information for the currently running process.
     * @returns {mixed} - TODO: add return description.
     */

  }, {
    key: "getOsInfo",
    value: function getOsInfo() {
      return {
        loadavg: os.loadavg(),
        uptime: os.uptime()
      };
    }
    /**
     * Gets a stack trace for the specified error.
     * @param {mixed} err - TODO: add param description.
     * @returns {mixed} - TODO: add return description.
     */

  }, {
    key: "getTrace",
    value: function getTrace(err) {
      var trace = err ? stackTrace.parse(err) : stackTrace.get();
      return trace.map(function (site) {
        return {
          column: site.getColumnNumber(),
          file: site.getFileName(),
          "function": site.getFunctionName(),
          line: site.getLineNumber(),
          method: site.getMethodName(),
          "native": site.isNative()
        };
      });
    }
    /**
     * Helper method to add a transport as an exception handler.
     * @param {Transport} handler - The transport to add as an exception handler.
     * @returns {void}
     */

  }, {
    key: "_addHandler",
    value: function _addHandler(handler) {
      if (!this.handlers.has(handler)) {
        handler.handleExceptions = true;
        var wrapper = new ExceptionStream(handler);
        this.handlers.set(handler, wrapper);
        this.logger.pipe(wrapper);
      }
    }
    /**
     * Logs all relevant information around the `err` and exits the current
     * process.
     * @param {Error} err - Error to handle
     * @returns {mixed} - TODO: add return description.
     * @private
     */

  }, {
    key: "_uncaughtException",
    value: function _uncaughtException(err) {
      var info = this.getAllInfo(err);

      var handlers = this._getExceptionHandlers(); // Calculate if we should exit on this error


      var doExit = typeof this.logger.exitOnError === 'function' ? this.logger.exitOnError(err) : this.logger.exitOnError;
      var timeout;

      if (!handlers.length && doExit) {
        // eslint-disable-next-line no-console
        console.warn('winston: exitOnError cannot be true with no exception handlers.'); // eslint-disable-next-line no-console

        console.warn('winston: not exiting process.');
        doExit = false;
      }

      function gracefulExit() {
        debug('doExit', doExit);
        debug('process._exiting', process._exiting);

        if (doExit && !process._exiting) {
          // Remark: Currently ignoring any exceptions from transports when
          // catching uncaught exceptions.
          if (timeout) {
            clearTimeout(timeout);
          } // eslint-disable-next-line no-process-exit


          process.exit(1);
        }
      }

      if (!handlers || handlers.length === 0) {
        return process.nextTick(gracefulExit);
      } // Log to all transports attempting to listen for when they are completed.


      asyncForEach(handlers, function (handler, next) {
        var done = once(next);
        var transport = handler.transport || handler; // Debug wrapping so that we can inspect what's going on under the covers.

        function onDone(event) {
          return function () {
            debug(event);
            done();
          };
        }

        transport._ending = true;
        transport.once('finish', onDone('finished'));
        transport.once('error', onDone('error'));
      }, function () {
        return doExit && gracefulExit();
      });
      this.logger.log(info); // If exitOnError is true, then only allow the logging of exceptions to
      // take up to `3000ms`.

      if (doExit) {
        timeout = setTimeout(gracefulExit, 3000);
      }
    }
    /**
     * Returns the list of transports and exceptionHandlers for this instance.
     * @returns {Array} - List of transports and exceptionHandlers for this
     * instance.
     * @private
     */

  }, {
    key: "_getExceptionHandlers",
    value: function _getExceptionHandlers() {
      // Remark (indexzero): since `logger.transports` returns all of the pipes
      // from the _readableState of the stream we actually get the join of the
      // explicit handlers and the implicit transports with
      // `handleExceptions: true`
      return this.logger.transports.filter(function (wrap) {
        var transport = wrap.transport || wrap;
        return transport.handleExceptions;
      });
    }
  }]);

  return ExceptionHandler;
}();