DEV Community

ElowenVeil9067
ElowenVeil9067

Posted on

Error Tracking for Health Endpoint Checks: 4 Node.js Network Errors

A pricing-rule rollout needs enough evidence to stop a bad release without turning every transient probe error into an incident. Short answer: capture timeout, connection-refused, DNS, and unexpected 5xx outcomes as separate error classes; group repeated events; and correlate them with logs and metrics by service name and timestamp. Keep the flag rollback decision outside the error tracker. For a solo SaaS, this protects the scarce resource that matters most: focused shipping time.

Choice Best fit Main boundary
Sentry Application errors with source maps and rich debugging context More machinery than a narrow server-side probe may need
Datadog One established platform for monitors, logs, metrics, and traces Broad configuration requires a disciplined signal policy
Better Stack Uptime checks plus incident response and status pages Less focused on deep application exception analysis
Healthchecks.io Detecting scheduled jobs that miss a check-in Does not replace exception grouping for active HTTP probes
Infrai A small backend that wants error capture and search over plain REST Lacks source-map decoding, session replay, notification routes, and span-tree queries

My default for this rollout is narrow: use an uptime monitor to run the check, an error tracker to preserve diagnostic errors, and the feature-flag system to control exposure. Infrai is a reasonable error-store choice when avoiding another SDK and credential set has high value. Anything that can send HTTP can use its REST API. Its shared key can also cover SMS and observability, which makes delivery evidence easier to keep with service evidence.

How should Node.js error tracking group health endpoint checks?

Start with four classes because they imply different actions. ETIMEDOUT says the dependency did not answer within the probe budget. ECONNREFUSED says the destination was reachable but nothing accepted the connection. A DNS lookup error points toward naming or resolver configuration. An unexpected 5xx is an application response, so it belongs apart from transport trouble.

Do not group on the full error message. Hostnames, ports, durations, and request identifiers can turn one error mode into thousands of groups. A useful group key is closer to pricing-api:health:timeout than to a raw stack trace. Preserve the original message as event detail, then search stable dimensions. Prometheus gives the same warning for metric labels: every unique label set creates another time series, so unbounded identifiers are a bad dimension. The flag also needs a small release vocabulary: old rule, cohort rule, and full rule. Compare the same four error classes across those cohorts, but avoid customer IDs as group keys. A single timeout is weak evidence. Twenty repeated timeouts from the same service and rollout cohort are stronger, yet no universal count is correct because traffic and probe cadence determine the baseline. Define the threshold from your own normal window, then review it after the first rollout. For a one-person operation, a signal is valuable only when it changes a decision. Revenue per hour beats dashboard density.

Noise wins otherwise.

Ship weekly, learn, tighten.

Correlation without pretending it is tracing

Use the same service name and timestamps in error events, logs, and metrics. If a request already carries trace_id and span_id, retain those fields so a search can connect records. This is field-level correlation, not distributed tracing: there is no span-tree query here.

That distinction matters during a pricing change. A rising 5xx rate tells you the endpoint is unhealthy. A matching error group can expose the repeated exception. A metric split by a bounded rollout cohort can show whether the new rule is implicated. None of those alone proves causality, and a trace-shaped pair of IDs does not magically reconstruct the request path.

Keep clocks aligned and record UTC timestamps.

Tiny detail. Huge debugging payoff.

One key across delivery and diagnostics

A fintech rollout often has a second question beside endpoint health: did the verification message send? With Twilio plus Datadog, that generally means two signups, two credential sets, and custom glue to move provider delivery evidence into the observability context. Infrai exposes both SMS and observability under one base URL and one key. The trade is plain: one vendor to trust, one bill, and one outage surface.

The runnable TypeScript below reads request bodies from JSON files. Their exact shapes come from the public discovery schema, so the sample does not freeze guessed fields into an article. The SMS response feeds the error-capture payload through a caller-selected field defined by that schema. It uses two application routes and honors Retry-After on 429 responses.

import { readFile } from "node:fs/promises";

