Observability for a side project: 80% of the value for $0/month

How I wire up logs, errors, uptime and product analytics on side projects without paying a cent. Free tiers, sharp edges, and what I actually skip.

Observability for a side project: 80% of the value for $0/month
KEY TAKEAWAYS
  • Axiom's 500GB/month free tier covers logs for almost any side project if you batch writes from a Cloudflare Worker.
  • Sentry's free tier (5k errors/month) plus a Slack webhook gets you 80% of error tracking with zero ops.
  • BetterStack and UptimeRobot both have generous free uptime checks, but BetterStack's status page is the actual reason to pick it.
  • PostHog's free tier (1M events/month) handles product analytics, session replay, and feature flags in one tool.
  • Skip APM and distributed tracing on side projects. You don't have the traffic to justify the wiring.

I have a side project that does maybe 4,000 requests a day. It runs on a Cloudflare Worker, talks to a Hetzner box, and charges people through Stripe. Total infra cost: about $7/month for the VPS. I am not going to spend $89/month on Datadog to watch it.

But I still need to know when it breaks. I need logs I can grep. I need an alert when Stripe webhooks start failing. I need to know if a user hit a 500 at 2am so I can fix it before they email me asking for a refund.

Here is the stack I have landed on after building maybe a dozen of these. It costs $0/month, takes about an hour to wire up, and catches roughly 80% of what a real paid setup would catch. The other 20% is APM, distributed tracing, and fancy dashboards I would never actually look at.

Logs: Axiom or Better Stack

For logs I use Axiom. Their free tier is 500GB/month ingest and 30 days retention. I have never come close to hitting that on a side project. The query language is decent and the UI does not feel like a 2014 enterprise tool.

From a Cloudflare Worker you can ship logs with their tail Worker, but honestly I just batch and POST directly:

// lib/log.ts
type LogEvent = { level: string; msg: string; [k: string]: unknown };

const buffer: LogEvent[] = [];

export function log(event: LogEvent, ctx: ExecutionContext, env: Env) {
  buffer.push({ ...event, _time: new Date().toISOString() });
  if (buffer.length >= 20) {
    ctx.waitUntil(flush(env));
  }
}

async function flush(env: Env) {
  if (!buffer.length) return;
  const batch = buffer.splice(0);
  await fetch(`https://api.axiom.co/v1/datasets/${env.AXIOM_DATASET}/ingest`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.AXIOM_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(batch),
  });
}

The ctx.waitUntil matters. Without it the Worker can return before the log ships, and you lose events. I learned that the hard way debugging a Stripe webhook that was “silently” 500ing. The 500 was loud. My logs just never made it out.

Better Stack (formerly Logtail) is the other reasonable option. 1GB/month free, 3 days retention. Smaller, but their UI is genuinely nice and they bundle uptime monitoring on the same account.

Errors: Sentry, and only Sentry

Sentry’s free tier is 5,000 errors/month, 10,000 performance events, and one user. For a side project that is plenty. If you are hitting 5k errors/month on a side project, the problem is your code, not your observability budget.

Install takes two minutes:

npm install @sentry/cloudflare
import * as Sentry from '@sentry/cloudflare';

export default Sentry.withSentry(
  (env) => ({
    dsn: env.SENTRY_DSN,
    tracesSampleRate: 0.1,
  }),
  {
    async fetch(request, env, ctx) {
      // your handler
    },
  }
);

The thing I actually care about is the Slack integration. Set it up to ping a #errors channel only on new issues, not regressions, not every occurrence. Otherwise you train yourself to ignore the channel within a week.

Glitchtip is the self-hosted alternative if you are allergic to SaaS. It speaks the Sentry SDK protocol. I have run it on a $4 Hetzner box for a year. It works. The UI is rougher and you are now on the hook for keeping Postgres alive, which is its own observability problem.

Uptime: BetterStack or UptimeRobot

UptimeRobot gives you 50 monitors at 5-minute intervals for free. BetterStack gives you 10 monitors at 3-minute intervals plus a public status page. For a side project you want the status page more than you want 50 monitors.

The trick with uptime checks is to not just ping /. Ping a real endpoint that touches your database. I have a /health route that does a SELECT 1 and returns the count of rows in a small table:

