Observability
BunBase provides structured logs, Prometheus metrics, and a hot backup endpoint for production operations.
Structured Logs
Section titled “Structured Logs”All logs are emitted as JSON to stdout:
{ "level": "info", "event": "request", "request_id": "a1b2c3d4-...", "method": "POST", "path": "/api/v1/auth/login", "status": 200, "latency_ms": 4, "user_id": "01JKX...", "ip": "203.0.113.1", "ts": 1709900000000}Log levels
Section titled “Log levels”| Level | Description |
|---|---|
debug | Detailed internal events |
info | Normal operation (default) |
warn | Non-fatal issues |
error | Errors requiring attention |
off | Disable all logging |
Set with LOG_LEVEL env var.
Access logs
Section titled “Access logs”HTTP access logs can be disabled separately:
LOG_ACCESS=off # disable request logs onlyLog retention
Section titled “Log retention”Logs are stored in SQLite files under DATA_DIR/logs/. Two pruning policies run every minute:
Row-count cap (always active) — oldest rows are deleted once the table exceeds the limit:
| Runtime setting | Default | Description |
|---|---|---|
access_log_max_rows | 10000 | Max rows in the access log |
app_log_max_rows | 10000 | Max rows in the app log |
Age cap (opt-in) — rows older than N days are deleted:
| Runtime setting | Default | Description |
|---|---|---|
access_log_max_age_days | 0 | Delete access logs older than N days (0 = disabled) |
app_log_max_age_days | 0 | Delete app logs older than N days (0 = disabled) |
Both policies are applied on every scheduler tick (every 60 seconds). Age-cap is applied after row-cap so both constraints are honoured.
Request ID
Section titled “Request ID”Every request gets a unique request_id (UUID v4). It is returned in the X-Request-Id response header and included in all log entries for that request.
Prometheus Metrics
Section titled “Prometheus Metrics”BunBase exposes a Prometheus-compatible /metrics endpoint:
curl http://localhost:8080/metrics# HELP bunbase_pending_requests Number of in-flight HTTP requests per worker.# TYPE bunbase_pending_requests gaugebunbase_pending_requests{worker="0",instance="..."} 3
# HELP bunbase_pending_websockets Number of active WebSocket connections per worker.# TYPE bunbase_pending_websockets gaugebunbase_pending_websockets{worker="0",instance="..."} 12
# HELP bunbase_active_workers Number of active HTTP workers in the cluster.# TYPE bunbase_active_workers gaugebunbase_active_workers 4
# HELP bunbase_request_latency_p50_ms 50th-percentile request latency (last 1000 requests, this worker).# TYPE bunbase_request_latency_p50_ms gaugebunbase_request_latency_p50_ms{worker="0",instance="..."} 2.3
# HELP bunbase_request_latency_p95_ms 95th-percentile request latency (last 1000 requests, this worker).# TYPE bunbase_request_latency_p95_ms gaugebunbase_request_latency_p95_ms{worker="0",instance="..."} 12.1
# HELP bunbase_request_latency_p99_ms 99th-percentile request latency (last 1000 requests, this worker).# TYPE bunbase_request_latency_p99_ms gaugebunbase_request_latency_p99_ms{worker="0",instance="..."} 45.3
# HELP bunbase_requests_per_minute Number of requests handled in the last completed minute, per worker.# TYPE bunbase_requests_per_minute gaugebunbase_requests_per_minute{worker="0",instance="..."} 87
# HELP bunbase_errors_per_minute Number of 5xx responses in the last completed minute, per worker.# TYPE bunbase_errors_per_minute gaugebunbase_errors_per_minute{worker="0",instance="..."} 0
# HELP bunbase_info Static server metadata.# TYPE bunbase_info gaugebunbase_info{version="0.7.0",bun_version="1.3.10"} 1Add a Prometheus scrape job:
scrape_configs: - job_name: bunbase static_configs: - targets: ['localhost:8080'] metrics_path: /metricsHot Backup
Section titled “Hot Backup”Trigger a consistent SQLite backup while the server is live:
POST /api/v1/admin/backupAuthorization: Bearer <admin-secret>The server streams back a .db file using SQLite’s VACUUM INTO, which produces a clean, defragmented copy with no journal files needed. The backup can be taken under full write load without locking the database.
curl -X POST http://localhost:8080/api/v1/admin/backup \ -H "Authorization: Bearer $ADMIN_SECRET" \ --output backup-$(date +%Y%m%d-%H%M%S).dbAutomated backups
Section titled “Automated backups”#!/bin/shDATE=$(date +%Y%m%d-%H%M%S)curl -sf -X POST https://your-domain.com/api/v1/admin/backup \ -H "Authorization: Bearer $ADMIN_SECRET" \ --output /backups/bunbase-$DATE.db# Keep last 30 backupsls -t /backups/bunbase-*.db | tail -n +31 | xargs rm -fSchedule with cron:
0 * * * * /usr/local/bin/bunbase-backup.shHealth Check
Section titled “Health Check”curl https://your-domain.com/api/v1/admin/health{ "status": "ok", "db": "ok", "uptime_ms": 86400000, "version": "0.9.0", "bun_version": "1.2.0"}When the DB worker is unreachable, the endpoint returns HTTP 503:
{ "status": "degraded", "db": "error", "uptime_ms": 86400000, "version": "0.9.0", "bun_version": "1.2.0"}Load balancers should check for HTTP 200 to route traffic; they will automatically stop sending requests to instances that return 503.