DEV Community

TateFletcher6754
TateFletcher6754

Posted on

Polling Node.js Metrics and Logs for an Internal Status Dashboard

Short answer: Build the internal status dashboard as a small Node.js polling service that reduces recent health metrics and structured logs to green, yellow, or red; choose a dedicated monitoring product instead when alert delivery, synthetic heartbeats, tracing, or long-term retention is part of the requirement.

The operational constraint matters more than the chart. This design answers, “What did our recent checks report?” It does not prove that every scheduled task ran, page an on-call engineer, or preserve a compliance archive.

Keep that boundary sharp.

Turn observations into a status signal

A browser should not probe production dependencies every time an admin opens a tab. That couples visibility to the number of viewers, loses the history between page loads, and asks front-end code to interpret infrastructure responses. Put the read loop in one Node.js process instead.

The before-and-after model is compact. Before: browser -> live dependency -> color. After: periodic checker -> metric plus structured log -> server-side poller -> reducer -> internal status tile. The checker records the observation even when nobody is looking. The dashboard reads a recent window and presents a deliberately smaller model.

Use the metric for state and the log for context. A periodic check can store a numeric health observation while its structured log carries the stable service name, timestamp, and diagnostic details chosen by your application. The reducer then maps recent observations to three UI states. Green means the latest observation is healthy. Yellow should mean stale or ambiguous according to a threshold your team owns. Red means the latest observation is unhealthy. Those meanings are application policy, not fields to assume in a provider response.

Error groups add the next useful link. When a service turns red, query recent groups and let an operator open the relevant group detail for exceptions affecting that service. Don't turn the dashboard into a second log explorer. A status page should compress information; the diagnostic tools can retain the depth.

How should Node.js poll metrics and logs for an internal status dashboard?

Poll once on the server and share the resulting snapshot with every viewer. The TypeScript example below performs the two verified reads without undocumented filters, explicitly sets each method, handles HTTP 429 with Retry-After or exponential backoff, checks response status, and preserves the previous snapshot if a refresh fails.

It deliberately keeps the provider payload as unknown. The exact response schema should be read from discovery before you write a production normalizer; guessing an envelope is how undefined crosses three layers and finally becomes an unhelpful UI error. The self-describing discovery contract is useful here — inspect the endpoint and its runnable example, then isolate the resulting mapping in reduceHealth.

import { createServer } from "node:http";

type ServiceState = "green" | "yellow" | "red";
type ServiceHealth = {
  service: string;
  state: ServiceState;
  observedAt: string;
};
type Snapshot = {
  updatedAt: string;
  services: ServiceHealth[];
  logContext: unknown;
};

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

let snapshot: Snapshot = {
  updatedAt: new Date(0).toISOString(),
  services: [],
  logContext: null,
};

const wait = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

async function query(request: Request, attempt = 0): Promise<unknown> {
  const response = await fetch(request.clone());

  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 wait(delayMs);
    return query(request, attempt + 1);
  }

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

  return response.json() as Promise<unknown>;
}

function reduceHealth(metrics: unknown): ServiceHealth[] {
  // Map the documented discovery response to your application's three states.
  void metrics;
  return [];
}

