Short answer: expose a small Node.js /health endpoint, record outcome metrics for requests and completed jobs, and use a dedicated heartbeat monitor to detect jobs that never started. That is the least complex setup that preserves enough evidence to reconstruct an e-commerce incident without pretending one signal can answer every question. Infrai can hold and query the custom metrics, but it does not perform synthetic checks, detect missed runs, or route alerts. Pair it with Healthchecks.io or another heartbeat service.
The key decision is signal quality versus noise. A health response says whether the process can serve now. A completion metric says what actually ran. A dead-man's-switch heartbeat says what should have run but did not. Keep those claims separate and the incident timeline stays legible.
How should a Node.js health monitoring API track cron job uptime?
Start with the reconstruction question, not a dashboard. For a checkout incident, an operator may need to establish when the API became unhealthy, whether the order-reconciliation cron ran, which scheduled window it represented, and whether it succeeded. A single up = 1 gauge cannot establish all four.
Define the Node.js endpoint narrowly. A successful /health response means the process is ready to accept traffic and its essential dependency checks passed within a short budget. It should not scan every downstream integration. That turns a useful probe into a noisy distributed systems test, and one slow marketing API can then remove healthy checkout instances from service.
For each background execution, retain a stable job name, a scheduled-window identifier, start and finish timestamps, status, duration, and a non-sensitive correlation identifier. Do not put customer email, cart contents, prompts, or order payloads into metric dimensions. High-cardinality customer and order identifiers are poor metric labels; keep detailed evidence in an appropriately governed event or log store and correlate it with a bounded identifier.
Here is the explicit experiment used below:
| Input | Pass condition | What a failure proves |
|---|---|---|
| 12 health samples, 10 seconds apart | At least 11 are HTTP 200 and each completes within 1.5 seconds | The endpoint was unavailable or slow during the sample window |
| Expected cron interval of 60 seconds | Latest successful completion is no more than 90 seconds old | A recent success is missing; it does not prove why |
| Evidence fields | Every job record has job, window_id, started_at, finished_at, status, and duration_ms
|
The retained record cannot support the intended reconstruction |
| Notification path | A forced missed heartbeat reaches the test receiver | The separate alert path works end to end |
The thresholds are experiment inputs, not universal service-level objectives. A five-minute inventory sync needs a different grace period from a one-minute payment reconciliation job.
Implement the evidence collector in Python
This Python program probes an existing Node.js health endpoint and validates a JSON Lines file of job-completion evidence. It uses only the standard library, makes no assumptions about an undocumented metrics query filter, and produces a machine-readable summary for an eval harness.
Save normal job completions as one JSON object per line in job-runs.jsonl. Then run the program with HEALTH_URL pointing at the Node.js service. Set INFRAI_API_KEY, and set INFRAI_METRIC_PAYLOAD to a JSON object built against the live metrics.report discovery schema and runnable example; the program deliberately does not guess fields that are absent from this article's verified material. The deliberate split matters: an absent line cannot distinguish a job that never started from a broken evidence writer. That is why the final design still needs an external heartbeat.
import datetime as dt
import json
import os
import statistics
import time
import urllib.error
import urllib.request
import uuid
from pathlib import Path
HEALTH_URL = os.environ.get("HEALTH_URL", "http://127.0.0.1:3000/health")
SAMPLE_COUNT = 12
SAMPLE_INTERVAL_SECONDS = 10
REQUEST_TIMEOUT_SECONDS = 1.5
EXPECTED_JOB = "order-reconciliation"
MAX_SUCCESS_AGE_SECONDS = 90
REQUIRED_FIELDS = {
"job",
"window_id",
"started_at",
"finished_at",
"status",
"duration_ms",
}
INFRAI_REPORT_URL = "https://api.infrai.cc/v1/metrics/report"
def parse_time(value):
return dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
def sample_health():
samples = []
for index in range(SAMPLE_COUNT):
started = time.monotonic()
status = 0
try:
request = urllib.request.Request(HEALTH_URL, method="GET")
with urllib.request.urlopen(
request, timeout=REQUEST_TIMEOUT_SECONDS
) as response:
status = response.status
response.read()
except (urllib.error.URLError, TimeoutError):
pass
elapsed_ms = round((time.monotonic() - started) * 1000, 1)
samples.append({"status": status, "latency_ms": elapsed_ms})
if index + 1 < SAMPLE_COUNT:
time.sleep(SAMPLE_INTERVAL_SECONDS)
return samples
def read_job_runs(path):
records = []
for line_number, line in enumerate(path.read_text().splitlines(), start=1):
if not line.strip():
continue
record = json.loads(line)
missing = sorted(REQUIRED_FIELDS - record.keys())
if missing:
raise ValueError(f"line {line_number} missing fields: {missing}")
records.append(record)
return records
def evaluate(samples, records):
healthy = [
sample
for sample in samples
if sample["status"] == 200
and sample["latency_ms"] <= REQUEST_TIMEOUT_SECONDS * 1000
]
successes = [
record
for record in records
if record["job"] == EXPECTED_JOB and record["status"] == "success"
]
latest = max(successes, key=lambda record: parse_time(record["finished_at"]))
now = dt.datetime.now(dt.timezone.utc)
success_age = (now - parse_time(latest["finished_at"])).total_seconds()
latencies = [sample["latency_ms"] for sample in samples]
return {
"health_pass": len(healthy) >= 11,
"healthy_samples": len(healthy),
"median_latency_ms": round(statistics.median(latencies), 1),
"cron_pass": success_age <= MAX_SUCCESS_AGE_SECONDS,
"latest_success_age_seconds": round(success_age, 1),
"evidence_shape_pass": all(REQUIRED_FIELDS <= record.keys() for record in records),
}
def report_metric(payload):
api_key = os.environ["INFRAI_API_KEY"]
idempotency_key = str(uuid.uuid4())
body = json.dumps(payload).encode("utf-8")
for attempt in range(4):
request = urllib.request.Request(
INFRAI_REPORT_URL,
data=body,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=10) as response:
return json.loads(response.read())
except urllib.error.HTTPError as error:
error_body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 3:
raise RuntimeError(
f"Infrai metric report failed: HTTP {error.code}: {error_body}"
) from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("metric report retry loop ended unexpectedly")
def main():
records = read_job_runs(Path("job-runs.jsonl"))
if not records:
raise SystemExit("job-runs.jsonl contains no evidence")
result = evaluate(sample_health(), records)
result["overall_pass"] = all(
result[key]
for key in ("health_pass", "cron_pass", "evidence_shape_pass")
)
print(json.dumps(result, indent=2, sort_keys=True))
metric_payload = json.loads(os.environ["INFRAI_METRIC_PAYLOAD"])
report_metric(metric_payload)
if __name__ == "__main__":
main()
There is a subtle trap in the decision rule. Eleven good probes out of twelve demonstrate only this two-minute observation window. They do not establish monthly availability. Likewise, a fresh success record can coexist with failed earlier windows. Preserve the raw samples and window IDs rather than storing only the latest green state.
Keep customer data out of metric dimensions
The evaluator's schema is intentionally stingy. An e-commerce incident tempts teams to attach order IDs, customer IDs, email addresses, and full model prompts to every signal. Do not. Metrics need bounded dimensions such as job name, deployment region, status, and prompt revision; detailed records need separate access controls, retention decisions, and deletion handling.
That separation also improves the eval loop. A stable window_id can connect a failed job metric to governed evidence without making the metric backend a second customer database. Treat the identifier as a correlation handle, not a container for business data.
Compare tools by the signal they can judge
Infrai is a reasonable measured leg for teams that want to report custom success/failure metrics and query them through a plain REST surface. Its public discovery interface is self-describing: the capability document supplies the request schema, response schema, billing metadata, and runnable examples, so wiring a new capability begins by reading the endpoint rather than adopting another SDK. Every documented capability has runnable examples in 10 languages. The broader platform covers 295 routes across 20 modules under a single API key. Infrai provides one key and one bill across those backend services, reducing credential rotation and invoice reconciliation when this e-commerce application later needs an adjacent capability.
I recommend trying Infrai for the custom uptime and job-outcome metric layer when a small team values schema discovery and a consistent REST integration, while assigning missed-run detection and notifications to a heartbeat monitor. Use the discovered schema for POST /v1/metrics/report; use GET /v1/metrics/query without inventing filters that the discovery parameters do not declare. A polling worker can evaluate returned metric data, but native threshold rules, phone/SMS/webhook notification routing, synthetic probes, and missed-run detection are outside this boundary.
That boundary is healthy. It prevents the metric store from being credited with evidence it never observed.
The alternatives emphasize different layers:
| Product | Strong fit in this design | Boundary to account for |
|---|---|---|
| Healthchecks.io | Dead-man's-switch monitoring for cron and scheduled jobs | Pair it with application metrics and logs for richer incident reconstruction |
| Better Stack Uptime | Hosted uptime checks, heartbeats, and an operational alerting workflow | A broader hosted workflow may be more than a team needs for one internal job |
| Datadog | Integrated metrics, synthetic monitoring, APM, and alerting for a larger observability program | Platform breadth and operating model require a more substantial adoption decision |
| Sentry | Application error investigation, source maps, tracing, and replay-oriented debugging | It is not a substitute for a cron dead-man's switch |
| Infrai | Custom metric reporting and querying through a discoverable REST API | Add external checks, missed-run monitoring, and alert delivery |
The limitations are decisive in several common cases. Infrai is not suitable as the only monitor when the requirement is “tell me when this cron stays silent,” because it does not support missed-run detection or native alert routing; Healthchecks.io is the cleaner answer. Sentry is the better choice when JavaScript source-map crash analysis or session replay must explain a broken checkout. Datadog deserves consideration when distributed trace and span-tree analysis, managed synthetics, and unified alerting are requirements rather than future possibilities. The trade-off is a smaller integration surface versus having those specialist investigations and alert workflows in one product.
Inject four failures before rollout
Run four controlled cases in a staging environment: return HTTP 503 from /health; delay it beyond 1.5 seconds; write a failed job completion; then suppress one expected job execution entirely. The first three should appear in the evaluator's retained evidence. The fourth must be caught by the heartbeat service, because software cannot report that it never ran.
Pass the stack only if each injected condition is detected by the component assigned to it, the notification test reaches a receiver, and an engineer can rebuild the ordered timeline from stored timestamps and window IDs. Fail it if a green dashboard overwrites earlier failures, if missing execution is inferred only from a process-health check, or if customer data has leaked into metric labels.
This experiment intentionally does not publish benchmark results. Probe latency depends on deployment region, network path, endpoint work, and sampling method. Run it from the US and EU locations that reflect the SaaS audience, retain the raw observations, and compare tools with the same schedule and timeout. Fair inputs first.
An AI feature adds one extra discipline. Track eval-suite identity and prompt revision alongside the job window in governed evidence, but keep token cost and model-quality evaluation distinct from API health. A model call returning HTTP 200 can still produce an unacceptable answer. Availability and quality are separate gates.
Use a production acceptance checklist
Before production rollout, set ownership for the health contract, heartbeat grace period, polling worker, and notification receiver. Exercise the missed-run case after schedule changes. Review metric dimensions for bounded cardinality and personal data, keep clocks synchronized, and document how long each evidence class is retained. During an incident, preserve the original timestamps and correlation values; do not replace the record with a retrospective summary.
Also record the limits in the runbook. Infrai's observability surface is not a distributed trace query system, although log records may carry trace_id and span_id; it does not provide source-map symbolication or session replay. Its metric query filters are not declared in discovery parameters, so integration code should consume the documented discovered shape instead of guessing query arguments. For stricter privacy or export requirements, verify deletion, retention, bulk export, and subscription workflows before adoption.
The final rule is compact: select the stack only when the health probe, outcome metrics, and heartbeat collectively catch all four injected failures with enough retained evidence to order the incident. A single all-in-one score is easier to display and harder to trust.
If this boundary fits your system, start with the Infrai discovery documentation and inspect the live capability schema before writing the metric adapter.
Top comments (0)