deploy · tutorial · vps

pm2 logs Saved My 2 a.m. (Read Them Like This)

pm2 logs plus pm2 monit plus structured app logs: the 3-layer debug flow that ends 2am guessing. Incident walkthrough inside.

A handwritten page of the ship's log of HMS Dolphin, 1765
A handwritten page of the ship's log of HMS Dolphin, 1765 · Photo: John Byron, public domain, via Wikimedia Commons

2 a.m., alerts firing, logs everywhere

Telegram buzzed at 2:07am: checkout 500s. Termius opened from bed, pm2 logs ran, and the reader drowned — three workers interleaving, restart notices, deprecation warnings from a dependency, and somewhere inside, the actual error. Twenty minutes of scrolling before a null dereference that took ninety seconds to fix.

The incident lasted forty minutes. The fix took two.

Pm2 logs debug nextjs skill isn't talent — it's a flow practiced calm so it works panicked. Mine now runs in under five minutes, most of it waiting for commands to print.

Log chaos vs log flow at 2am

Minute Chaos (first incident) Flow (now)
0–2 SSH in, pm2 logs, scroll blindly Alert context names suspect (orderId)
2–10 Grep guesses, wrong service --lines 50 --nostream per process
10–20 Restart "to see if it helps" monit: memory/CPU per worker
20–30 Find error, guess cause Structured log: cause + request id
30–40 Fix + deploy + pray Fix + gate + reload + verify

Three things 2am teaches about logs:

  1. Interleaved workers hide sequences. Three processes writing one stream turns causality into confetti — the error's context (request in, query out) scatters across worker boundaries. Per-process views (--name filters) restore narrative; global tails destroy it. Debug one worker's story at a time, always.
  2. Restarts destroy evidence. A "restart to see" wipes the dying worker's final state and resets the log context needed — treatment before diagnosis, evidence cremated. Read first, restart last (and only with the fix deployed). Logs are crime scenes; secure them before touching anything.
  3. Unstructured logs answer nothing quickly. "Error: failed" with no request ID, no user, no timestamp context takes ten grep rounds to place. Structured lines (time, level, route, userId, requestId, message) answer in one look. Logging discipline pays exclusively at 2am — which is exactly when debts come due.

The Telegram-alert post starts incidents. This one ends them.

Pm2 logs debug nextjs flow

Past, present, why — three layers in fixed order, drilled monthly from the couch.

Step 1 — Read the recent past, per process.

pm2 logs boringpush --lines 50 --nostream   # last 50, frozen (no follow yet)
pm2 logs boringpush --lines 200 --nostream | grep -i "error\|500\|orderId"
# per-worker when interleaved: pm2 logs --name "boringpush-1" --lines 100

Frozen first (nostream stops the scroll so you can think), filtered second (error/500/context IDs), per-process third when workers interleave. The alert's context (orderId, route) focuses every grep — alerts without context send you reading everything; context turns forty minutes into four. --lines before --follow, always: history first, live tail after.

Step 2 — Check the live present with monit.

pm2 monit   # CPU + memory per worker, live: leak? loop? dead worker?
# shapes: one worker 100% CPU = infinite loop; climbing memory = leak; 0 workers = crash-loop guard tripped

Shapes diagnose instantly: flat-and-low means code bug, spiking means resource bug, dead means crash loop.

Step 3 — Follow structured app logs to the why.

// app log line shape (winston/pino, or console JSON — structure matters, vendor doesn't)
{"t":"02:07:11","lvl":"error","route":"POST /api/order","user":"u_8821","req":"r_99120","msg":"coupon null","stack":"..."}
# one line holds: when, what, where, who, which request, why — grep any dimension

Request IDs tie the whole incident together (alert → route → query → error in one grep chain). Structured lines make 2am reading deterministic: filter by req ID, read top to bottom, find the break. Unstructured logs need interpretation; structured logs need eyes. At 2am, eyes are all you have — feed them structure.

That's the product — incidents that end before the coffee cools.

Incident ledger, illiterate vs flow

2am incident First (chaos) Recent (flow)
Time to SSH + orient 10 min (scrolling) 2 min (alert context)
Diagnosis method Guesses + restart Per-process logs + monit
Evidence preserved No (restarted first) Yes (read first)
Fix + verify 15 min + prayer 5 min + health check
Total / sleep lost 40 min 9 min

Said without spin: structured logging costs lines of code per route (or one middleware doing it globally — do that) plus log volume (disk, rotation, retention policy). Priced against thirty extra minutes per incident times quarterly frequency: the middleware afternoon pays back in two incidents. Logs are written for the 2am reader; write them like that person matters, because that person is on call.

FAQ

Should pm2 logs debug nextjs go to files, journald, or a service?

Files by default (PM2 manages rotation with pm2-logrotate), journald on systemd-purist boxes, external service past the volume where grep gets slow (millions of lines/day). Solo: files + logrotate + 90-day retention cron. The medium matters less than the shape (structured) and the flow (past→present→why). Fancy pipelines serving unstructured text are lipstick on chaos.

How do I keep logs from filling the disk?

Rotate (pm2-logrotate: 10MB files, keep 7), retain 90 days via cron, compress cold logs, alert on disk >80% (Telegram scream, obviously). Disk-full kills apps silently (writes fail, SQLite can't checkpoint) — the retention cron is infrastructure, not housekeeping. Logs should observe incidents, never cause them. Cap everything, alert early.

What log level belongs in production?

Error + warn always, info for money paths (orders, webhooks, auth events), debug behind a flag (off in prod, on per-incident via env + reload). Verbose prod logs cost disk and drown signal; silent prod logs starve diagnosis. Money-path info is the sweet spot: audit trail plus incident context for free. Levels are budgets; spend on money.

Can agents read pm2 logs to self-diagnose incidents?

Yes, carefully: paste the frozen --lines output (not live tails) into the session with the alert context — agents correlate well with bounded input. Never pipe live logs into long sessions (context flood, token burn, the loop post's warnings apply). Bounded excerpts in, diagnoses out, human verifies. Agents read; humans conclude. Same split as everything.

How do I correlate logs across app, Nginx, and Cloudflare?

Request IDs propagated end to end (Cloudflare cf-ray + app req id in one structured line per hop), timestamps in UTC everywhere (timezone math at 2am kills), clock sync via NTP (drift lies about order). Three conventions make distributed debugging linear: shared IDs, shared timezone, synced clocks. Implement once, use every incident forever.

When do logs graduate to APM/tracing (Sentry, Datadog)?

Past single-box scale (distributed traces matter when requests cross services) or compliance (audit-grade retention with access controls). Solo VPS: structured files plus monit plus alerts cover 95% of incidents at $0. Graduate on distributed pain, not dashboard envy — traces answer cross-service questions you don't have yet. Logs first, traces when lost.

Keep reading

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading