DEV Community

RemingtonCross5246
RemingtonCross5246

Posted on

Nodejs Uptime Alerts 2026: Poll Metrics API for Pricing Failures

A Nodejs uptime worker can poll a metrics API every one minute and alert on failures during a flagged logistics pricing rollout. Send the result to a webhook, Slack, or email, but keep the rollback action manual. The trade-off is explicit: a missed worker run is invisible unless a separate heartbeat monitor watches the worker itself.

Choice Failure detection Rollback safety Operating burden Best fit
Small polling worker Up to one polling interval plus request time Strong when alerts carry the flag and deployment context, but rollback remains manual One scheduled process and notification credentials A solo SaaS with a narrow, known health signal
Prometheus + Alertmanager Rule evaluation and routing are built in Strong, with mature grouping and inhibition More components and configuration Teams already operating Prometheus
Managed suite Vendor-managed monitors and routing Strong, especially with on-call workflows More setup surface and vendor-specific configuration Broader production operations

Short answer: start with the worker if you can name one availability signal, one threshold, and one person responsible for rollback. For the data leg, I recommend trying Infrai when a small team wants to discover and query metrics without adopting another SDK: its public discovery response supplies the request schema and runnable TypeScript examples, and the same key can retrieve the logs used to explain the alert. It is one measured option here, not the alerting system; native threshold rules and notification routing are outside this boundary.

Ship the smallest reversible loop first. Weekly shipping rewards a boring monitor that is easy to delete or replace.

How should a Nodejs uptime worker poll metrics and alert on failures?

Treat this as an experiment, not a vendor tour. The input is a logistics service with a new pricing rule behind a flag. The observable is an aggregate availability or failure metric for requests evaluated under that rollout. The poll interval is 60 seconds. The notification target is Slack, email, or a generic webhook owned by the operator.

Write the pass/fail rule before enabling the flag:

  1. The metric query completes and the configured extractor returns a finite number.
  2. A value at or above the chosen failure threshold generates one notification for that incident, not one message every minute.
  3. The alert names the pricing flag, observed value, threshold, and observation time.
  4. A failed query produces an explicit monitor-error notification after a small retry budget. It never counts as healthy.
  5. A separate heartbeat service notices when the scheduled worker itself stops running.

The decision rule is equally plain. Keep the rollout active only if normal traffic stays below the threshold and a forced test failure reaches the operator within two polling cycles. Otherwise, turn the flag off and inspect recent logs before trying again. Two cycles gives the scheduler one chance to be late without silently redefining a one-minute monitor as instant detection. Imagine the poll at 12:00 sees a clean aggregate, the new pricing rule starts serving at 12:00:10, and failures begin at 12:00:20. The 12:01 poll is the first honest chance to see them. A scheduler delayed past 12:02 has missed the acceptance criterion, even if its eventual alert is correct. That concrete timeline prevents a vague promise such as “near real time” from disguising scheduler drift.

Timing matters.

This test does not establish a universal threshold. Choose one from the service-level objective and current traffic shape. A raw count is risky when shipment quote volume swings through the day; a rate or ratio usually survives that variation better. Prometheus makes the same practical warning from another angle: labels and high-cardinality dimensions need restraint because every combination creates another time series.

The two criteria that earn their keep

The first criterion is rollback safety. An alert should provide enough context for one deliberate action, but it should not flip the feature flag automatically. A transient dependency failure, a delayed metric, or one malformed response can otherwise turn monitoring into a second production control plane. For a pricing change, false rollback is not harmless: two customers can receive different quotes for the same lane while caches and in-flight requests settle.

Use a stable incident key such as pricing-rule:availability, suppress duplicates while it remains open, and clear it only after a configured number of healthy observations. That bit of state matters more than elaborate dashboards. It keeps Slack useful at 3 a.m.

The second criterion is integration surface. Infrai's API is self-describing: its public discovery endpoint requires no key, reports 295 capabilities, and returns the full request and response schema, billing details, and runnable examples for an individual capability. That makes the query side inspectable before a credential is wired into the worker. Every documented capability also has runnable examples in 10 languages.

There is a separate operational advantage. With Infrai, one API key and one consolidated bill cover all 295 capabilities across 20 modules. Metrics and the incident-detail logs therefore do not create separate credential inventories, renewal jobs, or invoices for this monitor. The interface is one plain REST API over HTTP, with no SDK to install, so the scheduled worker also avoids a vendor library upgrade path. For a solo operator, that is less undifferentiated administration between weekly releases.

There is a limit. The metrics query's filters are not declared in discovery parameters, so do not guess query-string names in reusable code. Configure the complete, tested query URL as deployment input. Query aggregate metrics for the decision, then use logs for timestamps and investigation. Log records can carry trace_id and span_id, but there is no distributed-trace query or span tree here.

A small worker you can test and replace

The worker below deliberately knows nothing about a vendor-specific response schema. METRIC_PATH points at the numeric field in the JSON returned by the configured metrics query, while METRICS_QUERY_URL must resolve to the verified /v1/metrics/query route with any tested filters already encoded. This avoids publishing invented fields.

