Logging
Wide events from each Worker and security events from WordPress, kept as files in one R2 bucket per site through Logpush and Basin Pipelines.
Launch keeps a site’s logs as files in one R2 bucket, the site’s log bucket. Workers send theirs through Logpush, and WordPress on a host without Logpush sends its own through a Basin Pipelines stream. Logging is optional: with nothing configured, nothing is sent or stored, and nothing slows down.
Launch provides the plumbing: the event shapes, the Worker wrapper, the WordPress plugin and a provisioning script. Whatever reads the logs and decides what they mean, such as detection, alerting or dashboards, is out of scope.
What lands in the bucket
| Source | How it gets there | Path |
|---|---|---|
| Each request to a Launch Worker | Workers Trace Events Logpush | logpush/workers_trace_events/<date>/ |
| Cloudflare audit logs and Access logins | Account Logpush jobs | logpush/audit_logs_v2/<date>/, logpush/access_requests/<date>/ |
| WordPress security events and the host’s log files | Basin Pipelines stream | events/wordpress/year=/month=/day=/ |
Everything is gzipped JSON, one record per line. The files can be queried in place with a tool such as DuckDB.
Workers: one wide event per request
Each vinext app wraps its entry with withRequestLog from @patronage/logging. Every request then prints one JSON line with the service, method, path (without the query string), status, duration, ray ID, colo, country and ASN. Build assets are skipped. Errors and slow requests are always kept, even when you sample the healthy traffic.
Two fields help with performance:
cacheis vinext’s page cache result:HIT,STALEorMISS. Counting it over a day gives the cache hit rate.upstreamgives the calls, errors and total milliseconds for each upstream the request waited on before its response started:upstream.wordpressfor WPGraphQL and media reads, andupstream.d1for Payload’s queries.
That line goes wherever the Worker’s console output goes. To keep it, build the Worker with WORKER_LOGPUSH=true, which turns on Logpush in its cloudflare.config.ts, and give the account a workers_trace_events job that writes to the log bucket. Each event arrives inside the trace record’s Logs array.
No binding, token or extra request is involved, so the logging costs a Worker nothing at request time.
Traces and Issues
Launch’s Workers also turn on Cloudflare’s automatic tracing, Issues and query string redaction in their cloudflare.config.ts:
- Traces record a span for each subrequest and binding call, such as a fetch to WordPress or a KV cache read, so a slow request shows where its time went. Find them under the Worker’s Observability tab in the dashboard, kept for seven days. Every request is traced by default; set
WORKER_TRACE_SAMPLING(0 to 1) at build time to trace fewer on a busy site. - Issues groups exceptions and 5xx responses so repeated failures show up as one issue.
- Query string redaction keeps query strings, which can carry preview tokens, out of logs and traces.
Traces and logs are billed together under Cloudflare Observability pricing. A demo site’s traffic fits well inside the allowance that Workers Paid includes.
WordPress: security events and server logs
The patronage-logging mu-plugin ships with both WordPress installs and stays off until it is given an endpoint and a send-only token. Once on, it records one event for each request that did something worth keeping:
- logins, failed logins and logouts;
- user, role, plugin, theme and key option changes;
- application passwords created or used;
- content status changes;
- WPGraphQL mutations, admin and REST writes, and server errors.
Anonymous reads are skipped. Cloudflare already sees that traffic.
Requests never wait on the network. Events go to a spool file, and a server cron runs wp patronage-logging ship every five minutes. That command sends the spool, along with any new lines from the host’s log files. The token can only send, so someone who takes over the server can’t read or delete logs that were already shipped.
Turn it on for a site
-
Create the bucket and jobs. Run the provision script with an API token (see its README for the permissions it needs):
pnpm --filter @patronage/logging logging:provision --name <site> \ [--script <worker>]... [--no-account-logs] [--wordpress] [--zone <zone id>]It creates only what’s missing:
- the bucket;
- the account Logpush jobs for Worker traces, audit logs and Access logins (with
--script, the Worker job keeps only those Workers; with--no-account-logs, no audit or Access jobs); - with
--zone, that zone’s HTTP request and firewall jobs; - with
--wordpress, the Basin stream, sink and pipeline, plus a send-only token.
If you don’t pass an R2 key pair, it creates one that can write only to the bucket and prints it once. With
--wordpress, it also prints the two WordPress constants you’ll need in step 3. -
Turn on Logpush for each Worker. Set
WORKER_LOGPUSH=truein the Worker’s Workers Builds environment and redeploy. -
Turn on WordPress. Add
PATRONAGE_LOGGING_ENDPOINTandPATRONAGE_LOGGING_TOKEN(and optionallyPATRONAGE_LOGGING_FILES) to the server’swp-config.php. Then add the five-minute cron and check the setup withwp patronage-logging statusandwp patronage-logging test.
Without those two options, the jobs cover the whole account, which suits a site with its own Cloudflare account. On an account shared with other sites, pass each of the site’s Workers with --script and add --no-account-logs, so one site’s bucket doesn’t collect every Worker’s logs and the whole account’s audit trail. The Launch demo is set up that way.
Things to know
- The
cfCLI can’t create account Logpush jobs yet (cf logpush account-jobs createfails validation). The provision script calls the API directly, so it isn’t affected. - Basin wraps each record in a
valuekey. Unwrap it before reading the event. - On hosts where the cron user and the PHP-FPM user differ, as on Cloudways, the plugin’s spool folder is shared with their common group. That way, whichever runs first doesn’t lock out the other.
- Zone HTTP request and firewall logs need a plan that includes them. The script tries each job and reports any the plan refuses.
The decision is ADR 0016, and the full research is Wide-event logging and Logpush to R2.