🪦 feat: Request Tombstones, Memory Watermarks & Server Error Logging - #102
🪦 feat: Request Tombstones, Memory Watermarks & Server Error Logging#102dustinhealy wants to merge 1 commit into
Conversation
… bun server The server's only log output is its two startup lines. The HTTP metrics increment on completion, so a request that kills the process mid-flight is invisible to them, an uncaught handler throw produces no output at all, and there is no signal for memory growth between metric scrapes. Diagnosing a crash or an OOMKill from this is guesswork. Adds three narrow instruments to the serve path. Request lines log at arrival (before the handler) and completion, so a fatal request leaves its method and path as the process's last words; paths are logged without query strings, truncated, kubelet probes are skipped, and a per-window cap stops a request flood from amplifying into a log flood while still surfacing the suppressed count. A memory watermark logs RSS threshold crossings on a 10s tick to timestamp allocation bursts even when no request is in flight. An error hook on Bun.serve logs unhandled handler throws. On by default; disable with ADMIN_PANEL_REQUEST_LOG=false. Thresholds configurable via ADMIN_PANEL_MEMORY_LOG_THRESHOLDS_MB.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1637d38. Configure here.
| if (lastRssMb < threshold && rssMb >= threshold) crossed = threshold; | ||
| } | ||
| lastRssMb = rssMb; | ||
| return crossed; |
There was a problem hiding this comment.
Watermark false crossing at startup
Medium Severity
createMemoryWatermark treats the previous RSS sample as zero until the first check call. On the initial 10s tick, a process whose RSS already sits above configured thresholds (common after boot) logs an upward crossing even when memory did not grow since startup, which can misread deploy baselines as allocation bursts.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1637d38. Configure here.


Summary
The bun server's only log output is its two startup lines, which leaves crashes and OOMKills undiagnosable: the HTTP metrics increment on completion, so a request that kills the process mid-flight is invisible to them, an uncaught handler throw produces no output at all, and nothing captures memory growth between metric scrapes.
Adds three narrow instruments to the serve path (
server.ts, pure helpers insrc/server/logging.ts):[req] GET /pathat arrival (before the handler runs, so a fatal request leaves its method and path as the process's last words) plus[req] GET /path 200 12mson completion. An arrival line with no matching completion identifies the request a process died holding. Paths log without query strings (they can carry OAuth exchange codes) and truncate at 200 chars. Kubelet probes (kube-probe/user-agent) are skipped so steady-state logs stay near-silent. A per-window cap (200 requests / 10s) stops a request flood from amplifying into a log flood, surfacing[req] suppressed N requests in prior windowinstead.[mem] rss=391Mi heapUsed=213Mi (crossed 384Mi)on each upward threshold crossing (defaults 256/384/448 MiB, configurable viaADMIN_PANEL_MEMORY_LOG_THRESHOLDS_MB), re-arming when memory drops back below a threshold. This timestamps allocation bursts even when no request is in flight — the deciding evidence between a pathological request and runtime GC behavior.Bun.servehad noerror()handler, so an uncaught throw in the SSR handler produced no log line at all. It now logs the message and stack and returns a plain 500.On by default; disable with
ADMIN_PANEL_REQUEST_LOG=false. Prometheus metrics,/metrics, and/healthbehavior unchanged.Change Type
Testing
22 new unit tests (probe detection, truncation, flood-guard windows, watermark hysteresis); full suite (821 tests) passing;
tsc --noEmit,eslint, and a from-scratch production build clean.Verified live against the production build (
bun run build+bun run start):GET /login?redirect=/...logs as[req] GET /login— query string never appearskube-probe/1.29requests produce zero lines...(truncated)marker[req] suppressed 30 requests in prior windowwhen the next window opensADMIN_PANEL_REQUEST_LOG=falseproduces zero request lines under real trafficTest Configuration:
SESSION_SECRETsetADMIN_PANEL_MEMORY_LOG_THRESHOLDS_MB=50,80so thresholds sit below the server's baseline RSSChecklist