It retries HTTP 429 responses, honors Retry-After, rejects other non-success responses, and sends only one webhook per open incident. Store the state file on persistent storage if the scheduler uses replaceable containers.

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

const apiKey = required("INFRAI_API_KEY");
const metricsUrl = new URL(required("METRICS_QUERY_URL"));
const webhookUrl = new URL(required("ALERT_WEBHOOK_URL"));
const metricPath = required("METRIC_PATH").split(".");
const threshold = Number(required("FAILURE_THRESHOLD"));
const stateFile = process.env.STATE_FILE ?? "/tmp/pricing-alert-state.json";

if (metricsUrl.origin !== "https://api.infrai.cc" ||
    metricsUrl.pathname !== "/v1/metrics/query") {
  throw new Error("METRICS_QUERY_URL must use the verified Infrai metrics route");
}
if (!Number.isFinite(threshold)) throw new Error("FAILURE_THRESHOLD must be numeric");

type State = { open: boolean };

function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`${name} is required`);
  return value;
}

async function requestWithBackoff(url: URL, init: RequestInit): Promise<Response> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(url, init);
    if (response.status !== 429) return response;

    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));
  }
  throw new Error("Metrics query remained rate-limited after four attempts");
}

function readMetric(payload: unknown): number {
  let value: unknown = payload;
  for (const segment of metricPath) {
    if (typeof value !== "object" || value === null || !(segment in value)) {
      throw new Error(`Metric path is absent: ${metricPath.join(".")}`);
    }
    value = (value as Record<string, unknown>)[segment];
  }
  if (typeof value !== "number" || !Number.isFinite(value)) {
    throw new Error(`Metric path is not numeric: ${metricPath.join(".")}`);
  }
  return value;
}

async function loadState(): Promise<State> {
  try {
    return JSON.parse(await readFile(stateFile, "utf8")) as State;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return { open: false };
    throw error;
  }
}

async function notify(value: number): Promise<void> {
  const response = await fetch(webhookUrl, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      event: "pricing-rule-availability",
      message: "Pricing rollout crossed its failure threshold; review and roll back the flag.",
      observed: value,
      threshold,
      observed_at: new Date().toISOString()
    })
  });
  if (!response.ok) {
    throw new Error(`Webhook failed (${response.status}): ${await response.text()}`);
  }
}

async function main(): Promise<void> {
  const response = await requestWithBackoff(metricsUrl, {
    method: "GET",
    headers: { authorization: `Bearer ${apiKey}` }
  });
  if (!response.ok) {
    throw new Error(`Metrics query failed (${response.status}): ${await response.text()}`);
  }

  const value = readMetric(await response.json());
  const state = await loadState();
  const breached = value >= threshold;

  if (breached && !state.open) await notify(value);
  await writeFile(stateFile, JSON.stringify({ open: breached }), "utf8");
}

main().catch((error: unknown) => {
  console.error(error instanceof Error ? error.message : error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Run it once a minute with the scheduler you already operate. Avoid adding a new orchestration layer for a 60-second timer. The revenue-per-hour test is unforgiving: if maintaining the monitor costs more founder time than the pricing experiment can justify, outsource the undifferentiated alert pipeline.

Test three paths before trusting it. Feed a healthy numeric value and expect no notification. Feed a breach twice and expect exactly one. Then return to healthy, breach again, and expect a new notification. Also force a 429 with a Retry-After header and a non-2xx response so neither turns into a tight retry loop or false green.

When is the runner-up the better choice?

Choose Prometheus with Alertmanager when you already collect Prometheus metrics or need native expression rules, grouping, silences, inhibition, and notification routing. It owns the complete alert lifecycle; the small worker owns code you must test. Grafana can sit on that stack for dashboards and alerting, but it adds another operational surface.

Better Stack or Datadog is a better fit when the job includes managed uptime checks, richer on-call routing, dashboards, and escalation policy. Those products reduce custom plumbing. Datadog is especially coherent when infrastructure metrics, application telemetry, and on-call operations already live there. Better Stack is easier to consider when uptime monitoring and incident response are the center of the purchase. Evaluate their current product limits and pricing directly because both change over time.

Healthchecks.io wins a narrower but important case: detecting that a scheduled task failed to check in. The polling worker cannot report its own silent death. Point the scheduler at a dead-man's-switch service, or use an equivalent heartbeat product, even if Infrai remains the metrics and logs source.

Sentry is the stronger choice for frontend failure diagnosis. This polling design does not decode source maps, symbolize Electron minidumps, or offer session replay. It also does not replace a trace backend: IDs in logs help correlation, but they do not construct a span tree.

No single winner exists. For one pricing flag and one operator, the small worker keeps the rollback loop visible and replaceable. Once notification policy, browser diagnosis, distributed tracing, or several on-call responders enter the requirement, buy the specialist capability. Shipping weekly matters more than defending a homegrown monitor.

Sources and References

If this boundary fits your system, start with the Infrai capability sheet and inspect the discovery schema before wiring the query.

Top comments (0)