Package com.tnsai.observability.errors
Class DedupingErrorReportPublisher
java.lang.Object
com.tnsai.observability.errors.DedupingErrorReportPublisher
- All Implemented Interfaces:
ErrorReportPublisher
Decorator that suppresses repeat
ErrorReports of the same
ErrorReport.fingerprint() within a configurable time
window. The first occurrence in each window passes through to the
wrapped publisher; subsequent occurrences are dropped and counted.
Standard usage — wrap the SPI-discovered chain to keep your log aggregator from drowning when 1,000 concurrent users hit the same provider 500:
ErrorReportPublisher base = new Slf4jErrorReportPublisher();
ErrorReportPublisher deduped = new DedupingErrorReportPublisher(
base, Duration.ofMinutes(5));
ErrorReports.setPublisher(deduped);
Five-minute window matches Sentry / Rollbar defaults and the heuristic "if you can't see the error twice in 5 minutes you don't notice it's still happening."
What "same fingerprint" means
TheErrorFingerprint hash already partitions by error
class + code + normalised stack digest + relevant attributes
(e.g. provider, model). Two crashes from the same root cause
collapse to one fingerprint; crashes that differ in a meaningful
dimension stay separate. The dedup is therefore semantically
grouping by bug, not by exception object identity.
Counter exposure
getSuppressedCount(String) returns the running suppressed
count for a fingerprint. Tests assert against it; production
dashboards can expose it as a Prometheus gauge to surface "how
many duplicates were swallowed in the current window." The
counter resets to zero when the window for that fingerprint rolls
over (i.e. the next emit through the decorator).
Memory
One entry per distinct fingerprint, kept in aConcurrentHashMap. In practice fingerprint counts stay in
the tens to hundreds for a healthy service; the map doesn't need
eviction. If a deployment is producing thousands of distinct
fingerprints, that's a deeper problem than dedup memory pressure.
Thread safety
Backed by aConcurrentHashMap keyed by fingerprint.
Within a single fingerprint, the per-fingerprint state object
uses AtomicLongs for last-emit-nanos + suppressed-count;
publish() synchronises on the state object briefly to atomically
decide emit-vs-drop. Cross-fingerprint emits are fully parallel.- Since:
- 0.5.1 (issue #86 follow-up)
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final DurationDefault dedup window — matches industry-standard error trackers. -
Constructor Summary
ConstructorsConstructorDescriptionWrapsdelegatewith theDEFAULT_WINDOWdedup interval (5 minutes).DedupingErrorReportPublisher(ErrorReportPublisher delegate, Duration window) Wrapsdelegatewith the given dedup window. -
Method Summary
Modifier and TypeMethodDescriptionlonggetSuppressedCount(String fingerprint) Number of reports forfingerprintsuppressed since the last emit.voidpublish(ErrorReport report) Publish a single error report.Visible for tests / SPI introspection.intTotal number of distinct fingerprints currently tracked.static DedupingErrorReportPublisherwrap(ErrorReportPublisher delegate) Helper for callers building a default pipeline:DedupingErrorReportPublisher.wrap(new Slf4jErrorReportPublisher())reads more naturally than the constructor in fluent code.
-
Field Details
-
DEFAULT_WINDOW
Default dedup window — matches industry-standard error trackers.
-
-
Constructor Details
-
DedupingErrorReportPublisher
Wrapsdelegatewith theDEFAULT_WINDOWdedup interval (5 minutes). -
DedupingErrorReportPublisher
Wrapsdelegatewith the given dedup window.- Parameters:
delegate- downstream publisher; requiredwindow- minimum time between emissions of the same fingerprint; must be positive.Duration.ZEROeffectively disables dedup (every report emits).
-
-
Method Details
-
publish
Description copied from interface:ErrorReportPublisherPublish a single error report. Errors during publication must not propagate to the caller — the caller is already in an error path; an exception here would mask the original failure.- Specified by:
publishin interfaceErrorReportPublisher
-
getSuppressedCount
Number of reports forfingerprintsuppressed since the last emit. Returns 0 if the fingerprint has never been seen or its window has just rolled over. -
trackedFingerprintCount
public int trackedFingerprintCount()Total number of distinct fingerprints currently tracked. Useful as a sanity check / Prometheus gauge — should stay bounded for a healthy service. -
wrap
Helper for callers building a default pipeline:DedupingErrorReportPublisher.wrap(new Slf4jErrorReportPublisher())reads more naturally than the constructor in fluent code. -
snapshotSuppressedCounts
Visible for tests / SPI introspection.
-