DEV Community

Rivenor85
Rivenor85

Posted on

Nightly Pipeline Uptime with Metrics and Logs (An Internal Admin ADR)

A Node.js team should build its internal uptime dashboard from fresh metrics and structured logs because a nightly game-data pipeline can fail silently between runs; an empty chart is not service status.

Short answer: build a lightweight Node.js internal admin page that polls recent metrics and structured logs, derives green, yellow, or red service states locally, and links a failed run to its error group; choose a different system when you need pushed alerts, distributed traces, or long-term compliance retention.

This is an architecture decision, not a charting exercise. The primary objective is high signal quality with bounded telemetry volume. Every status check becomes bytes stored, every free-form label expands cardinality, and every refresh creates query work. The design should therefore preserve just enough evidence to answer two questions: did last night's job run, and, if it failed, what exception family explains the failure?

What should a Node.js internal admin uptime dashboard build from metrics and logs?

The page should expose one row per pipeline service or stage, not one row per process, pod, player, or request. Each row carries the latest known state, the observation timestamp, the age of that observation, and a linkable error-group identifier when one exists. Green means a recent successful completion. Yellow means the latest observation is approaching its freshness deadline or reports degraded work. Red means a recent explicit failure or a missed freshness deadline. Unknown is different from green and should remain visually distinct after the first load.

The freshness rule matters more than the color palette. Suppose the pipeline is expected to finish once per night. A successful log from two nights ago cannot justify a green state today. The dashboard backend should compare the newest completion timestamp with a configured deadline and turn absence into yellow or red. This closes one failure boundary inside the admin page, but it doesn't create synthetic monitoring: a Healthchecks-style heartbeat tool is still the better companion when the central question is whether a scheduled task ran at all.

Store a periodic status check as both a low-cardinality metric and a structured log only when each representation earns its keep. The metric supports cheap state counts and trends. The log carries the run identifier, stage, outcome, and concise diagnostic context. An error group connects an outage-like red state to recent exceptions affecting the same service. Don't copy stack traces into metric labels.

Cardinality is the budget. A safe label set might include service, environment, stage, and a small outcome enum. Player IDs, match IDs, filenames, run UUIDs, and exception messages belong in structured logs, where they can be searched without multiplying every metric series. I count cardinality before retention: 12 services multiplied by 4 stages, 3 outcomes, and 2 environments yields 288 possible series; adding 100,000 player identifiers changes the economics and the query shape completely.

Keep it boring.

Invariants and failure boundaries

The dashboard has four invariants. First, stale is a state, not a missing value. Second, aggregation is deterministic: the same metric and log window must produce the same color. Third, the admin request never waits indefinitely for telemetry. Fourth, the UI identifies when it was last refreshed, because operators must be able to distinguish an unhealthy pipeline from an unhealthy dashboard.

Polling is required because there is no batch export or subscription API for logs. Set the refresh interval from the operational deadline, not from impatience. For a nightly job, polling every few seconds creates noise without revealing a meaningful state transition sooner; a longer interval can still be well inside the response objective. Add jitter if several admin clients poll independently, and cache a completed aggregation briefly in the Node.js backend so ten open tabs don't issue ten identical query pairs.

The catch is that alerts and notifications are outside this design. There is no threshold-rule, phone, SMS, or webhook route, so the service must poll and implement its own alert transition logic if notification is required. It should notify on a state edge, such as green to red, rather than on every red poll. This avoids an accidental notification storm while keeping the dashboard calculation easy to audit.

Distributed trace queries and span trees are also unavailable. Logs may carry trace_id and span_id for correlation, but the internal page should not pretend those fields constitute a trace explorer. Source-map resolution, crash symbolication, Electron minidump parsing, and Session Replay are separate requirements too. If those dominate incident response, this small status page isn't a good fit.

Retention math sets the last boundary. At one check per minute, a single service emits 1,440 checks per day before retries or duplicate reporters. Multiply that by services, stages, and retained days, then add log payload bytes. Retention and cold-storage controls are limited, and logs have no per-user deletion or batch export interface, so this pattern is for recent operational visibility rather than long-term compliance reporting. I'm not sure what server-side filter semantics will eventually be documented for metric and log queries; the discovery parameters do not declare filters now. Until that contract is explicit, the client should send no invented query parameters and should perform windowing and aggregation after receiving the documented response.

