This is going to be a brief article primarily aimed at junior developers: when you’re building applications in the wild, things can and will go wrong in production. Whether accidental bugs, API downtime that’s out of your control, or outdated developer docs that lead you astray, you’ll build software that doesn’t work perfectly 100% of the time. Like death and taxes, there’s no getting around it.
The key is building so that you’re notified when things go wrong!
Beyond Error Logging
Even as a junior engineer, you’re probably familiar with error logging. Popular tools like Sentry make logging errors a mostly painless process; in fact, for most front and back-end tech stacks, there are options available for automatically logging errors. Database administrators and microservices architects often have a tougher problem, but error logging is usually not an impossible proposition.
What often gets missed by junior engineers is logging unexpected situations that don’t necessarily cause an error to be thrown. A good example is making an API call. Here’s some production TypeScript from an app I lead:
onCompleted: (res: SubscriptionStartResponse) => {
if (res?.stuSubsCreateportalurl?.portalUrl) {
setStripePortalUrl(res.stuSubsCreateportalurl.portalUrl);
} else {
Sentry.captureException(
new Error("Unexpected successful response, GetStripePortalUrl"),
{ extra: { response: res } }
);
// more code to handle this situation in the UI/UX
}
},
onCompleted
indicates that we’ve gotten a successful (200) response from our API. In the if
case, we got what we expected: a Stripe URL that will allow our users to view their subscription details. That’s the only successful response we ever expect to receive. If we get a 200 and res.stuSubsCreateportalurl.portalUrl
does not exist, something has gone quite wrong in our back-end. So we use our logging tool, Sentry, to log this situation.
Something unexpected has occurred in our application, but we’ll know about it!
(Note: I’ve simplified the code a bit; our error checking was slightly more complicated in practice than the code above shows)
Try / Catch Can Be Dangerous
In many languages, try/catch is powerful. It allows your application to continue after what otherwise would have been a fatal runtime error. That said, it’s dangerous. If you wrap code in a try/catch, most logging tools won’t automatically log errors that occur within the try.
You often want to be notified if errors occur in the try, even though you don’t want your program to stop execution. To do so, you’ve got to remember to log errors in the catch. Engineers forgetting this principle have led to some pretty insidious bugs throughout my career. No logging and the occasional user complaint “It doesn’t work!” is a recipe for a nightmare.
So, use your logging tool to log errors in the catch unless you really really know you don’t want to:
try {
// some code that can throw
} catch (e) {
// log the error!
Sentry.captureException(e);
}
Include Important Data
As a final note, I’ll add that when unexpected things happen in your app, having data that might help you determine what went wrong can be extremely helpful. Most logging tools allow you to include extra data in your log message. Think like a senior engineer — stop and consider what can go wrong, how it can go wrong, and what data will be helpful when debugging:
Sentry.captureException(
e, // the error object
{
// here we get to include extra data that might be helpful when debugging
extra: {
thing1: someData1,
thing2: someData2,
},
},
);
Top comments (0)