{"course":{"slug":"cloud-and-shipping","title":"Shipping It","url":"https://addaly.com/learn/cloud-and-shipping","level":"intermediate","track":"build"},"lesson":{"slug":"errors-that-find-you","title":"Errors that come to you","url":"https://addaly.com/learn/cloud-and-shipping/errors-that-find-you","position":24,"of":74,"minutes":8,"answer":"An error nobody sees is an error nobody fixes, so send exceptions somewhere that groups them, counts them and tells you which release they started in.","keyIdea":"An error nobody sees is an error nobody fixes, so send exceptions somewhere that groups them, counts them and tells you which release they started in."},"body":{"format":"markdown","text":"## The gap nobody notices\n\nYou have logs. An exception is in them. And nobody reads logs when nothing appears to be wrong, which is exactly the situation where a bug affecting 2% of users lives for four months.\n\nThe fix is a piece of infrastructure that is small: catch unhandled exceptions, send them somewhere, and have that somewhere group identical errors together and count them.\n\n## What an error tracker does that a log file cannot\n\n- **Grouping.** Ten thousand occurrences of the same exception become one item with a count, instead of ten thousand lines.\n- **First seen and last seen.** The two most useful timestamps in an incident.\n- **Release tagging.** Tie every event to the deployed commit and the question \"did we cause this?\" is answered by looking at when it started.\n- **Breadcrumbs.** The sequence of events before the exception: the route, the query, the user action.\n- **Stack traces that survive minification.** For browser JavaScript, this is the whole game. Without uploaded source maps, your stack trace reads `a.b.c:1:4213` and is worth nothing. With them, it points at a line of your code.\n\n## Wiring it up is twenty minutes\n\nThree things to add on the server:\n\n```js\nprocess.on(\"uncaughtException\", (err) => { report(err); process.exit(1); });\nprocess.on(\"unhandledRejection\", (err) => { report(err); });\n```\n\nBoth matter. A Node process that swallows an `uncaughtException` keeps running in an unknown state, which is worse than crashing: a supervisor restarts a crashed process in a second; nothing restarts a wedged one.\n\nThen a middleware that reports any error escaping a route handler, and a client-side handler for browser errors.\n\n## The noise, and why people stop looking\n\nTurn on browser error reporting and your feed fills with things you cannot fix:\n\n- Browser extensions injecting scripts and throwing inside them.\n- `ResizeObserver loop completed with undelivered notifications`: benign, extremely common.\n- Network errors from users on trains, in lifts, and on hotel wifi.\n- Bots probing for `/wp-admin` and generating 404 handlers.\n- A cross-origin script error reported as the useless string `Script error.`\n\nIf you do not filter these, the feed becomes wallpaper within a week and you stop opening it, which returns you to where you started. So: ignore-list the known noise, sample high-volume non-actionable errors, and be willing to mark things as ignored. A feed with six items you believe is worth more than one with six hundred you do not.\n\n## What not to send\n\nError payloads are a privacy leak waiting to happen. Sending the whole request object attaches the `Authorization` header, session cookies, the password from the login form and whatever the user typed.\n\nScrub at the client, before sending: drop headers by name, drop known sensitive fields, truncate bodies. Every SDK has a hook for this; use it before you turn the SDK on rather than after.\n\n## Errors are not alerts\n\nAn error tracker is a queue of things to look at, not a pager. The rule of thumb:\n\n- **A new error type appearing after a deploy**: worth a notification.\n- **An error rate crossing a threshold**: worth an alert, but that belongs with your metrics.\n- **The 400th occurrence of a known issue**: worth nothing at all; you already know.\n\nWiring every error to a notification channel produces exactly the fatigue the next lesson is about.\n\n## The free path\n\n**Sentry** has a free tier of a few thousand events a month, which is more than a small site produces, and its SDKs are the best in this space. **GlitchTip** is open source, self-hostable, and speaks the Sentry protocol, so the same SDKs work against it. **Bugsnag** and **Rollbar** are the paid alternatives.\n\nAnd if you want none of them: a fifty-line handler that POSTs a JSON payload to a webhook and appends it to a table gets you grouping by a hash of the stack trace and a count. That is 80% of the value. The point is not the tool. The point is that a human learns about the exception without going looking."},"check":{"question":"You add browser error reporting and within a week the feed has 600 items a day, mostly from extensions, cross-origin `Script error.` entries and network failures on mobile. Your team has stopped opening it. What is the correct response?","options":[{"text":"Filter and sample the known-unactionable categories aggressively, so the remaining feed is small enough that every item gets read","correct":true},{"text":"Turn off client-side error reporting and rely on server-side errors, which are actionable","correct":false,"tempting":"Discards the only view you have of what users actually experience; most user-visible breakage in a modern app happens in the browser."},{"text":"Route all errors into a chat channel so they are seen as they arrive","correct":false,"tempting":"Moves the noise somewhere more disruptive and converts a feed people ignore into notifications people mute."},{"text":"Raise the reporting threshold so only errors occurring more than a hundred times a day are recorded","correct":false,"tempting":"Volume is not the same as importance: a critical bug on the checkout page may fire eleven times a day and would be filtered out."}]},"prev":{"slug":"dashboards-people-actually-read","title":"One screen that answers the question","url":"https://addaly.com/learn/cloud-and-shipping/dashboards-people-actually-read"},"next":{"slug":"health-checks-that-tell-the-truth","title":"The health check that lies","url":"https://addaly.com/learn/cloud-and-shipping/health-checks-that-tell-the-truth"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Errors that come to you', in Shipping It. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/cloud-and-shipping/errors-that-find-you (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Errors that come to you","courseTitle":"Shipping It","module":{"index":3,"slug":"knowing-what-it-is-doing","title":"Knowing what it is doing"},"answer":null,"keyIdea":"An error nobody sees is an error nobody fixes, so send exceptions somewhere that groups them, counts them and tells you which release they started in.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-gap-nobody-notices","runs":[{"text":"The gap nobody notices"}]},{"t":"p","runs":[{"text":"You have logs. An exception is in them. And nobody reads logs when nothing appears to be wrong, which is exactly the situation where a bug affecting 2% of users lives for four months."}]},{"t":"p","runs":[{"text":"The fix is a piece of infrastructure that is small: catch unhandled exceptions, send them somewhere, and have that somewhere group identical errors together and count them."}]},{"t":"h2","id":"what-an-error-tracker-does-that-a-log-file-cannot","runs":[{"text":"What an error tracker does that a log file cannot"}]},{"t":"ul","items":[[{"text":"Grouping.","b":true},{"text":" Ten thousand occurrences of the same exception become one item with a count, instead of ten thousand lines."}],[{"text":"First seen and last seen.","b":true},{"text":" The two most useful timestamps in an incident."}],[{"text":"Release tagging.","b":true},{"text":" Tie every event to the deployed commit and the question “did we cause this?” is answered by looking at when it started."}],[{"text":"Breadcrumbs.","b":true},{"text":" The sequence of events before the exception: the route, the query, the user action."}],[{"text":"Stack traces that survive minification.","b":true},{"text":" For browser JavaScript, this is the whole game. Without uploaded source maps, your stack trace reads "},{"text":"a.b.c:1:4213","code":true},{"text":" and is worth nothing. With them, it points at a line of your code."}]]},{"t":"h2","id":"wiring-it-up-is-twenty-minutes","runs":[{"text":"Wiring it up is twenty minutes"}]},{"t":"p","runs":[{"text":"Three things to add on the server:"}]},{"t":"code","lang":"js","label":"JavaScript","text":"process.on(\"uncaughtException\", (err) => { report(err); process.exit(1); });\nprocess.on(\"unhandledRejection\", (err) => { report(err); });"},{"t":"p","runs":[{"text":"Both matter. A Node process that swallows an "},{"text":"uncaughtException","code":true},{"text":" keeps running in an unknown state, which is worse than crashing: a supervisor restarts a crashed process in a second; nothing restarts a wedged one."}]},{"t":"p","runs":[{"text":"Then a middleware that reports any error escaping a route handler, and a client-side handler for browser errors."}]},{"t":"h2","id":"the-noise-and-why-people-stop-looking","runs":[{"text":"The noise, and why people stop looking"}]},{"t":"p","runs":[{"text":"Turn on browser error reporting and your feed fills with things you cannot fix:"}]},{"t":"ul","items":[[{"text":"Browser extensions injecting scripts and throwing inside them."}],[{"text":"ResizeObserver loop completed with undelivered notifications","code":true},{"text":": benign, extremely common."}],[{"text":"Network errors from users on trains, in lifts, and on hotel wifi."}],[{"text":"Bots probing for "},{"text":"/wp-admin","code":true},{"text":" and generating 404 handlers."}],[{"text":"A cross-origin script error reported as the useless string "},{"text":"Script error.","code":true}]]},{"t":"p","runs":[{"text":"If you do not filter these, the feed becomes wallpaper within a week and you stop opening it, which returns you to where you started. So: ignore-list the known noise, sample high-volume non-actionable errors, and be willing to mark things as ignored. A feed with six items you believe is worth more than one with six hundred you do not."}]},{"t":"h2","id":"what-not-to-send","runs":[{"text":"What not to send"}]},{"t":"p","runs":[{"text":"Error payloads are a privacy leak waiting to happen. Sending the whole request object attaches the "},{"text":"Authorization","code":true},{"text":" header, session cookies, the password from the login form and whatever the user typed."}]},{"t":"p","runs":[{"text":"Scrub at the client, before sending: drop headers by name, drop known sensitive fields, truncate bodies. Every SDK has a hook for this; use it before you turn the SDK on rather than after."}]},{"t":"h2","id":"errors-are-not-alerts","runs":[{"text":"Errors are not alerts"}]},{"t":"p","runs":[{"text":"An error tracker is a queue of things to look at, not a pager. The rule of thumb:"}]},{"t":"ul","items":[[{"text":"A new error type appearing after a deploy","b":true},{"text":": worth a notification."}],[{"text":"An error rate crossing a threshold","b":true},{"text":": worth an alert, but that belongs with your metrics."}],[{"text":"The 400th occurrence of a known issue","b":true},{"text":": worth nothing at all; you already know."}]]},{"t":"p","runs":[{"text":"Wiring every error to a notification channel produces exactly the fatigue the next lesson is about."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"Sentry","b":true},{"text":" has a free tier of a few thousand events a month, which is more than a small site produces, and its SDKs are the best in this space. "},{"text":"GlitchTip","b":true},{"text":" is open source, self-hostable, and speaks the Sentry protocol, so the same SDKs work against it. "},{"text":"Bugsnag","b":true},{"text":" and "},{"text":"Rollbar","b":true},{"text":" are the paid alternatives."}]},{"t":"p","runs":[{"text":"And if you want none of them: a fifty-line handler that POSTs a JSON payload to a webhook and appends it to a table gets you grouping by a hash of the stack trace and a count. That is 80% of the value. The point is not the tool. The point is that a human learns about the exception without going looking."}]}],"check":{"question":[{"text":"You add browser error reporting and within a week the feed has 600 items a day, mostly from extensions, cross-origin "},{"text":"Script error.","code":true},{"text":" entries and network failures on mobile. Your team has stopped opening it. What is the correct response?"}],"options":[{"text":[{"text":"Filter and sample the known-unactionable categories aggressively, so the remaining feed is small enough that every item gets read"}],"correct":true,"tempting":null},{"text":[{"text":"Turn off client-side error reporting and rely on server-side errors, which are actionable"}],"correct":false,"tempting":[{"text":"Discards the only view you have of what users actually experience; most user-visible breakage in a modern app happens in the browser."}]},{"text":[{"text":"Route all errors into a chat channel so they are seen as they arrive"}],"correct":false,"tempting":[{"text":"Moves the noise somewhere more disruptive and converts a feed people ignore into notifications people mute."}]},{"text":[{"text":"Raise the reporting threshold so only errors occurring more than a hundred times a day are recorded"}],"correct":false,"tempting":[{"text":"Volume is not the same as importance: a critical bug on the checkout page may fire eleven times a day and would be filtered out."}]}],"order":[3,2,1,0],"why":null},"prev":{"slug":"dashboards-people-actually-read","title":"One screen that answers the question","minutes":8},"next":{"slug":"health-checks-that-tell-the-truth","title":"The health check that lies","minutes":9}}}