How to Set Up Error Monitoring You Don't Learn to Ignore
The purpose of error monitoring is to find out about problems before customers tell you, and to have enough context to fix them without reproducing. Most implementations achieve the first and fail the second: an alert fires, and the report contains a stack trace with no indication of which user, which request, or what they were doing.
The other common failure is volume. A system that alerts on every exception produces so much noise that people build filters, and then the one alert that mattered goes to an unread folder. Alert fatigue is not a discipline problem — it's a design problem.
Alert on symptoms, log everything else
Two different jobs. Logs are for investigating once you know something is wrong. Alerts are for finding out. Conflating them is what creates noise.
- ›Alert on user-visible symptoms: error rate above baseline, latency past a threshold, a queue backing up. Not on individual exceptions.
- ›Every alert should be actionable. If the answer is ever 'nothing, that's normal', delete or fix the alert.
- ›Log structured JSON, not formatted strings. You cannot query 'all errors for customer 42' in prose.
- ›Attach a request ID to everything and return it to the user on error, so a support ticket maps to exact logs.
- ›Scrub PII before it leaves your infrastructure. Error payloads capture request bodies, and that's how passwords end up in a third-party tool.
The approaches
Error tracking (Sentry, Rollbar, Bugsnag)
// best for: Exceptions in application code, on both server and client.
- Sentry
- React
- Next.js
- Django
- Laravel
- Express.js
- NestJS
These capture exceptions with stack trace, request context, and breadcrumbs, then group similar occurrences into a single issue. The grouping is the feature that makes them viable — ten thousand instances of one bug is one line in your dashboard, not ten thousand emails.
For frontend errors, upload source maps at build time. Without them, every browser error is minified gibberish pointing at column 40,000 of a bundle, which is worse than useless. Upload them privately as part of your deploy rather than serving them publicly.
The context you attach determines whether an issue is fixable. User ID, request ID, release version, and the relevant feature flags turn "TypeError: cannot read property of undefined" into something you can reproduce. Release tagging in particular lets you see immediately that an issue started with a specific deploy.
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.GIT_SHA, // ties an issue to a deploy
tracesSampleRate: 0.1, // sample, don't capture everything
// Error payloads capture request bodies. Without scrubbing,
// passwords and tokens end up in a third-party tool.
beforeSend(event) {
if (event.request?.data) {
for (const k of ["password", "token", "card", "ssn"]) {
if (k in (event.request.data as object)) {
(event.request.data as any)[k] = "[redacted]";
}
}
}
return event;
},
});The gotcha
Default configurations capture request bodies and headers, which means passwords, session cookies, and API keys get sent to your monitoring vendor and stored indefinitely. Configure scrubbing before you send real traffic — retroactively deleting captured secrets means rotating all of them.
Structured logging
// best for: Investigating what happened once you know something went wrong.
- Express.js
- Django
- FastAPI
- Go
- NestJS
- AWS
- Google Cloud
Logging JSON rather than formatted strings is the difference between grepping and querying. `{"level":"error","user_id":42,"request_id":"abc","msg":"payment failed"}` can be filtered by any field in any log platform. A printf-formatted string cannot.
The practice that matters most is a request ID generated at the entry point, attached to every log line for that request, propagated to downstream services, and returned to the client on error. A customer reporting a problem gives you that ID and you retrieve every log line from their request in one query. Without it, you're searching by timestamp and hoping.
Log levels need discipline: error means someone should look, warn means unexpected but handled, info means notable business events. Logging everything at error is how error monitoring becomes noise.
The gotcha
Logging entire objects with a spread or `JSON.stringify` captures whatever fields exist, including ones added later. A user object logged for debugging starts including a password hash the day someone adds it to the model. Log explicit fields, never whole objects.
Uptime checks and alerting
// best for: Knowing the site is down when nobody is looking.
- Sentry
- Cloudflare
- AWS
- Google Cloud
- Azure
Error tracking only reports errors your application successfully raised. If the process is dead, the database is unreachable, or DNS is broken, nothing gets sent — and everything looks quiet precisely when things are worst. External uptime checks cover the failure modes internal monitoring structurally cannot.
Check a real endpoint that exercises dependencies, not a static file. A health check that returns 200 without touching the database will happily report green during a database outage.
Synthetic checks that walk a critical path — log in, load the dashboard — catch breakage that a health endpoint misses, like a frontend deploy that shipped a broken bundle while the API stayed perfectly healthy.
Route alerts by severity. Site down pages someone; elevated error rate goes to a channel.
The gotcha
Alerting on absolute error counts rather than rates produces pages at every traffic peak. Fifty errors an hour is fine at a million requests and catastrophic at a thousand. Alert on error rate as a proportion, and on deviation from the normal baseline.
Common pitfalls
Alerting on every exception
Expected errors — validation failures, 404s, cancelled requests — drown the signal. People build inbox filters, and the filter catches the real incident too. Alert on aggregate rate, and investigate individual errors in the dashboard rather than by notification.
No source maps for frontend errors
Minified stack traces are unusable. Upload source maps privately at build time, tagged with the release, or your frontend error tracking produces reports nobody can act on.
No request correlation
Without a request ID threaded through every log line and returned to the user, a support ticket becomes a timestamp search across services. This is the cheapest high-value thing on the list.
Capturing PII by default
Request bodies, headers, and cookies commonly contain credentials. Configure scrubbing before sending real traffic — once secrets are in a vendor's storage, rotating them is the only remedy.
Questions people actually ask
What should page someone versus go to a channel?
Page for user-visible outage: site down, checkout failing, error rate several times baseline. Channel for everything else. If an alert has ever been acknowledged with no action taken, it should not be paging.
How long should I retain logs?
Thirty days covers most debugging; longer for anything with a compliance requirement. Costs scale with volume, so sample high-volume info logs and keep all errors.
Do I need APM as well as error tracking?
They answer different questions. Error tracking tells you something threw; APM tells you something is slow. Slowness rarely raises an exception, so error tracking alone leaves you blind to a whole class of degradation.