Short answer: configure automatic error grouping around a stable error type, application stack location, operation, and release; keep property and request details as separate evidence. Raw messages are for investigation, not identity.
The least complex configuration that works is a deterministic grouping key built from the normalized error type, a stable application frame, the operation name, and the deployed version. In a property-management system, a useful group identifies the failure pattern consuming investigation time and still lets the team reconstruct the customer incident.
What does automatic error grouping need to preserve?
A Node.js error tracking service receives more than an error string: it can receive an error name, message, stack, request context, and deployment metadata. Node's error documentation describes name, message, and stack as important diagnostic fields, but they have different stability. A message can contain a tenant identifier, property address, payment amount, or upstream response. Those values help investigation and make poor grouping keys.
The event needs two layers:
| Layer | Purpose | Example fields |
|---|---|---|
| Group identity | Join recurring failures | normalized error type, stable frame, operation, release |
| Incident evidence | Reconstruct one occurrence | timestamp, trace context, request id, redacted input facts, original stack |
OpenTelemetry's logs model gives this separation a standard vocabulary: a log record has a body and attributes, and can carry severity and trace context. It does not prescribe an automatic error-grouping algorithm. That policy remains an application or backend decision.
How should Node.js configure the grouping key?
Keep the rule explicit and testable. Normalize the error type, select the first application frame, attach an operation such as maintenance.create, and record an immutable release. Do not put a property id, request id, or raw message into the group identity.
type GroupKey struct { ErrorType, Frame, Operation, Release string } func GroupFor(e ErrorEvent) GroupKey { return GroupKey{normalizeType(e.ErrorType), firstApplicationFrame(e.Stack), e.Operation, e.Release} } normalizeType should map equivalent wrappers only when the team can explain the mapping. A grouping function that silently guesses is hard to audit.
Two maintenance messages can differ only by property number and still be one operational pattern. A message can also contain a resident's name, vendor reference, or downstream response. Redaction must happen before an event enters a longer-retention system, and the original value should not be recoverable from the group key.
How should retention support cost attribution?
Start with the scarce resource that the evidence protects. In property management, one event may support a resident communication, maintenance escalation, payment reconciliation, or reliability investigation. The cost is not just storage; it includes indexing, query volume, alert fan-out, analyst time, and the risk of retaining sensitive data without a purpose.
| Event class | Grouping | Evidence to retain | Cost owner |
|---|---|---|---|
| Payment transition failure | Operation and stable code location | Correlation id, redacted inputs, outcome, release | Finance platform |
| Maintenance workflow failure | Operation, type, application frame | Property and work-order references, trace, stack sample | Operations platform |
| Background timeout | Operation and dependency category | Counts, latency summary, sampled stack, release | Shared platform |
Assign each class a retention tier before ingestion. A payment transition may need a durable audit record; a repetitive health-check exception may need only a grouped count and short sample. A grouped count cannot replace an audit trail when the question is which account changed and when.
There is a compliance limit: error tracking is not automatically an audit system. Retention periods, access control, deletion obligations, and the definition of a financial record depend on jurisdiction and policy.
Deployment changes expose the trade-off.
Watch for cardinality explosion. A release identifier, request id, or property id in the key turns one defect into thousands of groups. Over-merging is the opposite: every database, API, and queue timeout becomes TimeoutError, even when ownership and customer impact differ. Keep release in evidence always; include it in identity only when deployment changes the meaning of the stack or operation.
Test equivalent failures and materially different operations. In staging, inspect group cardinality, sampled evidence, and trace correlation. In production, review new groups per release and the top groups by event count and investigation cost.
Keep the release in evidence always; include it in identity only when deployment changes the meaning of the stack or operation. A value such as 2026.10.1 is useful for a rollback investigation, but splitting every unchanged failure by release creates noise and increases review cost.
Keep it boring.
This method has limits. A team that needs full-stack search, long-lived audit records, or very high-volume aggregation may need separate systems for those jobs; a compact error-tracking group cannot provide all three guarantees at once. Choose the boundary before ingestion, because moving raw data back after redaction is impossible.
Automatic grouping succeeds when it reduces duplicate investigation without weakening reconstruction. A compact group is an index; it is never a substitute for evidence that explains a customer-impacting transition.
Further reading
- OpenTelemetry, “Logs signal concepts”: https://opentelemetry.io/docs/concepts/signals/logs/
- Node.js, “Errors”: https://nodejs.org/api/errors.html
- W3C, “Trace Context”: https://www.w3.org/TR/trace-context/