Scope
After v0.3.0 reliability is in. Observability is what turns "the bridge is running" into "I can tell when something is wrong without tailing journalctl."
Items
1. Structured JSON logs
Current logger.ts emits human-readable text. Switch to JSON by default with --pretty for humans.
- Add
LOG_FORMAT=json|pretty env var, default pretty
- JSON shape:
{ ts, level, msg, ...fields } (one JSON object per line)
- Use a minimal logger (write our own, ~30 lines, no dep) —
pino is overkill for this app
- The bridge's
[Bridge], [Fetch], [Token], [Proxy] prefixes should become structured fields (component: 'bridge', component: 'fetch', etc.) not text prefixes
This lets journalctl show structured fields via journalctl -o json and downstream tools (Loki, Vector) ingest cleanly.
2. Prometheus metrics endpoint
Standard /metrics endpoint on the same port as /healthz (or its own port). Exposes:
# HELP carelink_fetches_total Total fetch attempts
# TYPE carelink_fetches_total counter
carelink_fetches_total{result="success"} 42
carelink_fetches_total{result="failure"} 3
# HELP carelink_last_success_timestamp_seconds Unix timestamp of last successful fetch
# TYPE carelink_last_success_timestamp_seconds gauge
carelink_last_success_timestamp_seconds 1721305200
# HELP carelink_fetch_duration_seconds Duration of fetch attempts
# TYPE carelink_fetch_duration_seconds histogram
carelink_fetch_duration_seconds_bucket{le="1"} 30
carelink_fetch_duration_seconds_bucket{le="5"} 40
carelink_fetch_duration_seconds_bucket{le="30"} 42
- Counters:
carelink_fetches_total, carelink_uploads_total (per endpoint), carelink_token_refreshes_total
- Gauges:
carelink_last_success_timestamp_seconds, carelink_circuit_open (0/1)
- Histograms:
carelink_fetch_duration_seconds, carelink_upload_duration_seconds
Bind to 127.0.0.1:PORT/metrics. Use prom-client (~30kb, audited, well-known) OR a tiny hand-rolled prom exposition format (~80 lines).
3. Required for the above
The persistent state from v0.3.0 is the data source for the gauges; the circuit breaker is the data source for carelink_circuit_open.
Reference
- ROADMAP.md "v0.4.0 — observability" section
src/logger.ts (current text logger)
- v0.3.0 persistent state (dependency)
- v0.3.0 circuit breaker (dependency)
Why this is v0.4.0, not earlier
Without real-data validation we can't tell what the right histograms / useful metrics are. The "interesting" failures (CareLink rate-limit responses, token refresh edge cases, Nightscout upload timeouts) all need real traffic to know how to bucket them.
Scope
After v0.3.0 reliability is in. Observability is what turns "the bridge is running" into "I can tell when something is wrong without tailing journalctl."
Items
1. Structured JSON logs
Current
logger.tsemits human-readable text. Switch to JSON by default with--prettyfor humans.LOG_FORMAT=json|prettyenv var, defaultpretty{ ts, level, msg, ...fields }(one JSON object per line)pinois overkill for this app[Bridge],[Fetch],[Token],[Proxy]prefixes should become structured fields (component: 'bridge',component: 'fetch', etc.) not text prefixesThis lets journalctl show structured fields via
journalctl -o jsonand downstream tools (Loki, Vector) ingest cleanly.2. Prometheus metrics endpoint
Standard
/metricsendpoint on the same port as/healthz(or its own port). Exposes:carelink_fetches_total,carelink_uploads_total(per endpoint),carelink_token_refreshes_totalcarelink_last_success_timestamp_seconds,carelink_circuit_open(0/1)carelink_fetch_duration_seconds,carelink_upload_duration_secondsBind to
127.0.0.1:PORT/metrics. Useprom-client(~30kb, audited, well-known) OR a tiny hand-rolled prom exposition format (~80 lines).3. Required for the above
The persistent state from v0.3.0 is the data source for the gauges; the circuit breaker is the data source for
carelink_circuit_open.Reference
src/logger.ts(current text logger)Why this is v0.4.0, not earlier
Without real-data validation we can't tell what the right histograms / useful metrics are. The "interesting" failures (CareLink rate-limit responses, token refresh edge cases, Nightscout upload timeouts) all need real traffic to know how to bucket them.