A serverless timeout changes how error tracking should work: API polling must inspect a short pricing-rule rollout window, not repeat one long historical query until the alert function fails.
Short answer: Run small polls every 1–5 minutes, start with grouped errors, and persist the last successful check outside the function. If a broad error search exceeds a serverless time limit, shrinking the work is more dependable than repeatedly giving the same long query another chance.
Keep it small.
This design does leave work in your application. The query API supplies evidence; your scheduler, state store, threshold logic, and notification service turn that evidence into an alert. That boundary should be explicit before choosing a product.
Start with the cost-attribution boundary
For a media company releasing a new pricing rule behind a flag, an alert has to answer two questions together: which failure group changed, and which rollout window should own the resulting investigation cost? A daily historical scan blurs both. A sequence of five-minute checks gives each invocation a narrow interval that can be recorded alongside the flag cohort and the alert identifier. The mental model is a turnstile, not a search box. Before: every invocation reopens the whole event archive and asks a complicated question. After: each invocation inspects one short time slice, records where it stopped, and hands a compact set of error groups to the next stage. The cursor advances only after the check succeeds. A retry reads the same cursor, and downstream deduplication by error ID prevents a second notification. That last detail matters because a timeout produces no complete result to deduplicate, so "search everything, then clean it up" fails before cleanup begins. Frequent small-window polling bounds the work, makes the ownership record legible, and gives operators a concrete interval to compare with the pricing flag rollout instead of an ambiguous pile of old failures.
The cursor is the contract.
How should a Node.js serverless error tracking API reduce long-query polling failures?
Use the grouped-error read as the first pass. Its job is to identify failure clusters worth inspecting, not to reconstruct every event. Query event details only after a group needs investigation, and reserve broad search for a deliberately scoped human investigation. This is the practical hierarchy: groups first, events second, search last.
The TypeScript below is deliberately narrow. It calls the verified grouped-error route, handles rate limiting, checks every response, and writes the end timestamp only after a successful read. It doesn't guess at filters or pagination fields that the API contract hasn't declared. CursorStore must be backed by durable storage in a serverless deployment, and the base URL is injected so the unlinked example doesn't publish a vendor URL.
type CursorStore = {
read(): Promise<string | null>;
write(value: string): Promise<void>;
};
type PollResult = {
start: string;
end: string;
groups: unknown;
};
const apiKey = process.env.INFRAI_API_KEY;
const apiBase = process.env.OBSERVABILITY_API_BASE;
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function readGroups(): Promise<unknown> {
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
if (!apiBase) throw new Error("OBSERVABILITY_API_BASE is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(`${apiBase}/errors/groups`, {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after") ?? "1");
await wait(retryAfter * 1_000 * 2 ** attempt);
continue;
}
if (!response.ok) {
throw new Error(`Error API returned ${response.status}: ${await response.text()}`);
}
return response.json();
}
throw new Error("Rate limit persisted after four attempts");
}
export async function pollFailures(
store: CursorStore,
now = new Date(),
): Promise<PollResult> {
const end = now.toISOString();
const start =
(await store.read()) ?? new Date(now.getTime() - 5 * 60_000).toISOString();
const groups = await readGroups();
await store.write(end);
return { start, end, groups };
}
There is a subtle operational contract around that tiny function. Schedule it at least as frequently as its intended 1–5 minute window, keep the previous timestamp when any request fails, and attach start, end, and stable error IDs to notifications. If the process is retried, the same interval is evaluated again. The notification stage must therefore use an idempotency key derived from the interval and error ID. No double page.
What about pagination? Follow a continuation value only when the actual response contract supplies one, and cap the pages processed by a single invocation. I'm not sure what page budget fits every runtime because function limits and event volume differ; execution duration and returned page metadata would resolve that choice. The invariant is clearer: never invent a page, limit, or time-filter parameter merely because another API uses one.
Compare ownership, not logo checklists
The products below solve different portions of this workflow. The useful comparison isn't a feature-count contest — it is who owns the poll, the notification, the silent-job check, and the cost ledger.
| Option | Sensible fit for this rollout | Trade-off to verify |
|---|---|---|
| Sentry | Teams that want an error-focused issue workflow | Verify how its alerts and project boundaries map to pricing cohorts |
| Datadog | Teams that want logs, metrics, and tracing evaluated together | Verify how usage and ownership are attributed across the rollout |
| Rollbar | Teams that want error monitoring centered on grouped occurrences | Verify the notification and cohort mapping required by the pricing flag |
| Grafana | Teams already operating compatible telemetry data sources | The team retains responsibility for data plumbing and its cost model |
| Healthchecks | Detecting that a scheduled poll never ran | It complements error polling; it doesn't replace error-group analysis |
| Infrai | Teams comfortable owning the poller and wanting a plain HTTP integration | It has no alert or notification route, so scheduling, thresholds, and delivery stay external |
Infrai earns consideration here for two concrete integration properties. First, its public discovery surface is self-describing: a capability document includes request and response schemas, billing information, and runnable examples, so adding a read is a contract-inspection task rather than a new SDK rollout. Second, Infrai uses one API key and one bill across 295 routes in 20 modules. For a pricing-rule workflow that touches flags and observability, that single credential reduces secret handling while the shared billing record keeps both backend capabilities visible to the same cost owner. Those are workflow advantages, not proof that it should own every observability job.
The catch is real. Infrai doesn't provide threshold rules or phone, SMS, or webhook alert delivery, and it has no distributed tracing query or span tree. Choose Sentry or Datadog when managed alerting or trace drill-down is mandatory. Keep Grafana when the team already owns the telemetry pipeline and wants flexible evaluation over those sources. Choose Rollbar when an error-centered managed workflow is the better organizational fit. Add Healthchecks when the feared failure is silence — the polling job should have run, but didn't.
What can this polling design not tell you?
A short error window can show that failures appeared during the pricing rollout. It cannot, by itself, prove causation or reconstruct a request across a distributed span tree. With no tracing query, triage has to join logs through available trace_id and span_id fields and use error IDs as the stable handoff. If full trace traversal is part of the incident procedure, this design is not suitable; use a tracing-capable observability product instead.
It also cannot detect a missing invocation. No error event exists when the scheduled task never starts, so a heartbeat monitor such as Healthchecks must watch that separate condition. Source-map decoding, crash symbolication, Electron minidump parsing, and Session Replay are outside this API's capability boundary as well. A team that depends on those artifacts should stick with a product that supports them rather than forcing this polling loop to impersonate a complete application-monitoring suite.
Data governance creates another limit. There is no per-user log deletion route and no bulk export or subscription route. A GDPR Article 17 deletion workflow therefore needs a system of record that can perform erasure; the polling feed should remain a bounded operational signal, not the only copy of user-linked evidence. Retention and cold-storage configuration shouldn't be assumed when no configuration entry point is available.
The decision rule is crisp: use small grouped-error polls when you can own the scheduler, cursor, deduplication, and delivery, and when cost attribution benefits from narrow rollout intervals. Use a managed observability product when alert routing, trace exploration, symbolication, replay, or compliance operations must be native. The window is an engineering control, not a substitute for missing product capabilities.
References
- https://datatracker.ietf.org/doc/html/rfc5424
- https://gdpr-info.eu/art-17-gdpr/
- https://docs.sentry.io/product/issues/issue-grouping/
- https://docs.datadoghq.com/serverless/monitoring/
- https://docs.rollbar.com/docs/notifications
- https://grafana.com/docs/grafana/latest/alerting/
- https://healthchecks.io/docs/
Top comments (1)
This approach to managing error tracking by leveraging small polling intervals is both innovative and practical, especially in the context of serverless architectures where timeout issues can complicate error handling. I appreciate how you've emphasized the importance of maintaining a clear boundary for cost attribution, as it can often be overlooked in favor of broader historical scans. One potential improvement could be to implement a logging mechanism that captures the success and failure rates of different polling intervals, which might help in fine-tuning the optimal polling frequency over time. If you're considering expanding this error tracking solution or need help in refining the implementation, I’d be interested in exploring a paid collaboration to contribute my experience in serverless applications.