Logging and Monitoring Your Project

5.A Smoke Alarm for a One-Person App

A

In this chapter

Anna adds structured logging with request IDs, error tracking, an uptime monitor and a few simple metrics and alerts to her live app — reviewing Acts 17, 19 and 21 at the size of a one-person project, explained as fitting a small shop with a logbook, a smoke alarm and a doorbell camera.

12–14 min

The Problem in Real Life

12:30 PM. While Anna eats pizza, a colleague messages her: "Your waitlist said 'Something went wrong' when I joined." She opens the platform's log page. It's a wall of console.log("here") lines from yesterday's debugging, and nothing about the error.

"Now you know how Mark's repo felt," John says, smiling. "Logs are for the 3 AM version of you. Give her something useful."

A

If it breaks while I'm asleep, I want to know what, where and why — from my phone.

Anna

Finding Out From Users vs. Finding Out First

Useless logs

Debug leftovers and no context — the one error that matters is invisible.

Downtime nobody notices

If the app goes down at night, she'd only find out when someone complains.

Is anyone using it?

Without numbers, she can't tell if the app is healthy — or popular.

Logging, Error Tracking and Basic Monitoring for Your Own Project

The small shop safety analogy: even a tiny shop has a logbook of what happened each day, a smoke alarm that screams if something's on fire, a doorbell camera that tells the owner's phone when someone's at the door — and the owner glances at the till each evening to see how the day went. A one-person app needs the same four things, in small sizes.

  • Structured logs — the logbook (Acts 17, 19): replace console.log with a small logging library (like pino) that writes JSON lines with a level, a time, a message and named fields. Remove the debug leftovers. Log the important events: waitlist_joined, organiser_login_failed, entry_removed — without passwords or full email addresses (Act 22: mask them, m***@example).
  • Request IDs — following one visitor (Act 23): give every request an ID, include it in every log line for that request, and return it in an X-Request-Id header and in error responses. Now "Something went wrong (ref: req_7f2a)" leads straight to the exact log lines.
  • Error tracking — the smoke alarm: an error-tracking service (Sentry and similar, many with free tiers) captures every unhandled error with its stack trace (Act 17), the request and how many users it affected, and emails or messages her. She sees errors before users report them.
  • Uptime monitoring — the doorbell camera: a free external service calls https://waitlist.anna-builds.example/health every minute from outside (Act 26's lesson: test the path users take). If it fails twice in a row, her phone buzzes. It also watches the certificate's expiry (chapter four).
  • A few metrics — glancing at the till (Act 19): the platform already shows requests, error rate, response time, CPU and memory. She adds two of her own: waitlist joins per hour and failed logins per hour (a jump could mean bots — Act 22).
  • Alerts that matter, and only those (Act 19): app down for 2 minutes; error rate above 5% for 5 minutes; certificate within 14 days of expiry. Three alerts. Enough to sleep well, few enough to never ignore.
Table — Monitoring for a one-person project
PieceShop versionWaitlist setup
Structured logs + request IDsThe logbookpino JSON logs, X-Request-Id
Error trackingSmoke alarmEvery unhandled error, with stack trace
Uptime monitorDoorbell camera/health every minute, from outside
MetricsGlancing at the tillJoins per hour, failed logins per hour
AlertsThe alarm that wakes youDown 2 min · errors > 5% · cert < 14 days
Request IDs and structured logging (pino)
const pino = require("pino");
const { randomUUID } = require("crypto");
const logger = pino({ level: process.env.LOG_LEVEL || "info" });
app.use((req, res, next) => {
req.id = req.headers["x-request-id"] || randomUUID();
req.log = logger.child({ requestId: req.id });
res.setHeader("X-Request-Id", req.id);
next();
});
// inside the join handler
req.log.info({ event: "waitlist_joined", eventId: req.params.id, email: mask(email) });
// error handler: log it, never leak details to the user
app.use((err, req, res, next) => {
req.log.error({ err }, "unhandled error");
res.status(500).json({ error: { code: "internal_error", requestId: req.id } });
});

Finding the colleague's error: with request IDs and structured logs in place, she asks him to try again. The error tracker shows it within seconds: TypeError: Cannot read properties of undefined (reading 'trim') in the join handler — his phone's autofill sent the field as Email with a capital E. She fixes the client to always send email, adds a test for a missing field (400, not 500), and ships it through the pipeline. Twelve minutes, start to finish.

Key Takeaway

Give even a one-person app a small safety system, like a shop's logbook, smoke alarm and doorbell camera: structured JSON logs with request IDs (and no secrets or full emails), an error tracker that reports every crash with its stack trace, an external uptime monitor on /health that also watches the certificate, a couple of meaningful metrics, and three alerts that truly matter.

Why This Matters

Logging and monitoring are what make a deployed project trustworthy, and adding them to your own work is how the ideas from the operations Acts become second nature. A portfolio project with request IDs, error tracking and uptime alerts shows you think about software after it ships — which is exactly what teams need.

The app is live, safe, tested and watched. One thing is still missing — the thing Anna complained about in Mark's repo. Nobody else could use, run or understand her project yet.

Next