Skip to main content

Monitoring

What you'll do: know at a glance whether OpenBucket is healthy, scrape its metrics into Prometheus, follow structured logs by request ID, and audit who changed what.

Health and readiness probes

Two unauthenticated probes, safe for orchestrators to poll:

curl -s http://localhost:9000/api/admin/health # {"status":"ok","uptime":1234}
curl -s http://localhost:9000/api/admin/ready # {"status":"ready"} (503 while draining)
  • GET /api/admin/healthliveness: the process is up and the event loop responds. Use it for a container/liveness probe and to gate a load balancer.
  • GET /api/admin/readyreadiness: returns 200 {"status":"ready"} normally, and 503 {"status":"draining"} once a SIGTERM starts the graceful shutdown — so a rolling update pulls the pod before it stops accepting work.

(Under an embedded mountPath, these live at <mountPath>/api/admin/health and /ready.) See Deployment for probe wiring.

Prometheus metrics

A Prometheus scrape endpoint is served at /metrics (text exposition format 0.0.4). It is off by default — turn it on and pick an access mode:

METRICS_MODE=token # 'off' (default) | 'public' | 'token'
METRICS_TOKEN=<a-strong-random-token> # required + validated strong when MODE=token
  • off — the route isn't served; no registry body is ever leaked.
  • public — an unauthenticated scrape (for a trusted network / internal Prometheus).
  • token — requires Authorization: Bearer <token>, compared in constant time; a weak or empty token in token mode refuses to boot.

Scrape it:

curl -s -H "Authorization: Bearer $METRICS_TOKEN" http://localhost:9000/metrics

Exported families (all with bounded label cardinality — never a raw URL, object key, or client IP):

MetricTypeLabels
openbucket_http_requests_totalcountersurface, method, route_class, status_class
openbucket_http_request_duration_secondshistogramsame as above
openbucket_s3_operations_totalcounteroperation
openbucket_storage_bytesgaugebucket
openbucket_object_countgaugebucket
openbucket_replication_outbox_depthgaugestatus (pending/inflight/failed)
openbucket_integrity_objectsgaugestatus (ok/corrupt/unchecked)
openbucket_integrity_last_run_timestampgauge
openbucket_process_* / openbucket_nodejs_*default
What updates when

HTTP counters and the latency histogram are recorded live by the request interceptor. The per-bucket gauges are refreshed on the usage-rollup tick (USAGE_ROLLUP_INTERVAL_MS, default 15 min) from an in-memory aggregate — so a scrape never runs a database query — and a deleted bucket's series is evicted on the next tick. The integrity gauges appear only when the scrubber is enabled.

Good alerts to start with: openbucket_replication_outbox_depth{status="failed"} climbing, openbucket_integrity_objects{status="corrupt"} above zero, and the 0.99 quantile of the request-duration histogram.

Structured logs and request IDs

OpenBucket logs structured JSON via pino to stdout (Docker captures it; in NODE_ENV=development it pretty-prints). Set the level with LOG_LEVEL (tracefatal, default info).

Every request gets a request ID (a UUID v7, or the incoming X-Request-Id if you pass one), echoed back on two response headers so you can correlate a client error with a server log line:

X-Request-Id: 018f... the canonical id, also on every log line for the request
X-Amz-Request-Id: 018f... the same id, so S3 SDKs surface it in their error messages

Logs are redacted by design: secret-looking fields (password, secret, secretAccessKey, token, authorization, KEY_ENCRYPTION_SECRET, …) are censored, and SigV4 query-auth parameters are stripped from logged URLs so a presigned request never logs a replayable signature.

The audit log

Every state-changing admin action is emitted as a pino record (audit: true) and persisted to an audit_logs table, so you can browse history the log stream has rotated away. The admin console's Audit log page reads it, backed by two JWT-guarded, read-only endpoints:

EndpointReturns
GET /api/admin/audit?event=&subject=&bucket=&from=&to=&cursor=&limit=A newest-first, keyset-paged { items, nextCursor } (limit ≤ 200).
GET /api/admin/audit/catalogThe static event-name list for the filter dropdown.

Writes never block the request handler: emit pushes onto a bounded in-memory ring buffer, and a background flush tick (AUDIT_FLUSH_MS, default 2 s) batch-inserts drained rows and prunes anything older than AUDIT_RETENTION_DAYS (default 90). Secret-looking fields are stripped before a row is stored, and read-only GETs are not audited.

Tracing (optional)

Set OTEL_TRACING_ENABLED=true to wrap each request in an OpenTelemetry span (named by surface/route_class, only bounded attributes). It's a genuine no-op unless you install @opentelemetry/api and register an SDK — the library never hard-depends on any @opentelemetry/* package.

Next steps

  • Observability guide — a full walkthrough of metrics, dashboards, and alerts.
  • Deployment — health-probe wiring and the reverse proxy.
  • Durability — what the replication and integrity metrics mean.
  • Upgrading — what to watch during and after an upgrade.