skip to content

In Sentry, how is an event's fingerprint derived to group it into an issue, and what goes wrong at each extreme?

level: seniorimportance: should knowfreq 54%

answer

  1. not one issue per occurrence
  2. the event decides, not you
  3. frames plus the exception type
  4. one issue hiding twenty bugs
  5. variable text in the message splits it

basics

~20 s

Sentry groups events by a fingerprint computed from the event itself -- normally the stack trace's in-app frames and the exception type, falling back to the exception value or message when there is no trace. Same fingerprint, same issue.

solid answer

~50 s

Sentry computes a **fingerprint** for each event and folds every event sharing it into one **issue**, so a bug that fires forty thousand times is one row with an assignee, a status and a first-seen release. The default fingerprint comes from the event: the stack trace's frames, weighted toward in-app ones, plus the exception type, falling back to the exception value and then to the message when there is no trace. Both extremes hurt. **Over-grouping** -- everything rethrown from one wrapper, a generic exception type, obfuscated frames -- hides a dozen real bugs under one resolved heading. **Under-grouping** -- an id or an elapsed time interpolated into the message -- produces a wall of one-event issues that alert forever and accrue no history. Narrow the fingerprint to fix the first, keeping `{{ default }}` and adding a discriminator; parameterise the message to fix the second.

code

javascript · 11 lines
javascript
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  beforeSend(event) {
    const type = event.exception?.values?.[0]?.type;
    if (type === "PermitGatewayTimeout") {
      // refine the default grouping instead of replacing it
      event.fingerprint = ["{{ default }}", "permit-gateway-timeout"];
    }
    return event;
  },
});

go deeper

for a junior

Know that Sentry does not create one issue per occurrence: identical failures fold into a single issue with a count. Be able to say roughly what the fold is based on -- the stack trace and the exception type, not the time it happened.

for a middle

Explain how the default fingerprint is derived and why line numbers are excluded while interpolated message text is not. Be ready to describe setting a custom fingerprint and what {{ default }} adds when you include it.

for a senior

Bring a real case of over- or under-grouping you diagnosed, what it cost the team, and which lever you used: a fingerprint, a server-side grouping rule, corrected in-app frames, or removing an over-eager wrapper. Say why merging is only a stopgap.

for a principal

Own grouping as a platform concern: conventions for exception types and message templates, artifact uploads that keep frames symbolicated everywhere, and the review habit that catches a fingerprint containing an unbounded value before it reaches production.