app.get('/health', async (c) => {
  const start = Date.now();
  const result = await c.env.DB.prepare('SELECT COUNT(*) as n FROM users').first();
  return c.json({
    ok: true,
    db_ms: Date.now() - start,
    users: result?.n,
  });
});

When Postgres goes weird, / still returns 200 because it is a static HTML page. /health will time out or return a slow response, and the monitor will catch it.

Product analytics: PostHog

PostHog gives you 1 million events/month free. Same account covers product analytics, session replay (5,000 sessions/month), feature flags, and surveys. For a side project that is more than I will ever use.

I do not bother with PostHog for the first week of a project. I add it when I have actual users to learn about. The reason is that PostHog’s JS bundle is not tiny (about 50KB gzipped) and it shows up in Lighthouse scores. If you care about LCP on a marketing page, lazy-load it or use their posthog-js-lite package.

One thing I do religiously: set person_profiles: 'identified_only'. Otherwise PostHog creates a profile for every anonymous visitor and you burn through your event quota by week three of going semi-viral on Reddit.

What I deliberately skip

No APM. No distributed tracing. No custom Grafana dashboards. No Prometheus scraping. No OpenTelemetry collector.

On a side project with one or two services, traces tell you things you can already figure out from logs and Sentry. The wiring is real, the storage adds up, and you will never open the trace view after week two. I have tried. I built a beautiful Grafana setup for Photo AI Studio in 2024 and I have opened it maybe four times in eighteen months. Sentry told me everything I needed.

If the project actually takes off and you start getting weird latency complaints that logs cannot explain, then add tracing. Tigris and Baselime (now owned by Cloudflare) both have reasonable free tiers when that day comes. It is just not day one work.

The actual setup, in order

If I am starting fresh tomorrow:

  1. Sentry, ten minutes, ship errors to Slack.
  2. Axiom, twenty minutes, structured logs with a request_id on every event.
  3. BetterStack, ten minutes, monitor /health and put the status page on a subdomain.
  4. PostHog, when I have users.

That is it. One hour, $0/month, and I sleep through the night.

The trap with observability is that it scales with how seriously you take the project, not with how big the project is. A side project with five users does not need the same telemetry as a Series B startup. Pick the four tools above, wire them up once, copy the setup into the next project. You will be fine.

If you are building something and want a hand setting this up properly, or untangling a logging mess on a project that grew faster than the observability did, get in touch.

FREQUENTLY ASKED

Common questions

Why not just use console.log and tail the Cloudflare dashboard?

You can, for about a week. Cloudflare's tail UI only shows live logs, not historical ones, and you cannot query them. The moment a user reports a bug from yesterday, you are stuck. Shipping logs to Axiom or Better Stack takes 20 minutes and gives you 30 days of searchable history for free.

Is Sentry's free tier really enough for a real product?

For a side project with under a few thousand daily users, yes. 5,000 errors/month is roughly 160/day. If you exceed that, you have bigger problems than the bill. Set inbound filters to drop noisy browser extension errors and bot traffic, and the quota lasts longer than you think.

Should I self-host Glitchtip instead of using Sentry?

Only if you enjoy running Postgres. Glitchtip works and uses the same SDKs, but you are trading $0/month SaaS for $5/month VPS plus the time to keep it alive. For a side project the SaaS free tier wins. Self-host when you have compliance reasons or genuinely outgrow the free tier.

What about logging from a regular Node server, not a Worker?

Same approach, different transport. Use pino with the pino-axiom or pino-logtail transport, or just batch and POST yourself. The key idea holds: structured JSON logs with a request_id field, batched and shipped async, so you can correlate across services later.

Do I need OpenTelemetry?

Not for a side project. OTel is great when you have five services and need a vendor-neutral story. With one or two services, a request_id in your structured logs gives you 90% of what tracing would. Add OTel when you actually have a distributed system, not because a blog post said you should.

SOURCES
  1. [1]
  2. [2]
  3. [3]
    PostHog pricingposthog.com
  4. [4]
    Better Stack pricingbetterstack.com
  5. [5]
    UptimeRobot pricinguptimerobot.com
  6. [6]

Related posts