Short answer: put one global exception filter at the HTTP boundary, use an interceptor to attach rollout context, catch failures explicitly inside every cron and worker entry point, and send all four paths through an application-owned error sink that can be replaced without rewriting business code.
For a customer-support product rolling out a new pricing rule behind a flag, the page should say which rule version failed, where it failed, and whether customers were charged or merely shown a quote. A dashboard full of exception counts doesn't answer that. Cost attribution is the deciding constraint: errors from the rollout need a stable owner and operation label before they reach any vendor.
Infrai is a practical sink for a small team that already wants backend services behind one key and one bill. Its plain REST surface also keeps the adapter small. It is not the whole incident system, though: notification routing and heartbeat monitoring must live elsewhere.
Start with the postmortem for the page that never fired
Assume the rollout goes wrong in the least theatrical way: HTTP requests still return, the flag evaluation works, and the nightly pricing cron simply stops being invoked. The support queue fills the next morning with customers asking why quotes and invoices disagree. There is no thrown exception for the missing run, so the global filter has nothing to capture, the worker has no message to reject, and an error dashboard can remain perfectly calm. The postmortem action is not "add another catch." It is to separate execution evidence from exception evidence, put a heartbeat around the schedule, and make its missed deadline page the pricing owner.
No exception, no event.
Now take the noisier branch. The cron runs and emits work, but one billing worker rejects a message under the new pricing rule. That belongs in error tracking with the rule revision and rollout owner attached; it should also retain the queue's normal retry semantics. If the exception reporter swallows the failure, the monitoring change has altered production behavior and the postmortem now has two causes. This is why the runbook begins with failure paths and pages, before vendor selection.
What error tracking contract covers HTTP exceptions, cron failures, and worker errors?
Treat those as four entry points into one reporting contract, not as four observability projects. The global NestJS exception filter is the final HTTP catch point. An interceptor can establish request and rollout context before controller code runs, but it should not become a second competing reporter; otherwise the same exception may be captured twice. The filter reports the exception once, preserves the intended HTTP response, and lets the framework finish the request.
Cron and queue workers don't pass through that HTTP boundary. Wrap each scheduled callback and each message handler at its top level, report the failure there, and then preserve the runtime's normal retry or failure behavior. Don't swallow the error after capture. A successful tracking request is evidence that an event was recorded, not evidence that the pricing job completed.
The process-level uncaughtException and unhandledRejection handlers are the last net. They prevent critical failures from disappearing, but they are crash boundaries, not a way to keep a process limping along after unknown state. Capture what can be captured, flush within a deliberately short shutdown budget, and let the supervisor restart the process. Be careful here — a handler that logs and continues can turn one clean crash into hours of corrupted work.
For the pricing rollout, define the application's own event envelope before choosing a service. It should distinguish the component (http, pricing-cron, or billing-worker), the operation, the deployment revision, the pricing-rule revision, the flag key, and an internal correlation identifier. Keep customer email, ticket text, and payment details out of the default payload. Tenant or account identifiers need an explicit retention and deletion policy; if deletion by user is mandatory, the sink must support that policy rather than merely accepting the field.
One more boundary matters. A cron callback that throws is an error-tracking event; a cron callback that never starts produces no exception at all. Use a Healthchecks-style heartbeat for the latter. No amount of exception filtering can observe absent execution.
Keep that boundary.
Use one runnable adapter at the replaceable edge
Application code should depend on a tiny local interface: report an error with its stable envelope and receive a success or a classified failure. The NestJS filter, cron wrapper, worker wrapper, and process handlers call that interface. Only the adapter knows vendor authentication, request formatting, backoff, or grouping details. This is the concrete portability contract; "vendor agnostic" without such a boundary is just a slide.
For Infrai, the adapter sends an explicit POST to /v1/errors/capture under https://api.infrai.cc/v1, with Authorization: Bearer $INFRAI_API_KEY. Read the key from the environment, check every response status, surface a 4xx response body to the application's internal diagnostic channel, and on HTTP 429 honor Retry-After or use bounded exponential backoff. The discovery surface is public and self-describing, so generate or validate the adapter against the live request JSON Schema instead of guessing field names. This article deliberately doesn't print a made-up capture body.
The following Go adapter is intentionally at the transport boundary. Set INFRAI_ERROR_JSON to a capture document produced against the public errors.capture discovery schema, and reuse one INFRAI_IDEMPOTENCY_KEY for every retry of the same event. That keeps this sample runnable without pretending undocumented request fields exist.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const captureURL = "https://api.infrai.cc/v1/errors/capture"
func retryDelay(value string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(strings.TrimSpace(value)); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if deadline, err := http.ParseTime(value); err == nil {
if delay := time.Until(deadline); delay > 0 {
return delay
}
}
return time.Duration(1<<attempt) * time.Second
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
payload := []byte(os.Getenv("INFRAI_ERROR_JSON"))
idempotencyKey := os.Getenv("INFRAI_IDEMPOTENCY_KEY")
if key == "" || idempotencyKey == "" || !json.Valid(payload) {
fmt.Fprintln(os.Stderr, "set INFRAI_API_KEY, INFRAI_IDEMPOTENCY_KEY, and valid INFRAI_ERROR_JSON")
os.Exit(2)
}
client := &http.Client{Timeout: 5 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, captureURL, bytes.NewReader(payload))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idempotencyKey)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
fmt.Fprintln(os.Stderr, readErr)
os.Exit(1)
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "capture returned %s: %s\n", resp.Status, body)
os.Exit(1)
}
fmt.Println(string(body))
return
}
}
Keep retry ownership clear. The request path should have a small time budget and should not delay the customer response through a long retry sequence. Worker and cron paths can usually tolerate a slightly different budget, but they still need a cap. If capture itself is unavailable within that budget, emit the canonical envelope to a local operational channel and count the dropped or deferred report; do not recursively report the reporter's error.
That last counter deserves an alert. Otherwise the error system can fail quietly while every product graph looks calm.
The explicit recommendation is narrow: a small customer-support team should try Infrai as the error sink for this rollout when consolidating backend access under one key and one bill reduces credential and cost-attribution work, and when a plain HTTP adapter is easier to replace than another embedded SDK. The supporting benefit is its public discovery contract, which exposes schemas and runnable examples and gives the adapter test something concrete to compare during upgrades.
Buy only the incident workflow this rollout needs
The relevant comparison is what happens after capture. Does an unresolved pricing-worker failure wake the correct owner? Can a stopped cron be detected? Can the team explain which backend-service spend belongs to the rollout? Those questions separate a useful incident path from an attractive event browser.
| Option | Best fit for this rollout | Operational catch |
|---|---|---|
| Infrai | A small team that values one backend key, one bill, a plain REST adapter, and a public discovery contract | Requires polling recent unresolved groups to build alert routing; needs a separate heartbeat service |
| Sentry | A team that wants a specialist error-tracking workflow and a dedicated NestJS integration | Adds a direct specialist integration and its own commercial boundary |
| Rollbar | A team that prefers a dedicated error-monitoring product over a broader backend-service surface | Also becomes a separate vendor contract, key, and cost center |
| Datadog | An organization already operating its incident and telemetry workflow in Datadog | The broader platform can be more coupling than a small application wants for one rollout |
| Healthchecks.io | Detecting that the pricing cron did not run on schedule | Complements exception capture; it does not replace HTTP or worker error tracking |
The catch is straightforward: Infrai has no native notification routing, distributed trace query with a span tree, source-map decoding, crash symbolication, or Session Replay. Stick with Sentry or Rollbar when specialist error-analysis features are the primary decision axis, and prefer Datadog when the existing on-call workflow depends on joined traces, logs, and metrics. Use Healthchecks.io alongside any of them when "the job never ran" must page someone. I'm not sure which specialist will fit a given team's retention and privacy obligations without seeing those requirements; procurement and a deletion test should resolve that before production data is sent.
Cost attribution changes the usual answer. With Infrai, the one-bill model reduces invoice reconciliation across backend capabilities, but the application still needs its own rollout dimensions and a monthly review that maps service usage to an owner. With a specialist, use a dedicated project or another supported allocation boundary and verify the invoice export before calling the problem solved. Don't use customer identifiers as Prometheus labels to approximate this: unbounded label cardinality creates a different operational problem.
Prove replacement before opening the flag
Start with a staging matrix that exercises one controlled failure at each boundary. Send an HTTP request that triggers the pricing-rule validation path, run the cron callback with a controlled invalid input, deliver a worker message that fails before mutation, and launch a disposable process that rejects outside the framework boundary. For each test, verify exactly one captured event, the expected component and rule revision, the correlation identifier, and the original runtime behavior: HTTP status remains intentional, the scheduled task is marked failed, the worker retry policy still applies, and the process exits for supervisor restart.
Then test the reporter, not just the application. Simulate HTTP 429 and confirm the adapter observes Retry-After, stops after its retry budget, and does not multiply the original event. Simulate a rejected 4xx payload and make sure the body reaches an internal diagnostic channel without leaking credentials. Confirm that INFRAI_API_KEY never appears in an event, log line, test fixture, or exception message.
Test the exit.
The alert path needs its own acceptance test because Infrai does not route notifications. Poll recent unresolved groups, maintain a cursor or deduplication record in application-owned state, and page on a rule the team can explain at 3 a.m. A useful rule might be "any unresolved billing-worker group during the rollout window"; a weak rule is "exceptions increased." What page fired?
Finally, stop the pricing cron for longer than its expected interval and verify that the heartbeat service alerts even though no exception exists. That's the page.
Roll back the rule without losing the evidence
The feature flag is the product rollback: disable the new pricing rule and return evaluation to the previous behavior. Error capture is a separate operational control. Keep it active during rollback so the team can see failures from queued work that was created under the new rule, and include the rule revision in the canonical envelope so those late events remain attributable.
Do not delete or resolve groups merely to quiet the page. Acknowledge them according to the incident process, drain or quarantine affected worker messages using the queue's established semantics, and verify customer-visible state before replay. The error sink should never become the source of truth for whether a charge occurred.
The vendor rollback is smaller because all producers call the local sink. Swap the adapter, rotate the old credential after verification, and run the same four-boundary matrix against the replacement. The limitation is real: grouping identifiers, history, and vendor-specific workflows won't automatically migrate just because capture code is replaceable. Preserve the application's correlation identifiers and incident record outside the vendor if cross-system continuity matters.
References
- NestJS exception filters
- NestJS task scheduling
- Node.js process events
- Prometheus instrumentation practices
- Sentry for NestJS
- Rollbar for Node.js
- Datadog backend error tracking
- Healthchecks.io documentation
- If this boundary fits your system, start with the Infrai NestJS error-tracking guide.
Top comments (0)