DEV Community

HoratioFox1281
HoratioFox1281

Posted on

Better Uptime Stack: Self-Hosted Health Endpoints, Metrics, Logs, and EU/US Data

Short answer: for a SaaS MVP serving EU and US customers, start with an external uptime check, a small dependency-aware health endpoint, and low-cardinality metrics plus structured logs owned in a region you can verify. Use a self-hosted stack when residency and retention controls are more important than setup time; use a managed service when your team cannot operate storage and alert delivery.

The decision is about incident reconstruction, not collecting the largest pile of telemetry. In a logistics system, the useful question after a failed nightly data pipeline is: did the customer-facing API fail, did the import fail, or did the import finish with bad input? Your monitoring design should answer those questions in that order.

The field guide: which approach fits?

Approach Pick this when Good evidence for a pipeline incident Main trade-off
External uptime checks You need to know if customers can reach a public API from outside your network DNS, TLS, connection, HTTP status, and response-time history A green check says little about an internal queue or a stale shipment import
Self-hosted health endpoint and metrics You need control over region, retention, labels, and alert rules Dependency states, queue depth, import duration, and error counts Your team owns disks, upgrades, probes, and notification delivery
Managed logs and metrics You need a fast MVP path and do not have an observability operator Searchable events and dashboards with less infrastructure work You must review processing regions, retention, access controls, and export options
A scheduled-job heartbeat The highest-risk failure is a job that never starts or stops reporting Last successful completion and lateness It does not explain why a job failed or whether the public API is reachable

These are complementary checks. They are not four names for the same check.

An external probe follows this path: customer network -> DNS -> TLS -> edge -> public endpoint. A health endpoint follows another: request handler -> database -> queue -> dependency decision. Logs and metrics then preserve the evidence used to explain the decision. A heartbeat covers the batch process itself. Draw those arrows before choosing a product or deployment model.

For a small MVP, the least complex useful arrangement is one public probe, one /healthz endpoint, and a single incident identifier shared by the import log events. Add a heartbeat for the nightly job. That gives the on-call engineer a timeline without requiring distributed tracing on day one.

What should a SaaS MVP measure for uptime, health, metrics, and logs?

Define the failure modes first. For the logistics pipeline, I would record the following bounded signals:

  • pipeline_last_success_timestamp, so lateness is visible even when no new log arrives.
  • pipeline_duration_seconds, so a slow import is distinct from a stopped import.
  • pipeline_records_processed_total and pipeline_records_rejected_total, so a completed but empty or unhealthy run is visible.
  • queue_depth, labeled by a small fixed set such as region=eu or region=us.
  • Structured events with run_id, stage, result, and error_code fields.

Do not put customer email addresses, shipment payloads, or request IDs into metric labels. Prometheus calls out label cardinality as an instrumentation concern: labels with many possible values can create an unexpectedly large number of time series. The same discipline helps logs stay searchable and reduces the amount of personal data copied into operational systems.

A health endpoint should be explicit about its contract. A shallow liveness check answers “is this process able to respond?” A readiness check can include dependencies and answer “should this process receive traffic?” Mixing those meanings makes an incident timeline misleading. Return a stable status, a short machine-readable body, and no raw database error.

Here is a framework-neutral TypeScript example for a dependency-aware result. The handler around it can map ready: false to the HTTP status your deployment platform expects.

type DependencyState = "ok" | "failed";

type HealthResult = {
  ready: boolean;
  dependencies: Record<string, DependencyState>;
};

async function checkDependencies(
  checks: Record<string, () => Promise<void>>,
): Promise<HealthResult> {
  const entries = await Promise.all(
    Object.entries(checks).map(async ([name, check]) => {
      try {
        await check();
        return [name, "ok"] as const;
      } catch {
        return [name, "failed"] as const;
      }
    }),
  );

  const dependencies = Object.fromEntries(entries);
  return {
    ready: Object.values(dependencies).every((state) => state === "ok"),
    dependencies,
  };
}
Enter fullscreen mode Exit fullscreen mode

The endpoint is a measurement boundary, not an incident system. A failed database check should produce a bounded event with an error class and run identifier; it should not turn the response body into a log dump.

How can uptime checks, health endpoints, metrics, and logs reconstruct a pipeline incident?

Imagine the 02:00 UTC import for a carrier feed. The outside probe records HTTP 200 at 02:01. The API is reachable. At 02:04, pipeline_last_success_timestamp is old, queue_depth is rising, and the logs show stage=download result=failed error_code=carrier_timeout for one run_id. The public endpoint was healthy while the nightly business process was not. That distinction prevents an unnecessary rollback of the web service.

The useful reconstruction is a timeline, not a screenshot of a dashboard. Start with the probe event and its region, then match the run identifier to the job-start event, download result, validation count, and completion metric. If the job has no start event, investigate scheduling. If it has a start but no completion, investigate the worker and its dependencies. If it has a completion with a high rejection count, investigate input quality. A single “pipeline down” alert collapses all three cases into the same noisy page; these small signals keep the next action proportional to the failure.

Keep the timeline boring.

Now change the evidence. The external probe sees a TLS failure, while the application emits normal dependency metrics. The customer path is broken at the edge, and restarting a worker will not help. Or the job reports success, but pipeline_records_rejected_total jumps and the rejected count is close to the input count. The pipeline ran; its data quality did not meet the contract.

The incident record should preserve event time, ingestion time, run_id, stage, region, and result. Keep both timestamps. A delayed log can otherwise look like a late failure. Correlation is useful, but a correlation field is not a trace viewer; it only gives the responder a join key across records.

I would test these paths before calling the stack ready: a public endpoint failure, a dependency timeout, a job that never starts, a job that exits after partial work, and a successful run with rejected records. Each test needs an expected probe result, metric movement, log event, and alert owner. Five minutes of failure-mode testing is more valuable than another dashboard tile.

Where does this design stop being suitable?

The catch is operational ownership. A self-hosted metrics and log stack is not suitable when nobody owns disk capacity, retention, upgrades, access review, and alert delivery. Pick a managed service in that case, after confirming its processing region and deletion controls.

An external uptime check is not suitable as the only signal for a nightly pipeline. A self-hosted health endpoint is not suitable as proof that a customer can resolve DNS or complete TLS. A heartbeat is not suitable for diagnosing a bad carrier record. Choose the signal that observes the failure boundary.

For EU and US traffic, residency is a verification task, not a label in a dashboard. GDPR Article 5 includes data minimization, so send only fields needed for the operational question, restrict access, and document retention. I'm not sure a generic “EU region” promise tells you enough about backups, support access, or log exports; your mileage may vary, and the contract and data-flow review should resolve that uncertainty.

The practical stopping point for an MVP is modest: external reachability, dependency-aware readiness, one heartbeat, a few bounded metrics, and searchable structured events. Add richer tracing or longer retention when a demonstrated failure mode requires it.

References

Top comments (0)