changelogWhat shipped, and when.
Backfilled from the commit history. Parsemend's history starts on the day the ingest pipeline first accepted an event from an unmodified Sentry SDK, which is why every entry below carries the same date.
Ingest hardening
A gzip bomb could OOM the worker; a dedup key silently dropped events; hex parameterization collapsed distinct errors.
Four fixes worth naming, because each was a real defect rather than a tidy-up:
- Decompression is now bounded on both ends. The compressed body is rejected
before a byte is inflated, and the inflate runs in 4 KiB chunks that abort the
moment cumulative output passes the cap. Inflating first and checking the length
afterwards meant a few dozen KiB of zeros could expand into gigabytes and take
the worker down with an uncatchable OOM instead of a clean
413.
- The digest dedup key no longer silently drops events. Two distinct events
arriving in the same envelope could collide on the key and one would vanish.
- Hex parameterization no longer collapses distinct error messages. Messages
differing only in a hex substring were being folded into one issue.
- Future client timestamps are clamped. A device with a wrong clock could
write an event into a partition that did not exist yet.
Issue triage UI
Issue list with status, level and project filters; issue detail with stack trace, breadcrumbs, tags and contexts.
The issue list filters by status, level and project, and searches title and
culprit. The detail view renders the stack trace, the breadcrumbs leading up to
the throw, the indexed tags and the event contexts.
Organizations are a tenancy boundary, and issues cannot be reassigned across it.
Rate limiting and reason-coded outcomes
Per-key fixed-window limiting that answers with 429, Retry-After and X-Sentry-Rate-Limits.
Over quota, Parsemend responds the way a Sentry SDK already expects: 429, a
Retry-After header, and X-Sentry-Rate-Limits naming the category and scope. The
SDK backs off without any change on your side.
Every dropped event is counted against a reason — invalid, rate_limited,
filtered — so "where did my events go" has an answer.
Partitioned events table and retention
Events live in a Postgres range partition per day. Expiring a day is a DDL statement, not a mass delete.
events is range-partitioned by received_at. A scheduled command creates
partitions ahead of time and drops the ones past their retention window.
The alternative — DELETE FROM events WHERE received_at < ? — holds locks and
leaves the table to vacuum for an hour. Dropping a partition takes milliseconds.
Default retention is 90 days.
Issue counters, tags and regression detection
Denormalized counters, an indexed tag table, and resolved issues that flip back to regressed on the write path.
Each issue carries times_seen, an approximate user count backed by a Redis
HyperLogLog, and a per-day event bucket. Tags, environments and releases are
indexed into a key/value table rather than scanned out of JSON at query time.
A resolved issue that sees a new event flips to regressed as the event is
written. No cron job, no nightly sweep.
Grouping cascade
Custom fingerprint, then in-app frame signatures, then exception type and value, then a parameterized message.
Grouping tries four strategies in order, and stops at the first that applies:
- A custom
fingerprint, including {{ default }} expansion.
- Signatures of the in-app stack frames, with recursion collapsed.
- The exception type and value, skipping the type when the mechanism is synthetic.
- The message, with variable parts parameterized out.
Many hashes can fold into one issue through the group_hashes table, so merging
two issues does not lose the events that arrived under the old hash.
Envelope parser with unknown-type tolerance
A byte-offset scanner handling both length-prefixed and newline-terminated items.
Sentry's envelope format delimits items two different ways: a length prefix, or a
newline. Real SDKs use both, sometimes in the same payload, and a length-prefixed
item may contain newlines of its own.
The parser is a byte-offset scanner rather than a line splitter, which is the only
way to get this right. Golden-file fixtures captured from the real PHP and Node
SDKs keep it that way.
Sentry-compatible ingest
The envelope endpoint, the legacy store endpoint, and all three DSN authentication forms.
Parsemend accepts events at POST /api/{project}/envelope and at the legacy
POST /api/{project}/store. The DSN key is read from the X-Sentry-Auth header,
from a sentry_key query parameter, or from the dsn field in the envelope
header — whichever your SDK happens to use.
Envelope item types Parsemend does not store are consumed and counted rather than
rejected. An SDK that sends a transaction alongside an error will not start
erroring because we do not keep the transaction.
Subscribe via RSS.