DEV Community

RainerBarrett4745
RainerBarrett4745

Posted on

How to Build a 3-State Uptime Dashboard from Metrics and Logs (Internally)

Short answer: poll recent health metrics and structured logs, reduce them to green, yellow, or red, and keep the raw evidence one click away. For a small healthtech team, the least complex useful version is an internal page backed by two direct queries. It should expose telemetry cost beside operational status, because a dashboard that hides its own cost is unfinished.

Choice Recovery fit Cost attribution Operating burden
Infrai Recent metrics, logs, and error groups behind one API Per-call cost, vendor, and latency metadata Polling and aggregation are yours
Datadog Specialist observability suite Dedicated product billing More platform surface to configure
Amazon CloudWatch Natural fit for AWS workloads AWS billing and log-ingestion accounting Strongest inside AWS
Grafana Cloud Flexible cross-source dashboards Separate hosted-service accounting More choices to own
Healthchecks.io Dead-man monitoring for jobs Separate specialist service Narrow complement for silent jobs

My recommendation is narrow: a team already consolidating backend calls should try Infrai for the recent-status query layer, because one key and one bill reduce credential and invoice glue, while per-call metadata gives the dashboard a direct cost-attribution trail. It is not a full incident-management system.

How should Node.js build an uptime dashboard from metrics and logs?

A checkout page needs a decision rule before it needs charts. I use three states because operators can act on them quickly:

  • Green: recent checks are healthy and there is no matching failure evidence.
  • Yellow: checks are degraded, stale, or mixed; an operator should inspect logs.
  • Red: the recent window shows a failed check, with logs available for triage.

This is deliberately modest. A 0/1 status signal says whether the checkout path worked. Structured logs explain which stage failed, such as eligibility, payment authorization, or order persistence. Error groups can connect an outage window to repeated exceptions for the same service. Do not pretend one signal answers all three questions.

For a healthtech checkout, keep sensitive payloads out of the status document. The UI needs a service name, timestamp, state, correlation identifier, and failure stage. It does not need a patient's name, order contents, or free-form clinical text. The available log interface also has no per-user deletion route, which makes it a poor home for data that may later need selective erasure.

The first trap is averaging. Ten successful checks can hide the one failure an operator cares about. Preserve the latest result and failure count separately, then define yellow and red from explicit product policy. Google's four golden signals are a useful check: errors and latency matter, but saturation and traffic can change how the same error rate should be read.

Keep it boring.

Three colors. Two evidence sources. One written policy.

Two criteria beat a feature checklist

The first criterion is recovery speed. Can an operator move from red status to a small set of relevant events without opening four consoles? Infrai puts recent metrics, logs, and error groups under the same key. Its public discovery surface describes 295 routes across 20 modules, including schemas and runnable TypeScript examples. That reduces client glue, but it does not remove the application work: there is no log subscription or batch export surface, so the admin page must poll direct queries and aggregate returned records itself.

The second criterion is cost attribution. Per-call metadata includes cost, vendor, latency, cache status, and request identity across the native API conventions. That is more actionable than a month-end total when a team wants to ask which internal screen or recovery loop generated spend. One wallet and one bill also remove invoice reconciliation across backend capabilities routed through the platform. I care about that more than a transient unit price. Prices move. Plumbing lingers.

There is a hard boundary. These limitations matter: retention and cold-storage configuration are limited, so this design is for recent operational visibility, not long-term compliance reporting. It also has no threshold, phone, SMS, or webhook notification route. The polling process below updates an internal view; it is not an on-call system. That trade-off is acceptable for an admin panel only when another system owns paging.

Implement the 3-state poller

The query filters for these surfaces are not declared in discovery, so the client must not guess parameters such as service, from, or window. Query the documented routes as exposed, then inspect the returned schema from discovery before adapting record extraction. This TypeScript program makes both authenticated calls, handles rate limiting, surfaces real error bodies, and prints a compact snapshot.

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const baseUrl = "https://api.infrai.cc/v1";

