stanch
v0.1.1
Published
Suppress repeated events by key within a time window while counting what you dropped — kill log floods and alert flapping.
Maintainers
Readme
stanch
Suppress repeated events by key within a time window while counting what you dropped — kill log floods and alert flapping.
The Problem
A tight error loop logs the same message 40,000 times per second. An alerting rule flaps on/off every few seconds and pages someone 200 times. Both drown signal in noise and cost real money through log ingestion fees and pager fatigue.
Current solutions fall short. lodash.throttle/debounce are time-based on a single call site and don't count what they suppressed or key by message content. Logging frameworks have sampling, but it's config-heavy and framework-specific. There's no tiny, general "collapse repeats of the same key within a window and tell me how many I dropped" primitive.
Install
npm install stanch
# or
pnpm add stanch
# or
yarn add stanchUse
import stanch from "stanch";
const gate = stanch({ windowMs: 10_000 });
if (gate("ECONNREFUSED: 192.168.1.100:5432")) {
logger.error({ dropped: gate.dropped("ECONNREFUSED: 192.168.1.100:5432") }, msg);
}Realistic example with multiple error types:
import stanch from "stanch";
const gate = stanch({ windowMs: 10_000, max: 3 });
function handleError(error: Error): void {
const key = `${error.name}:${error.message}`;
if (gate(key)) {
logger.error({
error,
dropped: gate.dropped(key),
meta: { severity: "high" }
}, error.message);
}
}API
export interface StanchOptions {
windowMs: number; // suppression window per key
max?: number; // allow up to `max` passes per window, default 1
onFlush?: (key: string, dropped: number) => void; // called when a
// key's window closes with drops > 0
clock?: () => number; // test seam, default Date.now
}
export interface Stanch {
(key: string): boolean; // true = ALLOW (emit), false = SUPPRESS
dropped(key: string): number; // suppressed count in current window
reset(key?: string): void; // clear one key or all
readonly size: number; // active keys being tracked
}
function stanch(options: StanchOptions): Stanch;
export default stanch;First call for a key starts its window and returns true. Up to max calls per window return true; further calls return false and increment that key's dropped counter. When a window elapses, the key resets; if onFlush is set and dropped > 0, the callback fires with the final count.
Non-goals
stanch intentionally does NOT provide:
- Cross-process deduplication: Single-process, in-memory only. Use Redis
SET NX PXfor distributed dedupe. - Persistence: All state lost when process exits. Use database-backed idempotency keys for persistence.
- Content hashing: Caller supplies the key. Compose with
crypto.createHash()if you need content-based keys. - Framework binding: No pino/winston/express middleware. Wrap
stanchin your adapter layer instead.
TypeScript
stanch is written in TypeScript with strict mode enabled. Full type definitions are included. The function signature is:
import stanch from "stanch";
const gate: Stanch = stanch({ windowMs: 10_000 });Related Packages
Caching & Concurrency:
- @azghr/filterkit — Framework-agnostic, type-safe filtering for TypeScript
- @azghr/singlet — Deduplicate concurrent async calls
- staleness — Stale-while-revalidate caching for async functions
Text Processing:
- @azghr/shorn — Truncate strings by byte budget without breaking graphemes
- seriatim — Sequential processing utilities
HTTP & Network:
- forbear — Read server rate-limit instructions from HTTP responses
- forestall — Delay execution until a condition is met
- obviate — Render operations unnecessary through caching
System & Process:
- quiesce — Ordered, timeboxed graceful shutdown for Node
- sortition — Deterministic percentage rollouts and A/B bucketing
Utilities:
- expunge — Remove or exclude items from collections
- occlude — Hide or mask data and functionality
- placemark — Geographic location and mapping utilities
- specie — Currency and financial calculations
License
MIT
