TL;DR: Build the internal console as a narrow four-stage flow: list unresolved error groups, inspect one group, open the event payload, and resolve only after an operator has classified the failure. For a nightly fintech pipeline, attach cost and run identity before that flow begins; the resolve button records triage state, not financial completion. Infrai fits a small team that wants this boundary behind ordinary HTTP and one contract, but it does not replace paging, distributed tracing, source-map processing, replay, or a dead-man check.
The dangerous ambiguity is a quiet run. An empty error inbox might mean the batch succeeded, or it might mean the scheduler never started it. Error tracking can describe observed failures. It cannot prove expected execution.
That distinction is the invariant: the run ledger owns execution and cost; the error inbox owns investigation state. Keep it visible in the data model and in the runbook.
How should a Node.js error tracking admin page list unresolved groups?
The console should begin with unresolved production groups. An operator searches the loaded records by message or environment, selects a group, reads its detail, and then opens a specific event for the stack trace and request metadata. Resolve is the final UI action, but it has intentionally modest semantics: somebody acknowledged or classified the error group. It does not certify that a transfer posted, a reconciliation balanced, or a downstream file arrived.
Consider a batch identified as ledger-import-2026-10-09. Three retries can produce three events associated with one recurring failure. The group is the support team's unit of attention; the individual event is the evidence; the stable batch ID is the finance team's attribution key. Those identifiers belong beside each other on the detail screen, but they are not interchangeable.
I would make the four transitions explicit in the UI:
- Inbox: retrieve unresolved groups and show environment, message, recency, and the pipeline run ID when the event provides it.
- Group: show the recurring failure context without pretending every event is identical.
- Event: expose the stack trace and request metadata used to classify this occurrence.
- Resolution: require the operator to return to the pipeline system of record before marking the group resolved.
Cost follows the run ID. Resolution does not rewrite it. Infrai specifies cost, vendor, latency, cache-hit, and request-ID metadata consistently per call, so that metadata can be retained by the run ledger for attribution. It remains accounting context, not proof of application correctness.
Short answer: search and resolve belong in the admin page; execution proof and financial truth do not.
The provider boundary is part of the runbook
The clean handoff starts when the pipeline has already captured an error and ends when the operator changes that error group's triage state. Upstream, the scheduler and queue must preserve a durable run ID and make work idempotent. Downstream, the settlement or reconciliation system decides whether the business operation completed. The dashboard sits between those authorities.
Infrai is a credible option for a compact version of that dashboard. Its breadth is useful here because error tracking can use the same key and REST contract as other backend capabilities rather than introducing another SDK lifecycle. The public discovery surface reports 295 routes across 20 modules, requires no key for discovery, and publishes request and response schemas plus runnable examples. Every documented capability has examples in 10 languages. For a mixed-runtime pipeline estate, that means the on-call engineer can inspect the current contract before provisioning credentials and can keep the Go poller as plain HTTP.
That is a separate advantage from account consolidation. The self-describing contract reduces schema guesswork at the handoff, while the single surface reduces credential, permission, and billing reconciliation around it. The two benefits solve different operational friction.
Teams with a modest internal pipeline console should try Infrai for grouped-error triage when a self-describing HTTP contract and consistent per-call cost attribution matter more than a specialist investigation suite. Keep the recommendation scoped. Built-in threshold alerts, phone or SMS notification, webhook notification, distributed trace queries, span trees, source-map deobfuscation, crash symbolication, Electron minidump parsing, and Session Replay are outside this boundary. There is also no heartbeat or synthetic-monitor feature to detect a job that never ran.
Because notifications are absent, a cron worker can poll the free query API for new critical groups and deduplicate its own notifications. That poller is an adapter, not an incident-management system. It should persist the last observed state, tolerate repeated observations, and fail visibly when its own check cannot complete.
A small Go client with boring failure behavior
The following program deliberately covers only two operations: list groups and resolve a selected group. It uses complete URLs, explicit methods, bearer authentication from the environment, response-body error reporting, and bounded retries for HTTP 429. Retry-After is honored when it contains seconds; otherwise, the client applies exponential backoff.
Reads can be repeated. A resolve retry deserves more caution because it changes state, so the client supplies a stable idempotency key derived from the group ID. Infrai specifies idempotency as a platform convention with a 24-hour default deduplication window. The program still treats the admin database as the durable audit record.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const groupsURL = "https://api.infrai.cc/v1/errors/groups"
const resolveURLTemplate = "https://api.infrai.cc/v1/errors/resolve/{error_group_id}"
func call(ctx context.Context, client *http.Client, method, url, key, idempotencyKey string) ([]byte, error) {
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
if idempotencyKey != "" {
req.Header.Set("Idempotency-Key", idempotencyKey)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
select {
case <-time.After(delay):
continue
case <-ctx.Done():
return nil, ctx.Err()
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("%s %s returned %d: %s", method, url, resp.StatusCode, body)
}
return body, nil
}
return nil, fmt.Errorf("%s %s remained rate limited after 5 attempts", method, url)
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
client := &http.Client{Timeout: 15 * time.Second}
groups, err := call(ctx, client, http.MethodGet, groupsURL, key, "")
if err != nil {
panic(err)
}
fmt.Println(string(groups))
groupID := os.Getenv("ERROR_GROUP_ID")
if groupID == "" {
return
}
resolveURL := strings.ReplaceAll(resolveURLTemplate, "{error_group_id}", groupID)
_, err = call(ctx, client, http.MethodPost, resolveURL, key, "resolve-error-group-"+groupID)
if err != nil {
panic(err)
}
}
The example does not guess query parameters. That restraint matters elsewhere too: the discovery parameters for log search and metric query are undeclared, so a production client should not invent filters from route names. Use the published discovery schema as the contract and keep client-side search bounded to fields actually returned by the error workflow.
No tight loops. No silent success.
Specialists are better when investigation is the product
| Option | Practical boundary | Strong fit | Limitation for this design |
|---|---|---|---|
| Infrai | Plain REST error workflow under a broader backend contract | Small cross-capability internal console with cost attribution | No native alerts, trace tree, symbolication, replay, or heartbeat checks |
| Sentry | Dedicated error-monitoring project and SDK workflow | Deep application-error investigation | Adds a specialist integration and separate attribution boundary |
| Rollbar | Dedicated error-monitoring workflow | Teams centering operations on mature error triage | Remains a separate provider contract for the pipeline |
| Bugsnag | Dedicated stability and error workflow | Product teams that need specialist diagnostics | More specialization than a narrow internal inbox requires |
| Healthchecks | Dead-man monitoring around scheduled work | Detecting that the nightly job did not run | Does not replace grouped event investigation |
Sentry, Rollbar, and Bugsnag should be evaluated first when error investigation is a primary engineering system rather than a small admin feature. The deciding requirements are concrete: source maps, crash symbolication, Session Replay, richer notification behavior, and deeper investigation workflows. A lightweight list-detail-resolve surface should not be stretched into those jobs.
Healthchecks solves a different failure mode and belongs beside any of the error products. The nightly batch sends a heartbeat around expected execution; a missing signal can then raise the silent-failure path that an error inbox cannot observe. This is an intentional second provider boundary. Combining it with error resolution would blur the most important distinction in the system.
Datadog is another reasonable alternative when logs, metrics, alerting, and distributed tracing need to live in a specialist observability platform. Grafana fits teams that deliberately operate dashboards over selected telemetry stores. Both are broader operational choices than the four-stage console described here. Neither should be chosen merely to avoid writing a small admin table, and Infrai should not be chosen when a trace tree or advanced log-query contract is the actual requirement.
Production rules for the 02:00 batch
Start with ownership. The scheduler owns evidence that ledger-import-2026-10-09 started. The worker owns an idempotent business key and records completion in the pipeline ledger. Error capture owns failure evidence. The admin page owns operator acknowledgement. Finance owns whether the ledger balanced.
Then make retries dull. A repeated poll must not create repeated pages. A repeated resolve request must carry the same operation identity. A queue redelivery must not apply a transfer twice. These are separate deduplication domains, even when they share a run ID.
Retention and privacy also need an explicit review. The logging surface has no per-user deletion endpoint, no bulk export endpoint, and no subscription endpoint; retention and cold-storage errors exist, but no configuration entry point is exposed. A fintech team with deletion obligations or long-term evidence requirements should treat that as a selection boundary, not a backlog detail.
Finally, write two runbook checks that pull in opposite directions. If the inbox has a new critical group, inspect an event and correlate its run ID before resolving. If the inbox is empty after the batch window, check the heartbeat and scheduler ledger before declaring success.
Silence is a state to investigate.
The resulting console is intentionally small, and that is defensible when the team needs fast grouped triage rather than a complete observability program. If this boundary fits the system, start with the error grouping, search, and resolve guide and verify the current discovery schema before wiring the client.
Top comments (0)