Observability and audit

Every signal the platform records, what each is good for, and exactly how long it is kept — request logs, sampled metrics, agent traces, deployment records, and your app's own business event feed.

The Spikefrost Team30 Jul 20265 min read

Five independent signals, with different fidelity and different retention. Choosing the right one for a question is most of the skill.

Five signals. Fidelity decreases left to right; only your own event feed is under your control and retained as long as you keep it.

Your app
+ its agents

Request logs
exact · 14d bodies (opt-in)

Analytics
SAMPLED · 3 months

Agent traces
per turn · tools · tokens

Your event feed
D1 — you own retention

Every deploy

Deployment records
per-resource events

Five signals. Fidelity decreases left to right; only your own event feed is under your control and retained as long as you keep it.

What to use for what

Question Signal Why
"What did this specific request return?" Request logs Exact, per request
"Is error rate rising?" Analytics Aggregate trend, cheap to query
"What happened to this customer's order?" Your event feed Business-level, in your own database
"Did the deploy apply everything?" Deployment records Per-resource outcomes
"Did the agent really check inventory?" Agent traces Tool-call edges don't lie
"Who changed this code?" Commits Attributed, including AI turns

Retention, precisely

Signal Retention Fidelity
Request logs (metadata) Platform-managed rolling window Exact per request
Request/response bodies 14 days, opt-in per app JSON and text only, secrets redacted
Analytics metrics 3 months, not configurable Sampled — weight by the sampling interval
Agent traces and turn costs Platform-managed Exact per turn
Deployment records Full history Exact
Commits and file versions Full history Exact
Your own event feed As long as you keep it Exactly what you declared

Two consequences to design around. Analytics is sampled and capped at three months, so it cannot be your audit trail or your billing source — Cloudflare does not guarantee retrieval of any individual record. And the only signal whose retention you control is your own event feed, which is why anything with a regulatory retention period belongs there.

Request logs

sf logs --since 6h --errors             # filter to failures
sf logs "status>=500 path:/api" --expand
sf logs --env staging --since 1h

Bodies are off by default. To capture them for a specific investigation:

sf firewall set --body-capture on
sf logs body <rayId>

Then turn it back off. Two caveats worth internalizing: a cache hit produces no log (it never ran your code), and log delivery lags live traffic by up to about five minutes because of batching — this is a debugging tool, not a real-time monitor.

Your own event feed — the one that matters for audit

Platform signals tell you what the infrastructure did. Business facts — an order accepted, a refund issued, a guardrail refusal — are yours to record, in your own database, with retention you control.

// app/events.ts — declare the vocabulary; emitting an undeclared type throws
export default defineEvents({
  "order.accepted": {
    audience: ["business"], severity: "info",
    title: "Order {orderId} accepted for {customer}",
    fields: z.object({ orderId: z.string(), customer: z.string() }),
  },
  "guardrail.refused": {
    audience: ["business"], severity: "warn",
    title: "Refused: {rule}",
    fields: z.object({ rule: z.string() }),
  },
});
// Emit from DOMAIN code, so the same event fires whichever caller acted
await emitAppEvent(env, "order.accepted", { orderId, customer }, { actor });

Events land in _spike_events in your app's database, indexed on time and type:

SELECT ts, type, severity, title, actor, fields
  FROM _spike_events
 WHERE ts > unixepoch('now', '-1 day') * 1000
 ORDER BY ts DESC;

The doctrine that makes this valuable: every write-path domain function emits its outcome, every scheduled job emits completion, every guardrail refusal emits. Then "what happened yesterday, and who did it?" is a query instead of an investigation — and because it's a table in your database, it inherits the 30-day point-in-time recovery and can be exported anywhere.

Two conventions: titles name domain objects rather than people (a person goes in fields, so the feed doesn't read like surveillance), and event types should be things a colleague would say out loud.

Agent activity

Where AI acts on your data, the audit question is sharper: did it actually do what it says?

sf api get "/apps/<appId>/agent-turns?limit=100"     # calls, tokens, cost per turn
sf api get "/apps/<appId>/agent-trace?agent=<route>&session=<sid>&traceId=<tid>&format=mermaid&detail=1"
sf api get "/apps/<appId>/agent-describe"            # what each deployed agent may do
sf api get "/apps/<appId>/agent-messages?agent=<route>&session=<sid>"

Every tool call, delegation, and model step is an edge with duration and token counts, recorded without instrumentation. The habit worth institutionalizing:

Verify claims against trace edges, not against the agent's summary. If an agent says it checked inventory and there is no corresponding tool edge, it produced a plausible sentence.

agent-describe answers "what is this agent permitted to do?" from the deployed code rather than from a registry that can drift — because the deployed code is the registry.

Metrics

Opt in, then write from your Worker and query with SQL:

sf analytics enable && sf deploy      # the binding attaches on the NEXT deploy
sf analytics query "SELECT index1 AS route, sum(_sample_interval) AS hits
                      FROM ANALYTICS
                     WHERE timestamp > now() - INTERVAL '1' DAY
                     GROUP BY route"

Always weight aggregates by _sample_interval — a bare count() silently undercounts once sampling begins. Keep queries time-bounded and index-filtered.

Gaps, stated plainly

  • No built-in SIEM or log streaming. Export from your own event table on a schedule if you need it.
  • No synthetic monitoring or alerting built in. Point your existing monitoring at your app's URL and a health route.
  • Analytics cannot be an audit trail — sampled, three months, no per-record guarantee.
  • Logs lag by minutes. For real-time alerting, monitor externally.

If any of these blocks a control you must satisfy, raise it early.

Next

Frequently asked questions

Are request and response bodies logged?

Not by default — that's a deliberate privacy default. You can opt in per app, after which bodies are retained for 14 days, limited to JSON and text, with secrets redacted.

Can we rely on the metrics for billing or audit?

No. Analytics data is sampled and Cloudflare does not guarantee retrieval of any individual record. Use it for trends and rates; use request logs for exact events and your app's own event table for business facts.

Why are there no logs for a request we know happened?

Almost certainly a cache hit. A response served from the edge cache never runs your code, so there is nothing to log. Check the x-sf-cache header on the same route.

How do we prove what an AI agent did?

Every turn is traced automatically — each tool call, delegation, and model step is an edge with duration and token counts — and every code change it made is an attributed commit. Verify claims against trace edges, not against the agent's summary.

Can we export logs to our own SIEM?

Not as a built-in integration today. What you can do is record business-level events into your app's own database and export from there on a schedule. Tell us if SIEM streaming is a requirement.