Description
Problem
When an unhandled exception occurs, by default, Node.js writes the exception to the console and exits the process.
As I understand the source code and infer from other Exceptionless clients, the exceptionless.node.js client should extend that behavior by submitting the exception before the process quits. It doesn't do that. In fact, manually submitting errors and log messages works as expected, as long as the process keeps running long enough to submit the queue. But as soon as an unhandled exception occurs, it will be ignored:
var client = require("./dist/exceptionless.node.js").ExceptionlessClient.default;
client.config.apiKey = "YOUR_KEY";
client.config.useDebugLogger();
client.submitLog("Test: Log");
throw new Error("Test: Error");
Running this code will neither submit the log message nor the unhandled exception.
Causes
Inspection of exceptionless.node.ts and DefaultEventQueue.ts show several problems:
-
When exceptionless.node.js is loaded, it subscribes the
UNCAUGHT_EXCEPTION
andBEFORE_EXIT
events on the global process object. The Node documentation states that those events are named in lower camel case (uncaughtException
andbeforeExit
) instead. They have been named like that since the process class has been first introduced in Node. -
For some reason, the uncaught-handler doesn't include a call to
queue.process
while the beforeExit-handler does. But thebeforeExit
event will never be emitted in case of an unhandled exception:'beforeExit' is not emitted for conditions causing explicit termination, such as process.exit() or uncaught exceptions (...)
-
If the uncaught-handler was registered correctly, in the event of an unhandled exception it would keep the application running, thereby leaving it in an undefined state. This is bad behavior according to the Node docs:
If a listener is added for this exception, the default action (which is to print a stack trace and exit) will not occur.
(...)
An unhandled exception means your application - and by extension Node.js itself - is in an undefined state. Blindly resuming means anything could happen.
(...)
uncaughtException should be used to perform synchronous cleanup before shutting down the process. It is not safe to resume normal operation after uncaughtException. If you do use it, restart your application after every unhandled exception!
-
queue.process
is asynchronous, as it submits messages using http requests. Though, it has no means of signaling an empty queue. This would be needed for implementing a submit-exception-then-exit behavior.
Discussion
In my opinion, exceptionless.js should assist the user in adding low-ceremony error logging to their applications while making reasonable choices for common use cases, as other Exceptionless clients already successfully do. Unhandled exceptions are a rather common use case, so what seems to be reasonable, unobtrusive behavior in the event of an unhandled exception?
Without reading any docs or sources, I would have expected requiring("exceptionless") and setting athe api key to enable the following:
- An unhandled exception occurs.
- The error is logged to the console.
- The exception is added to the submission queue.
- The submission queue gets processed. No other code is allowed to run in the meantime.
- Any errors in regard to submitting the queue also get logged to the console.
- The process exits with code 1.
This would be in accordance to the Node documentation by not letting any application code run after an unhandled exception occurs.
Step 4 in particular seems tricky to get right. In an asynchronous environment like Node, how can we process our queue while blocking any other code from execution? There is no http.requestSync
.
Some libraries try to imitate synchronous behavior using child_process.spawnSync
- this would require forwarding the currently queued messages and configuration as arguments, submitting the queue in the subprocess and returning any errors to the parent process.
Before I go ahead and mess with the codebase, I would appreciate any of your comments about this topic.