Option comparison

The decision is mostly about existing operational ownership. A team already paying the cognitive and staffing cost of a telemetry stack should demand a strong reason before adding another one.

Option Sensible choice when Main trade-off for this dashboard
Infrai A small backend team wants recent metrics, logs, and error groups through plain REST, with one key and one bill across backend services Requires application-owned polling and aggregation; no alert route, trace query, log subscription, or configurable long-term archive
Datadog The organization already has its dashboards and operating conventions there A second dashboard may duplicate established operational work
Grafana with Loki The team already operates its own visualization and log workflow Self-operation remains part of the ownership cost
Sentry Exception grouping is the main incident entry point Service freshness still needs an explicit heartbeat or completion signal
Prometheus Health state is metrics-first and logs can remain in another system Operators retain a split metrics-and-logs workflow

The first row is a strong option when credential and invoice sprawl are the larger architectural burden: one key and one bill cover a broad backend capability surface, while plain HTTP avoids installing another SDK. It is not suitable when the dashboard must own compliance archives, receive streamed logs, or provide trace waterfalls. Stick with an established Datadog or Grafana deployment when migration would merely recreate working dashboards, and pair any option with a Healthchecks-style service when missing cron execution is the failure that matters most.

This recommendation isn't about a nominally free query. Storage volume, label cardinality, operator time, and the number of systems that must be reconciled are the durable cost terms.

Critical polling path

The critical path is deliberately narrow. The backend issues two authenticated GET requests, checks status, honors rate-limit retry guidance, and writes the bodies to temporary files for application-side validation and aggregation. There are no query parameters because the discovery contract does not declare any for these operations.

curl --request GET "${API_BASE_URL}/v1/metrics/query" \
  --header "Authorization: Bearer ${INFRAI_API_KEY}" \
  --header "Accept: application/json" \
  --fail-with-body \
  --retry 4 \
  --retry-all-errors \
  --output /tmp/nightly-metrics.json

curl --request GET "${API_BASE_URL}/v1/logs/search" \
  --header "Authorization: Bearer ${INFRAI_API_KEY}" \
  --header "Accept: application/json" \
  --fail-with-body \
  --retry 4 \
  --retry-all-errors \
  --output /tmp/nightly-logs.json
Enter fullscreen mode Exit fullscreen mode

Configure API_BASE_URL and INFRAI_API_KEY outside the source tree. Curl's retry behavior uses the server's Retry-After value when it is present; --retry-all-errors also prevents a transient transport failure from becoming a tight application loop. --fail-with-body makes a non-success status fail the command while preserving the response body for diagnosis. The Node.js layer should reject malformed data, apply a request timeout around this subprocess or equivalent HTTP client, and retain the last successful snapshot with its timestamp rather than repainting a query failure as healthy.

After both requests succeed, reduce the data in one pass. Select records inside the local time window, group by the bounded service and stage fields, identify the newest terminal outcome, and attach an error-group reference when the available records contain one. Compute colors on the server so every browser sees the same decision. Return a compact view model to the admin page; don't ship an unrestricted log corpus to the browser.

One more subtle boundary: a query failure means the dashboard's state is unknown. It does not prove the game pipeline is red. Preserve the previous color, mark it stale, and show the failed refresh time. That distinction prevents the observability path from manufacturing an outage report about the workload it is trying to observe.

Rejected option and its valid use case

The rejected design is a streaming status console backed by indefinite raw-log retention. It adds moving parts without solving this nightly pipeline's decision problem, and the required log subscription and configurable cold-storage controls are not available here. Retaining every successful payload also weakens the signal-to-noise ratio: routine success should be a small counter plus a concise terminal event, while diagnostic detail is reserved for degraded and failed runs.

Streaming remains valid when operators must react to events within seconds and the chosen telemetry system provides a supported subscription contract. Long raw retention is valid for a regulated audit trail when deletion, export, retention policy, and cold storage are explicit product requirements. In either case, choose the system that owns those guarantees rather than stretching an internal uptime page into a compliance archive.

For the gaming pipeline described here, the smaller decision stands: poll recent evidence, aggregate locally, budget cardinality before bytes, and let staleness speak.

References

Top comments (0)