Skip to main content

Cloudflare Worker Logs → Superadmin Audit Tab

Date: 2026-04-25 Status: Approved

Overview

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

Cloudflare Worker runtime
    │  OTLP HTTP POST (logs only)

POST /api/v1/otlp/logs  (public, no session auth)
    │  validate Authorization: Bearer <OTLP_INGEST_SECRET>
    │  parse OTLP JSON payload
    │  filter: severityNumber >= 17 (ERROR/FATAL only)

worker_logs table (PostgreSQL)


GET /api/v1/protected/admin/worker-logs  (superadmin only)


WorkerLogsPage.tsx  (new superadmin tab)

Database Schema

New table worker_logs in the app Postgres schema:
ColumnTypeNotes
idtext PKUUID
timestamptimestampFrom OTLP timeUnixNano
severitytextERROR or FATAL
severity_numberintegerOTLP numeric (17–24)
messagetextbody.stringValue
trace_idtextOptional
resource_attrsjsonbWorker name, env, version
log_attrsjsonbURL, method, status code, etc.
received_attimestampWhen server received it
Indexes on 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 against OTLP_INGEST_SECRET env var; returns 401 if 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
Cloudflare dashboard config:
  • Destination type: Logs
  • OTLP endpoint: https://api.odontox.io/api/v1/otlp/logs
  • Custom header: AuthorizationBearer <OTLP_INGEST_SECRET value>

Superadmin API

Added to server/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)
  • severityERROR | FATAL | all
  • search — message text contains
Response: { 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

FileChange
server/src/schema/worker_logs.tsNew schema table
server/src/schema/index.tsExport new table
server/src/routes/otlp.tsNew OTLP ingest endpoint
server/src/api.tsRegister /otlp route + TTL cron hook
server/src/routes/admin.tsAdd GET /worker-logs
ui/src/lib/serverComm.tsAdd getWorkerLogs function
ui/src/components/superadmin/WorkerLogsPage.tsxNew tab component
ui/src/components/dashboards/SuperAdminDashboard.tsxRegister 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 verifySuperAdmin middleware (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.)