How grouping works
Ten thousand events are not ten thousand problems. Grouping turns a stream of events into a list of distinct faults, and the only useful version of that is one you can predict: the same error must always land in the same group, and you have to be able to see why it did.
Every step below is a pure function of the event. Nothing here is statistical, nothing is trained, and nothing changes its mind between two runs. Any group page shows its own worked example.
01Detect the platform
Java and V8 stack traces have nothing in common past the leading
at. Each has its own parser, picked from the platform the client declared or, when it declared none, from whichever parser recognises the text with the highest confidence. A trailing:line:columnpair is decisive: the JVM never emits one.02Separate application frames from vendor frames
A frame belongs to the application unless it comes from the runtime, a framework or a dependency —
java.,org.springframework.,node_modules/,node:.This matters more than it looks. One bug in your code is reached through a different framework path depending on which request hit it. Grouping on those paths splits one fault into a dozen groups.
03Drop what changes between occurrences
Line numbers are excluded. Adding a line above a throw site shifts every number below it, and a fingerprint that moved on every such edit would open a new group for an error that never changed.
Paths are cut at the last recognisable source root, so
/app/src/cart.jsin a container and/home/dev/checkout/src/cart.json a laptop are the same frame.Inside messages, identifiers, addresses, timestamps, paths and plain numbers become placeholders. The rules run from most specific to least: a UUID contains digits, so replacing digits first would shred it, and two messages differing only by identifier would stop matching.
04Leave the message out when there are frames
Messages carry values and runtimes reword them between releases. Where a stack trace exists, the frames say what happened more reliably, so the fingerprint is built from the type and the in-app frames alone.
When there is no trace, the normalized message is all there is and it is used — the group says so on its page.
05Hash, and record the version
The assembled text is hashed with SHA-256 and truncated to 128 bits. A group is keyed by that hash and the algorithm version that produced it.
When the algorithm changes, old groups are frozen rather than recomputed. A group carries observed facts — when it was first seen, how often it happened — and a new version re-partitions events instead of relabelling them: groups can merge and split at once, so there is no correct answer for which first-seen survives. The honest cost is that a still-occurring error opens a new group after a version bump, which is why the version changes rarely and on purpose.
What grouping deliberately does not do
Minified frames. Minified names are reassigned on every build, so grouping on them opens a fresh group per deploy — worse than not grouping. They are detected and the message is used instead. Resolving them properly needs source maps, which this project does not accept yet.
Guessing. Similar groups are surfaced with Postgres trigram similarity over an index, and shown as suggestions on a group page. They never merge anything on their own.