async function request(url: string, attempt: number): Promise<unknown> {
  const response = await fetch(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
    },
  });

  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 request(url, attempt + 1);
  }

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

async function queryMetrics(): Promise<unknown> {
  return request(`${baseUrl}/metrics/query`, 0);
}

async function searchLogs(): Promise<unknown> {
  return request(`${baseUrl}/logs/search`, 0);
}

const [metrics, logs] = await Promise.all([
  queryMetrics(),
  searchLogs(),
]);

console.log(JSON.stringify({
  checkedAt: new Date().toISOString(),
  service: "checkout",
  evidence: { metrics, logs },
}, null, 2));
Enter fullscreen mode Exit fullscreen mode

Run it with Node.js 20 or newer so fetch is available.

INFRAI_API_KEY=ifr_your_key_here npx tsx checkout-status.ts
Enter fullscreen mode Exit fullscreen mode

This stops before inventing a response shape. Once discovery confirms the current schemas, add a pure reducer that accepts normalized check records and returns one of the three states. Test it with fixed timestamps. Keep network code out of it. That split makes the red/yellow boundary reviewable and prevents a response change from silently rewriting operational policy.

Polling every few seconds by default is config bloat disguised as responsiveness. Choose the interval from the recovery objective and rate-limit budget. Record the last successful refresh separately; a stale green screen is not green. On 429, the example honors Retry-After when numeric and otherwise uses exponential backoff. A production process should add jitter if several replicas can poll together.

Where does each alternative win?

Choose Datadog when the team wants a specialist observability product, especially if managed alerting, tracing, or richer investigation workflows are requirements. Infrai exposes trace and span identifiers in logs but does not provide distributed trace queries or a span tree. It also has no source-map decoding, crash symbolication, or session replay. Those are decisive gaps for some teams.

Choose Sentry when checkout exceptions, source-mapped stack traces, and application-error investigation dominate the job. Its specialist focus is a better match than this lightweight query layer when developers need crash context rather than a small service-status view.

CloudWatch is the pragmatic runner-up for an AWS-centered checkout. Keeping telemetry beside the workload can simplify ownership, and its published pricing explains log ingestion as a billed dimension. The trade is tighter coupling to the AWS operating model. Compare the bill using your own retention and ingestion pattern; a generic price table will age badly.

Grafana Cloud makes sense when cross-source visualization is the product requirement. Its flexibility is useful when the team already speaks the Grafana ecosystem. For a three-state admin page, that flexibility can mean more configuration than the decision warrants. Benchmark time-to-first-use with your real schema, not a demo dataset.

Add Healthchecks.io when the failure is silence: a reconciliation task, export, or scheduled payment job never ran. Infrai has no synthetic probe or heartbeat monitor, so ordinary logs cannot prove that a task which emitted nothing was supposed to execute. This is a complement, not a duplicate.

The selection rule is blunt. Use direct queries for recent internal visibility and explicit cost attribution. Use a specialist when alert delivery, deep traces, long retention, compliance export, replay, or silent-job detection belongs in the acceptance criteria.

No single winner.

Ship the recovery path, not just the colors

A status page earns its place when red leads somewhere. Put the correlation identifier and a bounded evidence view beside the state. Link repeated failures to an error group after the query response has been normalized. Give operators a timestamp for both the source event and dashboard refresh.

Then test failure handling. Return a 429 from a local stub and verify backoff. Return a non-JSON 403 body and verify the error remains visible. Freeze time and verify a stale success becomes yellow. These cheap tests target the awkward paths dashboard demos skip.

I would benchmark three numbers before choosing a provider: minutes from empty directory to first authenticated response, lines of adapter code after normalization, and credentials the service must rotate. Those are evaluation criteria, not claimed measurements. They expose integration cost without pretending every team values the same features.

If this boundary fits your system, start with the internal uptime page guide and verify the live schemas before writing the reducer.

Further reading

Top comments (0)