Cloudflare Worker Logs → Superadmin Audit Tab
Date: 2026-04-25 Status: ApprovedOverview
Receive Cloudflare Workers logs via OTLP HTTP push, store errors permanently in PostgreSQL, and display them in a new “Worker Logs” tab in the superadmin dashboard.Goals
- Capture ERROR/FATAL-level Worker logs permanently for superadmin review
- Keep DB load minimal: non-error logs are not stored (available in Cloudflare’s native observability for ~7 days)
- Auto-expire stored logs after 90 days to keep the table bounded
- Surface logs in a searchable, filterable superadmin UI tab
Architecture
Database Schema
New tableworker_logs in the app Postgres schema:
| Column | Type | Notes |
|---|---|---|
id | text PK | UUID |
timestamp | timestamp | From OTLP timeUnixNano |
severity | text | ERROR or FATAL |
severity_number | integer | OTLP numeric (17–24) |
message | text | body.stringValue |
trace_id | text | Optional |
resource_attrs | jsonb | Worker name, env, version |
log_attrs | jsonb | URL, method, status code, etc. |
received_at | timestamp | When server received it |
timestamp and received_at.
TTL: records where received_at < now() - interval '90 days' deleted by the existing 0 4 * * * daily cron.
OTLP Ingest Endpoint
File:server/src/routes/otlp.ts
Registration: api.route('/otlp', otlpRoute) in api.ts — outside protectedRoutes
POST /logs— public route, no session middleware- Auth:
Authorization: Bearer <token>header validated againstOTLP_INGEST_SECRETenv var; returns401if invalid - Parses standard OTLP JSON:
resourceLogs[].scopeLogs[].logRecords[] - Drops records where
severityNumber < 17 - Bulk-inserts surviving records into
worker_logs - Always returns
200 {}to prevent Cloudflare retries on processing errors
- Destination type: Logs
- OTLP endpoint:
https://api.odontox.io/api/v1/otlp/logs - Custom header:
Authorization→Bearer <OTLP_INGEST_SECRET value>
Superadmin API
Added toserver/src/routes/admin.ts (already behind verifySuperAdmin):
GET /api/v1/protected/admin/worker-logs
Query params:
page(default 1)limit(default 50)startDate,endDate(ISO strings)severity—ERROR|FATAL|allsearch— message text contains
{ logs: WorkerLog[], total: number, page: number, totalPages: number }
UI
New file:ui/src/components/superadmin/WorkerLogsPage.tsx
- Modeled after
AuditLogsPage.tsx - Table columns: Timestamp, Severity badge, Message (truncated), Resource/Worker name
- Row click → dialog with formatted JSON of
resource_attrs+log_attrs - Filters: date range, severity dropdown, message search
- Pagination matching existing audit logs pattern
- CSV export button
SuperAdminDashboard.tsx changes:
- Add nav item “Worker Logs” →
activeView === 'worker-logs' - Render
<WorkerLogsPage />for that view
Files Changed
| File | Change |
|---|---|
server/src/schema/worker_logs.ts | New schema table |
server/src/schema/index.ts | Export new table |
server/src/routes/otlp.ts | New OTLP ingest endpoint |
server/src/api.ts | Register /otlp route + TTL cron hook |
server/src/routes/admin.ts | Add GET /worker-logs |
ui/src/lib/serverComm.ts | Add getWorkerLogs function |
ui/src/components/superadmin/WorkerLogsPage.tsx | New tab component |
ui/src/components/dashboards/SuperAdminDashboard.tsx | Register new tab |
Security
- OTLP endpoint is public but protected by a static bearer token (
OTLP_INGEST_SECRET) - The token must be added as a Cloudflare Worker secret via
wrangler secret put OTLP_INGEST_SECRET - Superadmin API is behind
verifySuperAdminmiddleware (existing) - No clinic/patient data is stored in
worker_logs— infrastructure logs only
Out of Scope
- Storing INFO/DEBUG/WARN logs (available natively in Cloudflare for ~7 days)
- Live streaming of non-error logs in the UI
- Forwarding to third-party observability tools (Grafana, Honeycomb, etc.)

