ADR 0016: Each site's logs go to its own R2 bucket, through Logpush where it can
Status
Accepted, 2026-10-31. Takes up the usage-reporting future work in ADR 0014.
Context
A Reference site’s activity is spread across its Workers (Front-end, Payload, MCP gateway), its Cloudflare account (dashboard and API changes, Access sign-ins, edge traffic) and, for WordPress, a host outside Cloudflare. Workers Logs keep seven days. A monitoring agent, or a weekly usage report, needs the logs kept longer and in one place the site owns.
Launch is public and ships the plumbing. What reads the logs and what it concludes (detection rules, alerting, dashboards) is a separate product and stays out of this repo. Logging is optional: many sites built on Launch will not turn it on.
Decision
- One R2 bucket per site holds every log, under
logpush/<dataset>/for Cloudflare’s logs andevents/<source>/for events sent in. - Workers use Logpush only. Each Worker emits one evlog wide event per request as a JSON console line (
@patronage/logging’swithRequestLog). Workers Trace Events Logpush, switched on withWORKER_LOGPUSH=trueat deploy, copies those lines to R2. Workers get no logging binding, no drain and no Basin. - Cloudflare’s own logs use Logpush jobs into the same bucket:
workers_trace_events,audit_logs_v2andaccess_requestsfor the account, andhttp_requestsandfirewall_eventsper zone where the plan allows. - WordPress on a host without Logpush uses Basin Pipelines. The
patronage-loggingmu-plugin spools security events and, optionally, the host’s log files, and a cron sends them to an authenticated Basin stream with a send-only token. The stream’s sink writes gzipped JSON into the same bucket. - Off unless configured. With no Logpush job, no
WORKER_LOGPUSHand no logging constants, every piece is a no-op apart from the console line Workers Logs already keep. Tests cover the unconfigured path. - Provisioned by script,
packages/logging/scripts/provision-logging.ts, through the Cloudflare API and idempotent by name (ADR 0003).
Consequences
- An evlog event reaches R2 as a string inside a Workers trace event’s
Logsarray, so whatever queries the bucket unpacks it. That is the cost of using no binding. - Logpush cannot backfill and drops logs while a job is failing. Watching for gaps belongs to the monitoring product, not to Launch.
- Which datasets a site gets depends on its Cloudflare plan; the script skips and reports the rest.
- The events carry client IPs and the usernames tried at login, because they are security records; a username that is an email address is stored as a short hash. They carry no request bodies, query strings, cookies, passwords, content or email addresses.
- Collectors for sources outside the CMS and Cloudflare (third-party audit APIs, jump hosts) are out of scope.
- A read-only
logsconnector for the MCP gateway is the next step, and the weekly usage report becomes a query over the same bucket.