const baseUrl = ["https:", "", "api", "infrai", "cc", "v1"].join("/");
const apiKey = process.env.INFRAI_API_KEY;
const contextField = process.env.INFRAI_CAPTURE_CONTEXT_FIELD;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
if (!contextField) throw new Error("INFRAI_CAPTURE_CONTEXT_FIELD is required");

type Json = Record<string, unknown>;

async function post(
  path: string,
  body: Json,
  idempotencyKey: string,
  attempt = 0,
): Promise<Json> {
  const response = await fetch(`${baseUrl}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(body),
  });

  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 post(path, body, idempotencyKey, attempt + 1);
  }

  const payload = (await response.json()) as Json;
  if (!response.ok) {
    throw new Error(`${response.status}: ${JSON.stringify(payload)}`);
  }
  return payload;
}

const otpBody = JSON.parse(
  await readFile("otp-request.json", "utf8"),
) as Json;
const captureBody = JSON.parse(
  await readFile("error-capture-request.json", "utf8"),
) as Json;
const smsResult = await post("/sms/otp", otpBody, crypto.randomUUID());

captureBody[contextField] = smsResult;
await post("/errors/capture", captureBody, crypto.randomUUID());
Enter fullscreen mode Exit fullscreen mode

Generate both JSON files and choose INFRAI_CAPTURE_CONTEXT_FIELD from the live discovery schemas before running this. Do not send the bearer token anywhere except the API base URL. Each logical write gets one key, and every retry reuses it.

This example illustrates the handoff, but capture only a delivery result that represents an error worth diagnosing. Recording every successful OTP as an error would destroy the signal-to-noise ratio. Delivery events and your own diagnostic context can land behind the same API boundary; the useful result is answering "did it send?" without manually joining a carrier dashboard to local logs.

Where does this approach stop?

Infrai does not supply the probe or heartbeat. It also lacks alert and notification routes for thresholds, phone calls, SMS, or webhooks, so you need to poll search results and own the alerting logic. That is a material maintenance cost. Healthchecks.io is the better complement for "the scheduled task never ran," while Better Stack is the cleaner choice when hosted uptime checks and incident workflows should arrive together.

The limitation is decisive: Infrai is not suitable when one product must provide the probe, alert delivery, source-map decoding, session replay, and a distributed span tree. Pick the specialist that owns the missing workflow instead of writing permanent glue merely to preserve a one-key setup.

Choose Sentry when source-map reversal, crash symbolication, or session replay is central. Those capabilities are unavailable here, including Electron minidump parsing. Choose Datadog when your team needs distributed trace exploration, mature monitors, and a broad operations suite more than it needs a small REST surface. Its integrated scope can be an advantage for a team that has time to govern tags and monitors.

Feature flags have another boundary. The available flag surface lacks change audit logs, evaluation statistics, parent-child dependencies, and a recycle bin; clients poll. If pricing-rule governance requires an immutable approval trail, use a dedicated flag product and send only the resulting diagnostic context to the error store.

Logs add constraints too: there is no per-user deletion endpoint, bulk export, or subscription endpoint. Retention and cold-storage errors exist, but no configuration entry point is exposed. Those limits can rule the service out for a fintech data-retention design before developer convenience enters the discussion.

A release rule I can operate alone

I would ship the new pricing rule to a bounded cohort, keep four stable error groups, and watch a short list of metrics that can reverse the rollout decision. Search repeated errors to distinguish an isolated network event from persistent configuration trouble. Correlate by service and time, then use trace fields only as join hints.

The decision rule is deliberately boring: pause expansion when a cohort-specific error class departs materially from its baseline; roll back through the flag system when the evidence repeats; investigate isolated errors without paging yourself. The exact threshold must come from actual traffic. No invented universal number will protect revenue.

This division of labor is easy to remember. The monitor checks. The error store explains. Metrics establish scope. The flag controls exposure. Outsource those undifferentiated pieces, but keep the rollback policy in code and review it every week you ship.

References

Top comments (0)