Background
Current logs are dominated by health-check 200 request lines, while important subsystems (push notifications, WebAuthn signing flow, WebSocket lifecycle, MCP) produce little to no useful output. Debugging issues like the recurring iOS PWA push-notification disablement is hard because we can't see whether pushes were sent, accepted, or rejected (e.g. 410 Gone from APNs/FCM bridge).
Concrete recent example: notifications silently disable on the installed iPhone PWA. Logs show /api/push/subscribe being called on every app open (suggesting subscription eviction) and [WebAuthn Agent] Sign rejected … Signing request expired warnings, but nothing about whether web-push.sendNotification succeeded or what the push provider returned.
a) Structured app-wide logging
- Audit log call sites and add structured logs (with
reqId correlation where applicable) for:
- Push: subscribe / unsubscribe / send result (status, endpoint host, ttl) / dead-subscription cleanup on
404/410
- WebAuthn signing flow: request created, delivered to client, resolved/expired/rejected (with timing)
- WebSocket: connect / auth / disconnect / reconnect with reason
- MCP: tool invocations and errors
- Auth: login success/failure, session creation
- Demote noisy health-check / polling request logs (separate logger or
level: debug, or filter from access log).
- Standardize on a single logger config (pino) with consistent fields (
module, event, userId where safe).
b) Optional external log shipping
Make log destination configurable via env so a self-hosted aggregator can be plugged in. Candidates worth evaluating:
- Loki + Grafana (Grafana Labs) — lightweight, label-based, pairs naturally with the existing Grafana ecosystem. Probably the best fit.
- SigNoz — full observability (logs + traces + metrics), ClickHouse-backed.
- OpenObserve — single-binary, S3-backed, low resource use.
- Graylog — heavier but mature.
Likely approach: ship pino JSON logs to stdout (already the case) and let the operator wire up Promtail/Vector/Fluent Bit to forward to the chosen backend. Document the recommended setup in docs/.
Acceptance
- Triggering a push notification produces a clear log line including the provider response.
- Failed/expired subscriptions are logged and cleaned up from the DB.
- Health-check spam is no longer the dominant log output.
- README/docs describe how to forward logs to an external aggregator.
Background
Current logs are dominated by health-check
200request lines, while important subsystems (push notifications, WebAuthn signing flow, WebSocket lifecycle, MCP) produce little to no useful output. Debugging issues like the recurring iOS PWA push-notification disablement is hard because we can't see whether pushes were sent, accepted, or rejected (e.g.410 Gonefrom APNs/FCM bridge).Concrete recent example: notifications silently disable on the installed iPhone PWA. Logs show
/api/push/subscribebeing called on every app open (suggesting subscription eviction) and[WebAuthn Agent] Sign rejected … Signing request expiredwarnings, but nothing about whetherweb-push.sendNotificationsucceeded or what the push provider returned.a) Structured app-wide logging
reqIdcorrelation where applicable) for:404/410level: debug, or filter from access log).module,event,userIdwhere safe).b) Optional external log shipping
Make log destination configurable via env so a self-hosted aggregator can be plugged in. Candidates worth evaluating:
Likely approach: ship pino JSON logs to stdout (already the case) and let the operator wire up Promtail/Vector/Fluent Bit to forward to the chosen backend. Document the recommended setup in
docs/.Acceptance