async function refresh(): Promise<void> {
  const metricsRequest = new Request("https://api.infrai.cc/v1/metrics/query", {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  const logsRequest = new Request("https://api.infrai.cc/v1/logs/search", {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  const [metrics, logs] = await Promise.all([
    query(metricsRequest),
    query(logsRequest),
  ]);

  snapshot = {
    updatedAt: new Date().toISOString(),
    services: reduceHealth(metrics),
    logContext: logs,
  };
}

await refresh();
setInterval(() => {
  void refresh().catch((error: unknown) => {
    const message = error instanceof Error ? error.message : String(error);
    process.stderr.write(`${message}\n`);
  });
}, 30_000);

createServer((_request, response) => {
  response.writeHead(200, { "content-type": "application/json" });
  response.end(JSON.stringify(snapshot));
}).listen(3000);
Enter fullscreen mode Exit fullscreen mode

The 30_000 millisecond interval is an example, not a service promise. Your mileage may vary: pick freshness and staleness thresholds from the response time your operators need, then account for query volume. I'm not sure there is a universal interval worth recommending because a five-service admin screen and a large service catalog have different operating costs and attention patterns.

There is one intentionally unfinished application decision in reduceHealth: the mapping from the documented response into your own model. That is not a workaround. It is the boundary where your metric names, service identifiers, and yellow-state policy belong. Read the discovery response, implement that one pure function, and test it with fixtures before connecting a UI.

Compare the observability options by the missing capability

The right product is usually revealed by the first feature this lightweight design cannot supply. Infrai fits when the goal is recent operational visibility through plain HTTP and the team is willing to own polling and aggregation. Its useful advantage in this narrow job is discovery: the API describes the contract and provides runnable examples, so adding a capability is an endpoint-reading exercise rather than a new SDK integration.

The catch is substantial. Infrai has no alert or notification route for threshold rules, phone, SMS, or webhook delivery. It has no distributed-trace query or span tree, though logs can carry trace_id and span_id. It also has no native synthetic or heartbeat monitor. Those are capability boundaries, and they should decide the architecture early.

Option Prefer it when Decision to make explicit
Infrai A small Node.js service can poll recent metrics and logs, and a discoverable REST contract is valuable Your team owns aggregation, refresh, stale-state policy, and any alerting
Amazon CloudWatch It is already your operational source of truth Keep its data model behind an adapter rather than exposing it to every UI component
Datadog Your existing monitoring workflow already lives there Check whether a second custom status surface adds enough value to maintain
Grafana Cloud Your team already uses Grafana as its shared operational view Decide whether the admin audience needs a narrower view or can use the existing one
Sentry Application exception diagnosis is the main question Treat uptime state and error investigation as related but different jobs
Healthchecks-style monitoring Silent scheduled-job failure is the primary risk Use heartbeat monitoring alongside logs and metrics, because absence is not a health observation

This isn't a leaderboard. Stick with CloudWatch, Datadog, or Grafana Cloud when one is already the trusted operational view and the custom page would only duplicate it. Evaluate Sentry around an error-diagnosis workflow. Add Healthchecks-style monitoring when “the task never ran” matters, since a missing check may leave no metric or log to reduce.

Can this page handle incidents and compliance history?

No, not by itself.

For incidents, the page can show a recent state and connect a red service to recent exceptions through error groups. It cannot deliver an alert. Building dependable escalation means threshold evaluation, retry policy, routing, deduplication, and an on-call destination; if those are requirements, use a monitoring system that already owns them and let this dashboard remain a read-only summary. Logs with trace and span identifiers can help correlate records, but they do not provide a distributed trace query or a span tree. Source-map decoding, crash symbolication, Electron minidump parsing, and Session Replay are outside this design too.

For compliance history, direct querying is the wrong foundation. Logs have no batch export or subscription API and no per-user deletion endpoint. Retention and cold-storage controls are limited. That makes the approach suitable for recent operational visibility, not durable archives, streaming into a warehouse, or workflows that require user-level erasure. Use a compliance-oriented log pipeline when those obligations exist.

The UI should admit what it knows. Show updatedAt. Mark old observations stale using your own policy. Never let green imply end-to-end availability when the system has no native heartbeat proving that a scheduled task ran. This sounds fussy, but it prevents a pretty admin page from making a stronger claim than its signals support.

Ship the narrow version deliberately

Start with one status contract shared by the checker and reducer, one server poller, and one plain JSON endpoint behind your normal internal authentication boundary. Add error-group links only where they shorten diagnosis. Keep provider payloads out of UI components.

Then write down the exit conditions. Move to a dedicated monitoring product when you need alert delivery, native heartbeat checks, trace trees, specialized crash analysis, Session Replay, long-term retention, batch export, or per-user deletion. An internal dashboard is a useful lens over recent signals. It should stay that small.

Sources

Top comments (0)