## Why grouping exists at all Under load, one bug is not one error. A permit-renewal service holding a 5,400-request-per-second peak on the last day of the month can emit the same upstream timeout forty thousand times in ten minutes. A tool that showed you forty thousand rows would be a log store, not an error monitor. So Sentry computes a **fingerprint** for every event and folds all events sharing it into one **issue** -- the unit that carries an assignee, a status, a first-seen release and a resolution. Everything an error monitor is good for depends on that fold being right. ## How the default fingerprint is derived Sentry derives grouping components from the event itself, in rough order of preference: 1. **The stack trace, when there is one.** The frames -- module, function and filename -- are the strongest signal, weighted toward frames marked `in_app`, together with the exception type. Line numbers are deliberately excluded, so an unrelated edit above the failing line does not split the issue in two. 2. **The exception type and value**, when the event has an exception but no usable trace. 3. **The message**, when it has neither. A parameterised message (a template plus its parameters) groups on the template; a fully interpolated string groups on the whole string. Chained exceptions are considered together, and grouping enhancements configured on the project adjust which frames count -- marking library frames as out-of-app, or ignoring a frame that only ever appears as a wrapper. Two things follow immediately. **The fingerprint is derived from the event**, not configured per issue after the fact. And **anything variable that reaches a grouping component splits the issue**. ## The two failure modes | | One issue that is really twenty bugs | Twenty issues that are really one bug | | --- | --- | --- | | Symptom | an enormous event count with unrelated stack traces inside one issue | a wall of near-identical issues, each holding a handful of events | | Usual cause | everything rethrown from one wrapper; a generic error type carrying the detail in its message; obfuscated single-letter frames; an over-broad custom fingerprint | ids, timings or URLs interpolated into the message; frame paths that change every build; a custom fingerprint containing a variable | | What it costs | resolving hides live bugs; assignment is meaningless; the count tells you nothing | new-issue alerts fire forever; no issue accrues history; the quota burns on duplicates | | Fix | narrow the fingerprint; correct the in-app rules; upload mapping artifacts; stop collapsing distinct exception types into one wrapper | parameterise the message; fingerprint without the variable part; add a server-side rule; merge existing issues as a stopgap | Over-grouping is the more dangerous of the two because it looks healthy. The issue has a big count and a clear title, someone reads the top stack trace, fixes that bug and resolves it -- and the other twelve bugs inside keep happening under a resolved heading that nobody opens again. Under-grouping is merely loud, but it destroys the one thing the tool is for: an issue that is created fresh every few seconds has no history, so you can never say whether it is new. ## Controlling grouping deliberately Three levers, in increasing order of blast radius: - **A custom fingerprint on the event**, set in the SDK's before-send hook or on the event object. Keep `{{ default }}` as one element when you want to *refine* the built-in grouping rather than replace it; a fingerprint that omits it collapses every matching event into a single issue no matter what the trace says. - **Server-side rules** in the project's issue-grouping settings, applied to incoming events without a deploy. This is the right tool when the noisy pattern comes from a dependency you cannot change, or when you need the fix live before the next release. - **Merging issues in the UI**, which joins existing issues and keeps routing later matching events into the merged issue. It is sticky rather than a one-off tidy-up, and Sentry can unmerge, but it does not change how fingerprints are computed -- it treats the symptom. Two operational facts that candidates routinely get wrong. First, **grouping changes apply going forward**: events already stored keep the fingerprint they were ingested with, so after a change you live with the old issue and the new one side by side until the old one ages out. Second, **a custom fingerprint must never contain an unbounded value** -- a request id, a timestamp, an elapsed duration, a customer id on a large estate -- because that turns one issue into one issue per event: no aggregation, and a quota spent on storing the same bug thousands of times under thousands of headings.

  • What does including `{{ default }}` in a custom Sentry fingerprint do?
    It keeps the grouping components Sentry computed and adds yours alongside them, so you refine the default rather than replace it. A fingerprint of `["{{ default }}", tenant]` splits an existing issue per tenant while still separating unrelated stack traces. A fingerprint that omits it collapses everything your rule matches into one issue regardless of the trace, which is how a well-meant fix creates over-grouping.
  • You merge two Sentry issues in the UI. What actually happens to later events?
    The issues become one, and later events matching either original fingerprint land in the merged issue, so the merge is sticky rather than a one-time tidy-up; Sentry can also unmerge to split them apart again. What it does not do is change how fingerprints are computed, so the underlying grouping still produces the same hashes. Merging is a workaround; a fingerprint or a grouping rule is the fix.
  • Why do obfuscated frames make grouping worse, not just harder to read?
    The default grouping reads function and module names out of the frames. When every symbol in a bundle is `t`, `e` or `n`, two unrelated bugs can produce identical grouping components and collapse into one issue, and a rebuild that renames them can split one bug across releases. Uploading the mapping artifacts fixes the readability and the grouping in the same move.

Grouping is the difference between a defect tracker and a log file: one row per bug, not one row per victim.

saying these in an interview costs you the question

  • Thinks each occurrence of an error becomes its own Sentry issue
  • Believes Sentry groups purely on the exception message text
  • Sets a custom fingerprint containing a request id or timestamp
  • Reaches for issue merging as the cure for over-grouping
  • Expects a fingerprint change to regroup already-stored events
  • Interpolates ids into error messages, then wonders why issues explode