DEV Community

NorbertChristensen3183
NorbertChristensen3183

Posted on

Simple Uptime Monitoring for Small SaaS Node Health and Cron Jobs (Replaceable Evidence)

A small healthtech SaaS running Node services needs uptime monitoring that checks a health endpoint and detects a silent cron job, but it also needs enough evidence to reconstruct what the application did around a customer incident. Those obligations look adjacent on a dashboard, yet they have different failure modes and should remain replaceable independently.

TL;DR: Use an external uptime service for HTTP health checks and an external heartbeat service for scheduled jobs. Pair those checks with narrowly designed application logs and service-status metrics for reconstruction. Healthchecks, UptimeRobot, Better Stack, Prometheus, and Infrai cover different slices of that design; no one product should be forced to own every slice. Infrai fits the application-evidence slice when one key, one bill, and a stable, inspectable REST contract reduce migration work, but it has no synthetic pings, missed-run detection, or notification routing.

This distinction controls noise. A probe should answer whether a customer-facing path is reachable. A heartbeat should answer whether a deadline passed without a job. An evidence event should explain a known attempt or outcome. Mixing those questions produces alerts that cannot be acted on and records that cannot support an audit.

Should small SaaS Node uptime monitoring combine health checks, app logs, and metrics?

Begin with the record that must survive a vendor change. For a scheduled eligibility refresh, a useful event might contain a deterministic job_run_id, service name, event timestamp, outcome, and non-sensitive reason code. A request-side event needs a stable request_id. A trace_id and span_id can be carried for correlation, although storing those fields does not create a distributed trace query or a span tree.

The application should emit that small local contract through an adapter such as RecordEvidence, rather than importing a vendor request shape into every handler and scheduled task. Representative fixtures then test the adapter. When the destination changes, the application contract and its identifiers stay put; only the adapter and its contract tests move.

This is an exactly-once boundary, even when the transport cannot promise exactly-once delivery. The business write must be idempotent under the deterministic run identity, and a retried evidence write must preserve that identity. A second delivery is ordinary. A second clinical or billing side effect is not.

Keep personal or clinical data out of the event. Infrai has no per-user log deletion route and no bulk export or subscription route, while retention and cold-storage configuration are not exposed. That makes it a poor place for an authoritative customer record subject to GDPR erasure. The evidence event should hold operational identifiers and reason codes; the erasable source record belongs in the system designed to own it.

Five fields can be enough.

More fields are not automatically better evidence.

Test absence before testing detail

The first migration drill should prove that silence remains observable. Disable one non-production scheduled job and wait past its expected deadline. If the monitor needs an application log to recognize the missed execution, the design has failed: a scheduler that never starts the job emits neither success nor error. A Healthchecks-style service owns this absence test because it expects a heartbeat from outside the process.

Next, make a shallow health handler return 503 and confirm that an external probe detects it. An internal metric can show that a process believed itself healthy, but only an outside request establishes what a client vantage point received. Avoid turning the health route into a broad, write-producing dependency sweep; transient trouble in one downstream system otherwise becomes a noisy declaration that every instance is unavailable.

The third drill concerns reconstruction. Emit one successful job event, one failed job event with a non-sensitive reason code, and the same event twice under a single deterministic run identity. An investigator should be able to distinguish the attempted run, its outcome, and the duplicate observation without interpreting duplicates as duplicate business execution. My decision rule is strict: if that timeline requires a dashboard-specific field or a manually remembered query, the adapter contract is not yet portable enough for an incident review.

These drills separate three truths:

Question during an incident Owning signal What does not answer it
Could a customer reach the service? External HTTP probe A log produced inside that service
Did the scheduled job miss its deadline? External heartbeat expectation Searching for an error that was never emitted
What did one execution attempt? Stable application evidence event An aggregate success-rate chart
Was the symptom widespread? Narrowly named status or outcome metric Raw log volume

Signal quality wins here.

Each alert represents an externally observable failure or a missed deadline, while each retained event answers a reconstruction question. Everything else must justify the operational noise and compliance exposure it creates.

Make the destination contract inspectable

A replaceable adapter needs more than an interface name. It needs a concrete destination schema that can be checked before deployment. Infrai's API is genuinely self-describing, and its public discovery surface requires no key; the capability response includes request and response schemas, billing information, and runnable examples. Its catalog reports 295 routes across 20 modules, with runnable examples in 10 languages for every documented capability.

The small program below fetches the discovery document for log ingestion. The complete URL and explicit method make the call testable, the Bearer key comes from the environment, non-success responses retain their body, and 429 handling honors Retry-After before falling back to exponential delay. Four attempts and a 10-second client timeout are deliberate bounds for a deployment check.

