Stringifying Errors: A Uniquely Javascript Problem
Evan K
Posted on September 29, 2024
Exception handling, like most things in the Javascript world, is...complicated.
What is or isn't supported may vary widely between client (browser) and server (NodeJS), and further even from one browser to another.
If you're not too familiar already, this is a great guide to all things Javascript error handling.
The short version goes like this:
- Exception handling typically involves throwing and catching exceptions, either synchronously (in a try...catch block) or asynchronously (with Promise.prototype.catch).
- Javascript provides a standard built-in Error class for you to use, either directly or with extending sub-classes.
- The standard Error class comes with useful but not-always-supported (and so not-always-used) features like the stack and cause properties.
- The throw statement allows you to throw a user-defined exception, which is notably not required to be an Error object.
The Error class
Error objects are great, but the biggest headache around handling them typically involves stringifying or serializing them, as is evident from this NodeJS console example:
> new Error('a problem occurred').toString()
'Error: a problem occurred'
> JSON.stringify( new Error('a problem occurred') )
'{}'
The error object in question — an instance of the Error class — supports conversion to a string via its toString
method. This produces (essentially) a concatenation of the name
and message
, omitting the stack and any other instance properties. Sometimes this is good, as you just want to know what broke but not necessarily where.
Serialization to JSON is decidedly unhelpful, returning an empty object.
Logging to different destinations
For debugging and most logging purposes, the built-in console.log
method prints a friendly and verbose summation of any error directly to stdout (in NodeJS), or to the web console (in browser):
And if instead you need it printed to stderr, look no further than the console.error
method.
Well, what if you need to log an error somewhere else? To a file, a database table, a third party logging utility, etc.
You might wonder if there's a way to capture this same output from the console to a string variable, and in NodeJS you technically could. It is not simple, however, and one approach requires another console instance and some trickery with streams.
Streams give me anxiety, so I went another way.
Enter loggable-error
A little over two kilobytes of javascript, with zero runtime package dependencies and a single default export.
It produces a string approximate to what console.log
would print — an Error object expanded to constructor name, message and stack, with any other instance properties in an object notation:
import stringify from 'loggable-error';
class CustomError extends Error {
constructor(message, options) {
super(message, options);
this.extra = options?.extra;
}
}
try {
throw new Error(
'testing one two three',
{
cause: new CustomError(
'the actual root cause',
{
extra: {
message: 'some extra debugging info',
values: [ 3.14, 42, null ]
}
}
)
}
);
} catch(e) {
process.stdout.write(
stringify(e)
);
}
/* =>
Error: testing one two three
at file:///home/jdoe/test.js:11:11
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5) {
[cause]: CustomError: the actual root cause
at file:///home/jdoe/test.js:14:20
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5) {
[extra]: {"message":"some extra debugging info","values":[3.14,42,null]}
}
}
*/
As shown above, it also handles error objects nested as properties with recursive calls to itself, indenting them at the appropriate level.
You've got options
Because different situations call for different formatting, the exported function accepts a second options
object, allowing you to toggle display of stack traces and control the initial indentation:
// shown here with the default options
stringify(e, { depth: 0, stack: true });
That's all, folks!
The goal here was to keep it simple, but if you have issues or feature requests, I'd love to hear them.
Posted on September 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024