Something threw. One name for every exception, with the detail in attributes.
| Field | Value |
|---|---|
| Name | exception |
| Severity | 17 (ERROR) |
| Written by | Every client, on error(). The browser tag can also catch uncaught ones, if you ask. |
| Turned off by | data-auto-errors is off by default, so the tag catches nothing on its own until you set it to "true". Your own error() calls are unaffected. |
Whenever you call error(). It takes anything, because a catch block catches anything: an error, a string, a rejected promise carrying a number, an object with a message. Whatever it is becomes a message rather than nothing.
One name for every exception, with the exception.* attributes saying which. Every exception is then one name and one particular exception is a filter on a path, rather than a thousand names nobody can enumerate.
It is a log entry like any other. There is no error table, no error pipeline and no separate ingest path. It is an error because of its severity and its attributes, and nothing in the backend branches on either.
Turned on, the browser tag also catches uncaught errors and unhandled rejections. That is the one automatic measurement that is off by default: it is a behaviour change for a site already running the tag, and it is the only one whose volume you do not control, because a third-party widget throwing on every page load produces entries at a rate nothing on the page is choosing.
| Attribute | Type | What it holds |
|---|---|---|
| exception.type | string | The class of the thrown thing. Error when it did not have one. |
| exception.message | string | Its message, or the thrown thing itself as a string when it was not an error. |
| exception.stacktrace | string | The formatted stack, as one string with newlines. Absent when there was none. The Node client appends the cause chain to it. |
| body | string | The message again as the human-readable line. Node client only. |
| exception.escaped | boolean | true when it reached the top of the stack. Written by the tag's automatic handler, since escaping is what makes it worth an entry. |
| url.full | string | Where it happened. Written by the tag's automatic handler only. |
| firstrun.exception.source | string | unhandledrejection when it was a rejected promise rather than a throw. Absent otherwise, so the two stay one name and one filter. |
Every entry also carries the resource its client sends once per batch: session.id, user.id once you have called user(), and the rest of the list on the overview. Anything you pass yourself lands in the same map, and your key wins on a collision.
No minidump, no symbol upload and no symbolication. A stack arrives as the string your runtime produced it as, and nothing here resolves it further.
Nothing is suppressed on the way past. The tag listens rather than assigning window.onerror, and never calls preventDefault, so the error still reaches the console and every other handler exactly as it would have. A tag that silently disabled somebody's error reporting would be worse than a tag that reported nothing.
A failed image or script tag is skipped. Those fire an error event on the way up with no message and no error object, and counting them would turn one broken tracking pixel into an entry per page view.
A widget is a filter, a group by, an aggregate, a time bucket and a limit. These are those five parts written out, and every one of them is something you can build yourself.
| Question | The query |
|---|---|
| What is breaking most | Name is exception . group by exception.type . count of entries . limit 20 |
| How many people it hits | Name is exception . count of uniques . bucket by day |
| Whether a release fixed it | Name is exception . group by service.version . count of entries |
| Only the ones that escaped | Name is exception . exception.escaped is true . group by url.full |
| Everything at ERROR or worse, whatever it is called | Severity is at least 17 . count of entries . bucket by hour |