Every team I've worked with had error tracking before they had Sentry: grep ERROR against a log aggregator, a Slack webhook someone bolted onto an exception handler, or nothing at all until a customer complained. The gap between that and a real error tracker isn't the capture step — a try/catch and a log line will catch the exception too. The gap is everything that happens after: knowing this exception is the same one that started three deploys ago, knowing it just jumped from 2 users a day to 200, and knowing which line in your actual source, not the 400KB minified bundle, threw it.
Why error tracking beats grepping logs
Logs are optimized for the question "what happened at this timestamp." Error tracking is optimized for a different question: "is this a new problem, a recurring problem, or the same problem affecting more people." Sentry groups events into issues by stack trace fingerprint, so a NullPointerException thrown from the same line 400 times in an hour shows up as one issue with a count of 400 and a list of affected users, not 400 lines you have to eyeball for sameness. That grouping is the entire value proposition. Once errors are grouped, you can triage by volume and by regression instead of by whichever log line you happened to scroll past.
The other thing logs don't give you for free is context: which user, which request, which release, which breadcrumbs led up to the crash. Sentry attaches all of that automatically to every captured event, so a report isn't "NullPointerException at line 214" — it's that plus the request payload, the user ID, the last five UI actions or HTTP calls before the crash, and the release version. That's usually enough to reproduce the bug without asking the reporting user anything.
Source maps: without them, a minified stack trace is useless
If you ship bundled, minified JavaScript — which is almost everyone shipping a frontend, and plenty of Node backends running through esbuild or webpack — the stack trace Sentry captures by default points at app.a3f9c2.js:1:284913. That's a single line of minified code containing your entire application. No amount of staring at it tells you which function threw. Source maps are the fix: they're a mapping file generated at build time that lets Sentry (or any tool) translate a minified position back to the original file, line, and function name.
The mechanics: your bundler emits a .js.map file next to each bundle. You upload those maps to Sentry as part of your release, tagged with the same release identifier your app reports at runtime. When an event comes in referencing a minified frame, Sentry looks up the matching source map for that release and rewrites the trace before showing it to you. If the maps were never uploaded, or the release tag doesn't match, you're back to staring at bundle offsets.
export SENTRY_AUTH_TOKEN=sntrys_xxx
export SENTRY_ORG=my-org
export SENTRY_PROJECT=my-app
sentry-cli releases new "$RELEASE"
sentry-cli releases files "$RELEASE" upload-sourcemaps ./dist \
--url-prefix "~/static/js" \
--rewrite
sentry-cli releases set-commits "$RELEASE" --auto
sentry-cli releases finalize "$RELEASE"
Uploading maps to Sentry and serving them alongside your production JS are different things. If your build pipeline writes .js.map files into the same directory that gets deployed to your CDN, anyone can download your original source. Upload the maps to Sentry, then delete them from the deploy artifact before it goes out.
Release tracking ties errors to deploys
A release in Sentry is just a string — a git SHA, a version tag, whatever you already use to identify a deploy — attached to every event and every source map upload. The value shows up the moment something breaks: Sentry can tell you an issue was "first seen in release 2026.7.14" and, more usefully, flag it as a regression if it had already been marked resolved in an earlier release and just came back. Without release tags, every deploy is invisible to your error tracker; a spike right after a deploy looks like a spike with no cause, instead of a spike your CI pipeline could point straight at the diff.
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.GIT_SHA,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
integrations: [
Sentry.httpIntegration(),
Sentry.expressIntegration(),
],
});
The release value here has to match exactly what you passed to sentry-cli releases new and used for the source map upload — this is the join key between "this error happened" and "here's the readable stack trace for it." Most CI setups just export the git SHA as an environment variable at build time and use it consistently for both the SDK init and the CLI commands.
Alert rule design: avoiding noise and alert fatigue
The default Sentry alert — "notify me on every new issue" — is fine for a project with ten users and dies immediately at any real scale. A single flaky third-party API will generate a new issue for every distinct error message it returns, and now you're getting paged for a vendor's rate limiter. The fix isn't turning alerting off, it's writing rules that match how much you actually care.
What's worked for me: alert on issue state changes (new issue, regression) rather than every event, and add a volume threshold so a single one-off exception doesn't page anyone — something like "more than 10 events in 5 minutes" catches a real spike without firing on background noise. Route by severity: unhandled exceptions in a checkout flow go to a pager; handled exceptions logged at warning level go to a Slack channel someone checks once a day. And mute or ignore known-noisy issues explicitly — a third-party SDK that throws harmlessly on every page load shouldn't sit in your inbox forever waiting to be triaged.
If your rule fires per event instead of per issue, one client retry-storming a broken endpoint can generate hundreds of notifications for what is, underneath, a single root cause. Scope alert conditions to "issue is new" or "event count exceeds N in period Y," not "an event was received."
Performance monitoring is a related but distinct feature
Error tracking answers "what broke." Sentry's performance monitoring — transactions and tracing — answers a different question: "what's slow, and where." A transaction is a unit of work, usually one HTTP request or one background job, broken into spans for each meaningful piece of work inside it: the DB query, the external API call, the template render. Sentry stitches these into a trace, and across a distributed system with multiple services, a single trace ID can follow a request from the edge through your API to your database and back, showing you exactly which hop added the latency.
This is worth turning on separately from error capture, and it's worth thinking about as its own signal — a service can have zero unhandled exceptions and still be serving p95 latencies that are quietly getting worse every release, and tracing is what surfaces that trend before it becomes a page.
Sampling considerations at scale
Errors are rare relative to total traffic, so capturing every one is usually fine and Sentry does this by default. Transactions are not rare — every request is a transaction — and capturing every single one at meaningful traffic volumes gets expensive fast, both in Sentry billing and in overhead on your own services. That's what tracesSampleRate in the init snippet above controls: 0.1 means roughly 10% of transactions get fully traced and sent, the rest are dropped before they leave the process.
Pick the rate based on traffic, not habit. A low-traffic internal tool can run tracesSampleRate: 1.0 and see everything. A public API doing hundreds of requests per second needs something closer to 0.01–0.05, or you'll be paying to store traces you'll never open. Sentry also supports a tracesSampler function instead of a flat rate, so you can sample health checks at near-zero and checkout endpoints at something much higher — the traffic you care about most doesn't have to share a sampling budget with traffic you don't.
Wrapping up
None of this — source maps, release tags, sane alert rules, tracing — is exotic, and none of it takes more than an afternoon to wire up properly. The return is disproportionate to the setup cost: readable stack traces instead of minified noise, a "first seen in" instead of a mystery regression, and an alert channel people actually trust instead of one they've muted. If you're setting this up for the first time, get source maps and release tracking working before you touch alert rules — noisy alerts on unreadable stack traces is the worst version of this tool, and it's the default you get if you skip both.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.