Journal — March 27, 2026 · 5 min read

Observability on a Startup Budget

What to add first, second and third when you can't spend much — structured logs, error tracking, uptime checks, and the two variables (sampling and cardinality) that decide your bill.

The cheapest useful observability setup costs about $30/month and takes an afternoon. The expensive one costs more than your compute and tells you less, because nobody reads it.

The difference isn't budget, it's order. Here's what I'd add, in sequence, with what each stage costs.

Stage 0: structured logs, before you buy anything

Cost: nothing.

Before any vendor, turn your logs from strings into JSON objects. pino for Node, structlog for Python, slog in Go. Every line gets a level, a timestamp, a request ID and relevant fields.

This comes first because it's the one thing you can't retrofit cheaply. Every tool downstream is far better against structured data and nearly useless against console.log("user thing failed: " + err). Skip it and you'll re-instrument the codebase later, during an incident.

Two rules while you're in there. Attach a request ID at the edge and propagate it through every log line and outbound call. Never log full request or response bodies by default. That's your PII exposure and your log bill in one habit.

The logs can keep living in CloudWatch or docker logs for now. That's fine.

Stage 1: error tracking

Cost: free tier, then roughly $26–30/month on Sentry's team plan, metered by event volume.

Highest return on a dollar in the whole category, and it isn't close. Sentry (or Rollbar, or Bugsnag) gives you the exception, the stack trace with source maps applied, the request context, the user, the release it started in, and how many people hit it.

What it replaces is learning about bugs from customers.

Configure three things on day one:

  • Releases. Tag every deploy. "This started in Tuesday's build" is most of a root cause.
  • Source maps for anything JavaScript. Without them the stack traces are noise.
  • beforeSend scrubbing. Strip tokens, emails and card data before they leave your process. The default filters aren't enough.

Then be aggressive about resolving and ignoring. An issues list with four hundred open items is a list nobody opens.

Stage 2: uptime and synthetic checks

Cost: $0–20/month. Better Stack, Checkly, UptimeRobot, or a cron job hitting a healthcheck.

Error tracking tells you the app broke. It says nothing when the app is gone, because a dead process reports no exceptions. A DNS failure, an expired TLS certificate, a container that won't start: all silent in Sentry.

Check from outside your infrastructure, on the real domain, over the real certificate, against an endpoint that exercises the database rather than returning 200 from memory. Add a synthetic check for your most valuable flow, login or checkout. Ten minutes for the alert you'll be gladdest to have.

Certificate expiry belongs here too. Everyone automates renewal, and then it fails once.

Stage 3: four signals, and only four

Cost: usually included in what your host already gives you.

Now add metrics and alerts. The temptation is dashboards. Resist it: dashboards get read when someone already suspects a problem, alerts are what wake you up. Pick the small set you'd act on at 3am.

  • Error rate. A sustained rise in 5xx as a proportion of requests, not a raw count. Raw counts alert on traffic, not health.
  • Latency at p95 or p99. Never the mean. The mean hides the tail, and the tail is your users.
  • Saturation. Whichever resource runs out first. For most apps that's database connections or memory, not CPU.
  • Queue depth or lag. A backlog growing rather than draining is the earliest warning you get for a whole class of problems.

Everything else goes on a dashboard, unalerted. The best alert configuration I've worked with had six rules total.

The two variables that decide your bill

Every observability invoice comes down to sampling and cardinality. Understand both before you sign anything.

Sampling. You don't need every trace. Head sampling at 5–10% of successful requests plus 100% of errors and slow requests gives nearly all the diagnostic value for a fraction of the cost, and most SDKs do it in one config line. Teams skip it, ingest everything, then learn what per-GB pricing means.

Cardinality. A metric labelled with user_id or request_id creates a distinct time series per value. That's how one innocent label turns a hundred series into a million, and metrics platforms price on series count. Labels are for bounded values: endpoint, status class, region, deployment. Identifiers belong in logs and traces, never in metric labels. This is the most common route to a five-figure monitoring bill at a small company.

The pricing models to avoid at your size

Per-host APM. Datadog-style pricing around $15–31/host/month is reasonable for a stable fleet and hostile to autoscaling containers, where a "host" can be a task that lived ninety seconds. Read how your vendor counts hosts before committing.

Per-GB log ingestion. Roughly $0.10–0.50/GB depending on vendor and tier. Fine until a debug log ships to production, or a health check writes a line per second per instance, and you've bought a bill that arrives thirty days later. Set retention deliberately: 7 days for debug, 30 for application logs, longer only where compliance demands it. Same discipline that keeps a cloud bill from tripling.

Per-seat, where the seat blocks access. Price it so only three people can see production data and you'll have three people who understand production.

What not to add until later

Distributed tracing until you have services calling services. With one API and one database, a trace tells you what the slow query log already did.

Self-hosted Grafana, Loki and Prometheus until someone owns it. On paper you swap a few hundred dollars a month for open source. In practice you've acquired a stateful, storage-hungry system needing upgrades, retention tuning, and its own alerting for when the alerting is down. If the monitoring stack pages you about itself, you've bought a second product to operate. Below roughly ten engineers that's a false economy, and Grafana Cloud's free tier is generous.

Delete the alerts nobody acts on

Once a quarter, list every alert that fired and what was done about it. Any alert where the answer is "acknowledged and ignored" gets deleted or its threshold moved. Not muted. Deleted.

I kept a disk-usage alert that fired most weeks on a volume that always recovered on its own. It taught the team that pages are usually nothing, and that lesson is expensive, because it applies to the next page too. An alert that causes no action is worse than no alert: it trains you to ignore the channel the real one will arrive on.