In today's tutorial, we're going to focus on a real-world application of extending the
Error prototype with ES6 classes and how we can use this for effective error tracing.
This tutorial expects that you have a Sentry account set up and are somewhat self-sufficient in getting your project up and going.
It also expects you are running a version of Node that supports ES6 classes. I am running
12.16.1 in this project.
Let's set up a new Nodejs project and install some deps.
We are going to use dotenv to hide away our Sentry endpoint.
Throw in the files we don't wish to store in Git.
While this won't go into details, we want to set up a new Nodejs project in our Sentry account.
New Sentry Nodejs project
Once it is set up, it will give you a
dsn URL that we are going to add to our
We are now ready to set up our custom error!
Add the following to
In the code, we are doing to following:
dotenvto read in our
SentryErrorextends from the
Errorprototype. We can use the constructor to initialise all the properties we are inheriting from Error.
constructor itself is a method called whenever we call
new SentryError() for a new
We are telling it we take an error message (similar to
new Error('error message')), data (which we will use to set breadcrumbs to help us debug) and an breadcrumb type that we use within the constructor (which defaults to
The first thing we do in the constructor is call
super(errMessage), which is us calling up the chain to the Error prototype. This will set properties on this object that we expect to get from
stack (which we will see later).
Afterwards, we are essentially setting a breadcrumb and telling Sentry to capture an exception. You can read more about these on the Sentry docs but the tl;dr is that these calls will populate our telemetry data on Sentry.
With this alone, we are ready to roll!
index.js, add the following:
Here, we are simply doing to following:
mainfunction that simply throws our new
data.nonExistentValuedoes not exist (which it won't).
mainwith an object of information that will be assigned to
Running the following will give us this:
catch block, you can see our new error has access to
stack properties, which we mentioned will be the case from above thanks to use call
super(errMessage) in our class to inherit the properties of the
If we head to Sentry, we can see our error has been logged!
If we look at the basic settings, we can see that our breadcrumb was logged under
data, the console logs are also tracked (this is configurable).
App error view
Those logs came from our
catch block. If we change from "App only" to "Raw" you can see our stack trace also shows up in the exception:
As a company, we do not want Personally Identifiable Data from our customers being shared with the third-party.
These tools for us are a way to help debug and trace back through a user journey to improve our product, and they trust us not to share this information.
There are a few ways that we can go about protecting ourselves, but one example I will give today is how we can implement our own "deny" or "block" list.
Let's make some small updates to our
index.js, let's update the info passed into
main to include some dummy user data (and my public email):
Let's say that we do not wish to share the name, user email, user's manager email or their address, but we DO want to keep the ID for debugging issues. We can add a helper method to our class and set up a
denyList that we can use in this method to recursively alter our breadcrumb data.
denyList outside or the class? There is no particular reason, but I find it makes it easier to write unit regex tests if this is abstracted and it can be used for other 3rd-party block lists you may want to set up.
redactSensitiveInformation could also have been pulled out of the class for the same reason if it was re-useable elsewhere.
redactSensitiveInformation uses the power of recursion. We basically want it to recursively check through an object to redact information that match a regex.
This means that the following:
...will become redacted to the following with our current deny list:
denyList.some iterates through our Regex array and if any regex matches, it will return "true" - this helps us identify from our list which data to redact.
node index.js again and confirm this in Sentry.
Today, we used ES6 classes to extend error. If anyone wants the "Why would you do that vs just extending the prototype?", my answer is that it is mainly preference.
I find classes in this case to be more readable and a better developer experience, but note that there is a cost if doing this in the web for transpiling back to ES5.
Today, we went with the idea of a "block" list. If you want a stronger alternative, go with the idea of an "allow" list where a property must be allowed before it will show up on Sentry.
Image credit: Ali Saadat