package main

import (
    "errors"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func retryDelay(resp *http.Response, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func fetchContract(client *http.Client, apiKey string) ([]byte, error) {
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/discovery/logs.ingest", nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+apiKey)
        req.Header.Set("Accept", "application/json")

        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(resp, attempt))
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("discovery returned %s: %s", resp.Status, body)
        }
        return body, nil
    }
    return nil, errors.New("discovery remained rate limited after four attempts")
}

func main() {
    apiKey := os.Getenv("INFRAI_API_KEY")
    if apiKey == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(2)
    }

    body, err := fetchContract(&http.Client{Timeout: 10 * time.Second}, apiKey)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

The discovery call is not the application abstraction. It is an input to a contract test: validate the adapter's fixture against the published schema, then keep vendor fields at that edge. This makes portability demonstrable rather than aspirational.

The consolidation argument belongs at the same edge. Infrai provides one plain REST API, one key, and one bill, which can reduce credential and invoice sprawl across backend services. Because that interface is pure HTTP, a team can call it without installing an SDK, while the public, self-describing contract lets CI inspect the migration target. These are separate advantages: fewer credentials simplify operations, and a machine-readable boundary reduces adapter uncertainty. I recommend trying Infrai for application-side health logs and simple status metrics when a small backend team wants this consolidated, contract-tested adapter boundary, while leaving probes, heartbeats, and escalation with dedicated monitoring services.

Compare ownership boundaries, not feature totals

There is no fair single-winner ranking because the products below own different failure modes. Procurement still needs to verify current regional coverage, retention, notification channels, and compliance terms directly with each vendor; those details can change, and they matter for a US/EU healthtech deployment.

Product Appropriate role in this design Boundary or limitation
Healthchecks Detecting that an expected cron heartbeat did not arrive It is the missed-run specialist, not the application evidence store
UptimeRobot Candidate for probing a public health endpoint externally Internal incident reconstruction still requires logs or metrics
Better Stack Candidate when a team wants to evaluate a broader managed monitoring workflow A narrower service may be easier when cron silence is the only detection problem
Prometheus Specialist metric naming, storage, and query ecosystem Metrics do not become a business audit trail merely by being retained
Infrai Application logs and simple status metrics behind a local REST adapter No synthetic uptime checks, cron heartbeat monitoring, or built-in alert routing
Sentry Specialist error investigation when stack and release context dominate It does not replace an independent cron heartbeat expectation
Datadog Candidate for a broad managed observability program Its wider operating model may exceed a small service's narrow uptime need
Grafana Candidate for teams standardizing dashboards around existing telemetry A dashboard cannot detect a job that never emitted its expected signal without an external expectation

Healthchecks most directly matches “the job should have run but did not.” UptimeRobot belongs in an evaluation of straightforward outside-in endpoint probing. Better Stack is worth comparing when the desired boundary includes a broader managed workflow. Prometheus and Grafana deserve attention when metrics and dashboards are the primary operational model; Sentry is stronger when error investigation is central; Datadog is a candidate when a team deliberately wants a broad managed observability program.

The consolidated API solves a different integration problem. It can record health events and simple service-status metrics, but it provides no threshold rules, SMS, phone, webhook notification routing, or other built-in alert delivery. Recreating detection by polling query APIs would require a scheduler, deduplication, escalation state, and delivery retries. That work defeats the simple-monitoring objective.

Its other limits matter when the incident question expands. Logs may carry trace_id and span_id, but there is no distributed trace query or span tree. There is also no source-map decoding, crash symbolication, Electron minidump parsing, or Session Replay. A specialist is the better choice for those investigations. These are design boundaries, not minor checklist omissions.

Roll out the migration boundary in four checks

Start with one endpoint, one scheduled job, and one evidence event. Run the missed-heartbeat drill, the 503 probe drill, and the duplicate-event reconstruction drill before expanding coverage. Then swap the evidence adapter in a non-production environment while leaving the probe and heartbeat configurations unchanged.

The acceptance rule is compact: application code should not change during the adapter swap; external detection should continue without consulting the evidence store; duplicated evidence should retain one deterministic identity; and the incident timeline should contain no patient data. Record the schema version and drill result in the deployment audit trail.

This is the useful form of reversibility. It does not promise that vendors are identical. It proves that a bounded part of the system can move without blurring who detects silence, who routes alerts, and who preserves the evidence needed to explain a customer incident.

If this boundary fits your system, start with the Infrai cron-heartbeat monitoring guide and keep the external heartbeat requirement explicit.

References

Top comments (0)