exception-handler.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /**
  2. * exception-handler.js: Object for handling uncaughtException events.
  3. *
  4. * (C) 2010 Charlie Robbins
  5. * MIT LICENCE
  6. */
  7. 'use strict';
  8. function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
  9. function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
  10. function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
  11. function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
  12. function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
  13. function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
  14. var os = require('os');
  15. var asyncForEach = require('async/forEach');
  16. var debug = require('@dabh/diagnostics')('winston:exception');
  17. var once = require('one-time');
  18. var stackTrace = require('stack-trace');
  19. var ExceptionStream = require('./exception-stream');
  20. /**
  21. * Object for handling uncaughtException events.
  22. * @type {ExceptionHandler}
  23. */
  24. module.exports = /*#__PURE__*/function () {
  25. /**
  26. * TODO: add contructor description
  27. * @param {!Logger} logger - TODO: add param description
  28. */
  29. function ExceptionHandler(logger) {
  30. _classCallCheck(this, ExceptionHandler);
  31. if (!logger) {
  32. throw new Error('Logger is required to handle exceptions');
  33. }
  34. this.logger = logger;
  35. this.handlers = new Map();
  36. }
  37. /**
  38. * Handles `uncaughtException` events for the current process by adding any
  39. * handlers passed in.
  40. * @returns {undefined}
  41. */
  42. return _createClass(ExceptionHandler, [{
  43. key: "handle",
  44. value: function handle() {
  45. var _this = this;
  46. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  47. args[_key] = arguments[_key];
  48. }
  49. args.forEach(function (arg) {
  50. if (Array.isArray(arg)) {
  51. return arg.forEach(function (handler) {
  52. return _this._addHandler(handler);
  53. });
  54. }
  55. _this._addHandler(arg);
  56. });
  57. if (!this.catcher) {
  58. this.catcher = this._uncaughtException.bind(this);
  59. process.on('uncaughtException', this.catcher);
  60. }
  61. }
  62. /**
  63. * Removes any handlers to `uncaughtException` events for the current
  64. * process. This does not modify the state of the `this.handlers` set.
  65. * @returns {undefined}
  66. */
  67. }, {
  68. key: "unhandle",
  69. value: function unhandle() {
  70. var _this2 = this;
  71. if (this.catcher) {
  72. process.removeListener('uncaughtException', this.catcher);
  73. this.catcher = false;
  74. Array.from(this.handlers.values()).forEach(function (wrapper) {
  75. return _this2.logger.unpipe(wrapper);
  76. });
  77. }
  78. }
  79. /**
  80. * TODO: add method description
  81. * @param {Error} err - Error to get information about.
  82. * @returns {mixed} - TODO: add return description.
  83. */
  84. }, {
  85. key: "getAllInfo",
  86. value: function getAllInfo(err) {
  87. var message = null;
  88. if (err) {
  89. message = typeof err === 'string' ? err : err.message;
  90. }
  91. return {
  92. error: err,
  93. // TODO (indexzero): how do we configure this?
  94. level: 'error',
  95. message: ["uncaughtException: ".concat(message || '(no error message)'), err && err.stack || ' No stack trace'].join('\n'),
  96. stack: err && err.stack,
  97. exception: true,
  98. date: new Date().toString(),
  99. process: this.getProcessInfo(),
  100. os: this.getOsInfo(),
  101. trace: this.getTrace(err)
  102. };
  103. }
  104. /**
  105. * Gets all relevant process information for the currently running process.
  106. * @returns {mixed} - TODO: add return description.
  107. */
  108. }, {
  109. key: "getProcessInfo",
  110. value: function getProcessInfo() {
  111. return {
  112. pid: process.pid,
  113. uid: process.getuid ? process.getuid() : null,
  114. gid: process.getgid ? process.getgid() : null,
  115. cwd: process.cwd(),
  116. execPath: process.execPath,
  117. version: process.version,
  118. argv: process.argv,
  119. memoryUsage: process.memoryUsage()
  120. };
  121. }
  122. /**
  123. * Gets all relevant OS information for the currently running process.
  124. * @returns {mixed} - TODO: add return description.
  125. */
  126. }, {
  127. key: "getOsInfo",
  128. value: function getOsInfo() {
  129. return {
  130. loadavg: os.loadavg(),
  131. uptime: os.uptime()
  132. };
  133. }
  134. /**
  135. * Gets a stack trace for the specified error.
  136. * @param {mixed} err - TODO: add param description.
  137. * @returns {mixed} - TODO: add return description.
  138. */
  139. }, {
  140. key: "getTrace",
  141. value: function getTrace(err) {
  142. var trace = err ? stackTrace.parse(err) : stackTrace.get();
  143. return trace.map(function (site) {
  144. return {
  145. column: site.getColumnNumber(),
  146. file: site.getFileName(),
  147. "function": site.getFunctionName(),
  148. line: site.getLineNumber(),
  149. method: site.getMethodName(),
  150. "native": site.isNative()
  151. };
  152. });
  153. }
  154. /**
  155. * Helper method to add a transport as an exception handler.
  156. * @param {Transport} handler - The transport to add as an exception handler.
  157. * @returns {void}
  158. */
  159. }, {
  160. key: "_addHandler",
  161. value: function _addHandler(handler) {
  162. if (!this.handlers.has(handler)) {
  163. handler.handleExceptions = true;
  164. var wrapper = new ExceptionStream(handler);
  165. this.handlers.set(handler, wrapper);
  166. this.logger.pipe(wrapper);
  167. }
  168. }
  169. /**
  170. * Logs all relevant information around the `err` and exits the current
  171. * process.
  172. * @param {Error} err - Error to handle
  173. * @returns {mixed} - TODO: add return description.
  174. * @private
  175. */
  176. }, {
  177. key: "_uncaughtException",
  178. value: function _uncaughtException(err) {
  179. var info = this.getAllInfo(err);
  180. var handlers = this._getExceptionHandlers();
  181. // Calculate if we should exit on this error
  182. var doExit = typeof this.logger.exitOnError === 'function' ? this.logger.exitOnError(err) : this.logger.exitOnError;
  183. var timeout;
  184. if (!handlers.length && doExit) {
  185. // eslint-disable-next-line no-console
  186. console.warn('winston: exitOnError cannot be true with no exception handlers.');
  187. // eslint-disable-next-line no-console
  188. console.warn('winston: not exiting process.');
  189. doExit = false;
  190. }
  191. function gracefulExit() {
  192. debug('doExit', doExit);
  193. debug('process._exiting', process._exiting);
  194. if (doExit && !process._exiting) {
  195. // Remark: Currently ignoring any exceptions from transports when
  196. // catching uncaught exceptions.
  197. if (timeout) {
  198. clearTimeout(timeout);
  199. }
  200. // eslint-disable-next-line no-process-exit
  201. process.exit(1);
  202. }
  203. }
  204. if (!handlers || handlers.length === 0) {
  205. return process.nextTick(gracefulExit);
  206. }
  207. // Log to all transports attempting to listen for when they are completed.
  208. asyncForEach(handlers, function (handler, next) {
  209. var done = once(next);
  210. var transport = handler.transport || handler;
  211. // Debug wrapping so that we can inspect what's going on under the covers.
  212. function onDone(event) {
  213. return function () {
  214. debug(event);
  215. done();
  216. };
  217. }
  218. transport._ending = true;
  219. transport.once('finish', onDone('finished'));
  220. transport.once('error', onDone('error'));
  221. }, function () {
  222. return doExit && gracefulExit();
  223. });
  224. this.logger.log(info);
  225. // If exitOnError is true, then only allow the logging of exceptions to
  226. // take up to `3000ms`.
  227. if (doExit) {
  228. timeout = setTimeout(gracefulExit, 3000);
  229. }
  230. }
  231. /**
  232. * Returns the list of transports and exceptionHandlers for this instance.
  233. * @returns {Array} - List of transports and exceptionHandlers for this
  234. * instance.
  235. * @private
  236. */
  237. }, {
  238. key: "_getExceptionHandlers",
  239. value: function _getExceptionHandlers() {
  240. // Remark (indexzero): since `logger.transports` returns all of the pipes
  241. // from the _readableState of the stream we actually get the join of the
  242. // explicit handlers and the implicit transports with
  243. // `handleExceptions: true`
  244. return this.logger.transports.filter(function (wrap) {
  245. var transport = wrap.transport || wrap;
  246. return transport.handleExceptions;
  247. });
  248. }
  249. }]);
  250. }();