# Observability

> Add production observability to Cloudflare Workers apps with structured logs, request IDs, metrics, traces, Durable Object/Queue/Workflow visibility, error handling, and incident debugging. Use before deploying or debugging Cloudflare applications.

- Skill: `zllovesuki/observability` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zllovesuki/observability`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zllovesuki/observability/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: zllovesuki (https://skillmd.com/u/zllovesuki)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/zllovesuki/observability

---

# Observability

Use this skill for logging, metrics, traces, and incident debugging in Cloudflare applications.

## Observability stance

- Edge systems are ephemeral; design logs and metrics as the primary debugging evidence.
- Add correlation IDs at the Worker boundary and pass them through bindings, queues, workflows, and external calls.
- Log structured events, not unparseable strings.
- Redact secrets and minimize PII.
- Observe every asynchronous boundary: `waitUntil`, Queues, Workflows, Durable Objects, AI calls, and container calls.

## Request ID middleware pattern

```ts
export function getRequestId(request: Request) {
  return request.headers.get("cf-ray")
    ?? request.headers.get("x-request-id")
    ?? crypto.randomUUID();
}

export function log(event: string, fields: Record<string, unknown>) {
  console.log(JSON.stringify({ event, ...fields }));
}
```

## Worker handler pattern

```ts
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const requestId = getRequestId(request);
    const started = Date.now();

    try {
      const response = await route(request, env, ctx, requestId);
      log("request.complete", {
        requestId,
        status: response.status,
        durationMs: Date.now() - started
      });
      return response;
    } catch (error) {
      log("request.error", {
        requestId,
        durationMs: Date.now() - started,
        error: error instanceof Error ? error.message : String(error)
      });
      return Response.json({ error: "internal_error", requestId }, { status: 500 });
    }
  }
} satisfies ExportedHandler<Env>;
```

## What to log by primitive

- Workers: route, status, duration, request ID, tenant ID, user ID hash, cache outcome.
- Durable Objects: object key, method, queue length/backpressure signal, storage operation summary, WebSocket counts.
- D1: query class/name, row counts, duration, not raw user data.
- R2: key prefix/category, operation, bytes, duration.
- KV: key namespace/category, hit/miss, TTL class.
- Queues: queue name, message ID/job ID, retry count, ack/retry/failure.
- Workflows: instance ID, step name, retry count, status.
- AI: model, prompt class, tokens where available, duration, fallback, refusal/abstention.

## Error response rules

- Include request ID in user-visible errors.
- Never expose stack traces or secrets.
- Map validation/auth errors to 4xx; unknown application failures to 500.
- Use safe error messages and log detailed internal messages.

## Incident checklist

- Can you identify affected tenants/users?
- Can you follow one request through Worker -> DO/Queue/Workflow -> storage/AI?
- Are retries amplifying the incident?
- Is there a hot Durable Object or hot D1 query?
- Are external APIs failing or slow?
- Is an AI model/provider unavailable or returning malformed output?

## Anti-patterns

- Logs only say `failed` without request/job IDs.
- Logging full prompts, secrets, tokens, or uploaded file contents.
- No visibility into background work after the initial HTTP 202.
- Treating local debugging as enough for production edge behavior.

