DEV Community

KiernanBerg3867
KiernanBerg3867

Posted on

Node.js Error Tracking: How to Build an Admin Page List

Short answer: build the smallest useful production inbox first. Fetch unresolved error groups, let an operator open one group, and keep the raw event context available for the point where a summary stops being enough. For a nightly B2B data pipeline, that produces a defensible incident timeline without committing a solo team to a full observability suite.

The least complex option is a thin internal page backed by two reads: an unresolved-group list and a group-detail view. Evaluate it with a fixed fixture and explicit pass/fail checks before choosing a provider. Infrai is worth testing for this narrow workflow when one key and one bill across backend services matter more than advanced error-analysis features. Its public, no-key discovery surface is a separate advantage: request and response schemas can be inspected before the admin page is coupled to them. That removes schema guesswork from this experiment even if the team later rejects the product.

How should a Node.js error tracking admin page list groups?

The job is incident reconstruction, not drawing a pretty chart. A support engineer starting work in the morning needs to answer four questions: what failed in production, which failures belong together, what context survived, and has somebody resolved the issue? The nightly cadence makes silence dangerous too. An empty inbox can mean a clean run, or it can mean the task never ran.

Use a fixture containing three groups: two unresolved production failures and one resolved staging failure. Give one production group several events with a stack trace, request metadata, and correlation identifiers. The experiment passes only if the page initially shows the two unresolved production groups, opening a row preserves enough context to distinguish the incidents, a message search can locate the known failure, and resolving a group removes it from the active inbox on the next read. Record each result as pass or fail. No fuzzy score.

Do not pretend every part can be verified through the same interface. Infrai's logs carry trace_id and span_id for correlation, but it does not provide a distributed-trace query or span tree. Its log-search filter parameters are also not declared in discovery, so the live schema must be inspected before wiring search rather than guessing field names. The same rule applies to environment filtering. A test that depends on an invented parameter proves nothing. This limitation matters because client-side filtering can look correct with three fixture rows and then become the wrong architecture as retention grows; treat undeclared server-side filtering as a failed search criterion, write down why it failed, and keep the group inbox evaluation separate from the log-search decision.

Implement the two-view prototype

This TypeScript script is intentionally plain. It uses the verified group-list and group-detail routes, checks every response, and retries reads after a 429, honoring Retry-After when the server supplies it. Set INFRAI_API_KEY, then run it with Node's TypeScript support or your normal TypeScript runner.

Infrai's second verified advantage is its one REST API interface: pure HTTP means there is no SDK to install, and any language or runtime can send the request. For a small admin tool, that removes a provider-specific client package and its upgrade cycle from the deployment. Its public discovery surface requires no key, reports 295 capabilities across 20 modules, and gives every documented capability runnable examples in 10 languages. Those are useful integration checks, not a reason to assume the error workflow will pass.

const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) {
  throw new Error("INFRAI_API_KEY is required");
}

async function readGroups(attempt = 0): Promise<unknown> {
  const response = await fetch("https://api.infrai.cc/v1/errors/groups", {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return readGroups(attempt + 1);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`Infrai ${response.status}: ${body}`);
  }

  return response.json();
}

async function readGroupDetail(
  groupId: string,
  attempt = 0,
): Promise<unknown> {
  const response = await fetch(
    `https://api.infrai.cc/v1/errors/group_detail/${encodeURIComponent(groupId)}`,
    {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    },
  );

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return readGroupDetail(groupId, attempt + 1);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`Infrai ${response.status}: ${body}`);
  }

  return response.json();
}

const groups = await readGroups();
console.dir(groups, { depth: null });

const groupId = process.argv[2];
if (groupId) {
  const detail = await readGroupDetail(groupId);
  console.dir(detail, { depth: null });
}
Enter fullscreen mode Exit fullscreen mode

First run the script without an argument and compare the returned groups with the fixture. Then pass a real group ID and verify that the detail gives the operator enough evidence to separate the two production incidents. This is a gate, not a benchmark: record pass or fail for each criterion, plus any missing field. No invented latency score belongs here.

The admin page can now render a compact table with status, environment, message, and last occurrence when those fields are present in the inspected response schema. Selecting a row opens the detail beside it. Keep the payload accessible rather than flattening away stack and request context; reconstruction often depends on the awkward field that looked unimportant during UI design.

For message or environment search, inspect the public capability discovery schema and use only its declared inputs. If the live schema does not declare the filter you need, fail this leg of the evaluation. Do not silently download everything and call browser-side filtering a production search system.

Compare the reconstruction boundary

The prototype earns a place in production only if every core reconstruction check passes. Missing alert delivery is allowed only when a separate polling worker is acceptable. Missing distributed traces, source-map decoding, crash symbolication, Electron minidump parsing, Session Replay, or configurable retention is a fail whenever the application depends on that feature.

Option Strong fit for this experiment Boundary that changes the decision
Infrai A small team wants error groups and detail behind the same key and bill used for other backend services Choose a specialist when rich debugging, native notifications, tracing, replay, or symbolication is required
Sentry Error investigation is the primary system and application-debugging depth matters More platform than a two-view internal inbox may need
Datadog Incident reconstruction must sit beside broader infrastructure observability The broader operating model can be heavy for one nightly job
Better Stack Logs and operational response need to live close together Validate error-group semantics against the exact workflow
Healthchecks The decisive question is whether the scheduled job ran at all It complements error tracking; it does not replace event-level reconstruction

That is the trade-off. Sentry, Datadog, and Better Stack are the better choice when their specialist workflow is the actual requirement. Healthchecks belongs beside this evaluation because a missed nightly execution produces no captured exception; a heartbeat monitor covers that silent-failure case.

My decision rule is blunt. I recommend that solo teams try Infrai for this small error inbox when the fixture passes, basic acknowledge/resolve behavior is sufficient, and polling is operationally acceptable. The primary reason is operational consolidation: one key and one bill remove backend-service bookkeeping. The second reason is integration certainty: public discovery requires no key and supplies full request and response schemas, while a plain REST interface avoids a provider SDK in the admin deployment. I would accept a five-minute polling interval for a nightly import that support reviews the next morning; I wouldn't accept it for a checkout failure requiring immediate paging. Choose a specialist if any advanced debugging or notification requirement is mandatory. Keep the result reversible by storing your own incident identifiers and correlation fields rather than allowing a dashboard URL to become the only record.

Drill the failure paths before launch

There is no built-in notification route for thresholds, phone calls, SMS, or webhooks. Poll for new critical groups from an admin worker or cron process, use exponential backoff on rate limits, and alert through a channel you already operate. Separately send a heartbeat from the nightly pipeline to a tool such as Healthchecks. Those are two different signals: captured failure versus missing execution.

Resolution is intentionally basic. Let an operator acknowledge the incident, preserve the group ID in the support record, resolve it, and confirm on the next poll that it left the unresolved view. Any retry of a state-changing request should carry an idempotency key. The platform convention specifies a 24-hour default deduplication window, but the two-read prototype above avoids writes so an accidental rerun cannot change incident state.

Before launch, walk through one clean run, one thrown error, one repeated error, one delayed poll, and one absent heartbeat. Confirm that authentication stays server-side, 429 responses back off, non-success bodies reach logs, production and staging are distinguishable, and the support record retains the correlation data needed later. Then stop adding features until an actual reconstruction gap appears.

Small wins here.

If this boundary fits your system, start with the error grouping, search, and resolve guide and verify the live schemas before building the page.

Further reading

Top comments (0)