123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- /**
- * exception-handler.js: Object for handling uncaughtException events.
- *
- * (C) 2010 Charlie Robbins
- * MIT LICENCE
- */
- 'use strict';
- const os = require('os');
- const asyncForEach = require('async/forEach');
- const debug = require('diagnostics')('winston:exception');
- const once = require('one-time');
- const stackTrace = require('stack-trace');
- const ExceptionStream = require('./exception-stream');
- /**
- * Object for handling uncaughtException events.
- * @type {ExceptionHandler}
- */
- module.exports = class ExceptionHandler {
- /**
- * TODO: add contructor description
- * @param {!Logger} logger - TODO: add param description
- */
- constructor(logger) {
- 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}
- */
- handle(...args) {
- args.forEach(arg => {
- if (Array.isArray(arg)) {
- return arg.forEach(handler => 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}
- */
- unhandle() {
- if (this.catcher) {
- process.removeListener('uncaughtException', this.catcher);
- this.catcher = false;
- Array.from(this.handlers.values())
- .forEach(wrapper => this.logger.unpipe(wrapper));
- }
- }
- /**
- * TODO: add method description
- * @param {Error} err - Error to get information about.
- * @returns {mixed} - TODO: add return description.
- */
- getAllInfo(err) {
- let { message } = err;
- if (!message && typeof err === 'string') {
- message = err;
- }
- return {
- error: err,
- // TODO (indexzero): how do we configure this?
- level: 'error',
- message: [
- `uncaughtException: ${(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.
- */
- 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.
- */
- 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.
- */
- getTrace(err) {
- const trace = err ? stackTrace.parse(err) : stackTrace.get();
- return trace.map(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}
- */
- _addHandler(handler) {
- if (!this.handlers.has(handler)) {
- handler.handleExceptions = true;
- const 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
- */
- _uncaughtException(err) {
- const info = this.getAllInfo(err);
- const handlers = this._getExceptionHandlers();
- // Calculate if we should exit on this error
- let doExit = typeof this.logger.exitOnError === 'function'
- ? this.logger.exitOnError(err)
- : this.logger.exitOnError;
- let timeout;
- if (!handlers.length && doExit) {
- // eslint-disable-next-line no-console
- console.warn('winston: exitOnError cannot be false with no exception handlers.');
- // eslint-disable-next-line no-console
- console.warn('winston: 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, (handler, next) => {
- // TODO: Change these to the correct WritableStream events so that we
- // wait until exit.
- const done = once(next);
- const transport = handler.transport || handler;
- // Debug wrapping so that we can inspect what's going on under the covers.
- function onDone(event) {
- return () => {
- debug(event);
- done();
- };
- }
- transport.once('logged', onDone('logged'));
- transport.once('error', onDone('error'));
- }, 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
- */
- _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(wrap => {
- const transport = wrap.transport || wrap;
- return transport.handleExceptions;
- });
- }
- };
|