Skip to main content
skill CX Codex

cloudflare-workers-observability — Claude Code skill: Workers logs

A skill for Cloudflare Workers observability: structured logging, Analytics Engine, Tail Workers, metrics and alerting. Useful when a worker fails in production and you can't see why.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add secondsky/claude-skills --skill cloudflare-workers-observability -a claude-code
Open on GitHub

What cloudflare-workers-observability does

The skill walks through observability for a worker end to end: from console.log up to Tail Workers, Analytics Engine, Logpush and trace events. It ships TypeScript templates — a logger factory that stamps every entry with a requestId, a Logger class with levels and redaction, Analytics Engine data points, and a tail() handler that consumes logs from other workers.

A table covers eight common failures: logs missing from the dashboard, messages cut off at 128KB, a Tail Worker that never fires because the binding name doesn't match wrangler.jsonc, writes to Analytics Engine that silently fail. The rules are blunt: emit JSON instead of plain text, attach request context to every entry, strip tokens and PII, sample high-volume request logs in production.

One thing to check first: logging only works if observability.enabled is set in wrangler.jsonc, along with head_sampling_rate. Skip that and the dashboard stays empty while you assume the worker is fine. The skill installs into six platforms, but the config and code samples target Workers specifically.

What it helps with

  • add structured logging to a worker with a requestId on every entry
  • set up a Tail Worker to collect production logs
  • write business metrics to Analytics Engine with writeDataPoint
  • redact tokens and PII before logs leave the worker
  • debug why logs never show up in the Cloudflare dashboard

Example prompts for the agent

  • add structured logging to my Cloudflare Worker with a requestId and token redaction
  • set up a Tail Worker that receives logs and filters out errors
  • record response time as a metric in Analytics Engine using the ANALYTICS binding

How to install cloudflare-workers-observability

1Skills CLI (Vercel)

npx skills add secondsky/claude-skills --skill cloudflare-workers-observability -a claude-code

2Manually: put the files in place

~/.claude/skills/cloudflare-workers-observability/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/cloudflare-workers-observability/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about cloudflare-workers-observability

How do I install cloudflare-workers-observability in Claude?

Run npx skills add secondsky/claude-skills --skill cloudflare-workers-observability -a claude-code. The skill file lands at ~/.claude/skills/cloudflare-workers-observability/SKILL.md.

What does cloudflare-workers-observability do?

It provides patterns and rules for Cloudflare Workers observability: structured logs, Analytics Engine metrics, Tail Workers, Logpush and tracing. It also helps when log parsing, metric aggregation or alert configuration breaks.

Why are my worker logs missing from the dashboard?

The skill points to a common cause: the observability block in wrangler.jsonc isn't enabled. Another is truncation at 128KB, which means large payloads need chunking or sampling.

Source text

SKILL.md · MIT
---
name: cloudflare-workers-observability
description: Cloudflare Workers observability with logging, Analytics Engine, Tail Workers, metrics, and alerting. Use for monitoring, debugging, tracing, or encountering log parsing, metric aggregation, alert configuration errors.
license: MIT
---

# Cloudflare Workers Observability

Production-grade observability for Cloudflare Workers: logging, metrics, tracing, and alerting.

## Quick Start

```typescript
// Structured logging with context
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const requestId = crypto.randomUUID();
    const logger = createLogger(requestId, env);

    try {
      logger.info('Request received', { method: request.method, url: request.url });

      const result = await handleRequest(request, env);

      logger.info('Request completed', { status: result.status });
      return result;
    } catch (error) {
      logger.error('Request failed', { error: error.message, stack: error.stack });
      throw error;
    }
  }
};

// Simple logger factory
function createLogger(requestId: string, env: Env) {
  return {
    info: (msg: string, data?: object) => console.log(JSON.stringify({ level: 'info', requestId, msg, ...data, timestamp: Date.now() })),
    error: (msg: string, data?: object) => console.error(JSON.stringify({ level: 'error', requestId, msg, ...data, timestamp: Date.now() })),
    warn: (msg: string, data?: object) => console.warn(JSON.stringify({ level: 'warn', requestId, msg, ...data, timestamp: Date.now() })),
  };
}
```

## Critical Rules

1. **Always use structured JSON logging** - Plain text logs are hard to parse and aggregate
2. **Include request context** - Request ID, method, path in every log entry
3. **Never log sensitive data** - Redact tokens, passwords, PII from logs
4. **Use appropriate log levels** - ERROR for failures, WARN for recoverable issues, INFO for operations
5. **Sample high-volume logs** - Use 1-10% sampling for request logs in production

## Observability Components

| Component | Purpose | When to Use |
|-----------|---------|-------------|
| `console.log` | Basic logging | Development, debugging |
| **Tail Workers** | Real-time log streaming | Production log aggregation |
| **Analytics Engine** | Custom metrics/analytics | Business metrics, performance tracking |
| **Logpush** | Log export to external services | Long-term storage, compliance |
| **Workers Trace Events** | Distributed tracing | Request flow debugging |

## Top 8 Errors Prevented

| Error | Symptom | Prevention |
|-------|---------|------------|
| Logs not appearing | No output in dashboard | Enable "Standard" logging in wrangler.jsonc |
| Log truncation | Messages cut off at 128KB | Chunk large payloads, use sampling |
| Tail Worker not receiving | No events processed | Check binding name matches wrangler.jsonc |
| Analytics Engine write fails | Data not recorded | Verify AE binding, check blobs format |
| PII in logs | Security/compliance violation | Implement redaction middleware |
| Missing request context | Can't correlate logs | Add requestId to all log entries |
| Log volume explosion | High costs, noise | Implement sampling for high-frequency events |
| Alerting gaps | Incidents not detected | Configure monitors for error rate thresholds |

## Logging Configuration

**wrangler.jsonc**:
```jsonc
{
  "name": "my-worker",
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1 // 0-1, 1 = 100% of requests
  },
  "tail_consumers": [
    {
      "service": "log-aggregator", // Tail Worker name
      "environment": "production"
    }
  ],
  "analytics_engine_datasets": [
    {
      "binding": "ANALYTICS",
      "dataset": "my_worker_metrics"
    }
  ]
}
```

## Structured Logging Pattern

```typescript
interface LogEntry {
  level: 'debug' | 'info' | 'warn' | 'error';
  message: string;
  requestId: string;
  timestamp: number;
  // Contextual data
  method?: string;
  path?: string;
  status?: number;
  duration?: number;
  // Error details
  error?: {
    name: string;
    message: string;
    stack?: string;
  };
  // Custom fields
  [key: string]: unknown;
}

class Logger {
  constructor(private requestId: string, private baseContext: object = {}) {}

  private log(level: LogEntry['level'], message: string, data?: object) {
    const entry: LogEntry = {
      level,
      message,
      requestId: this.requestId,
      timestamp: Date.now(),
      ...this.baseContext,
      ...data,
    };

    // Redact sensitive fields
    const sanitized = this.redact(entry);

    const output = JSON.stringify(sanitized);
    level === 'error' ? console.error(output) : console.log(output);
  }

  private redact(entry: LogEntry): LogEntry {
    const sensitiveKeys = ['password', 'token', 'secret', 'authorization', 'cookie'];
    const redacted = { ...entry };

    for (const key of Object.keys(redacted)) {
      if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
        redacted[key] = '[REDACTED]';
      }
    }
    return redacted;
  }

  info(message: string, data?: object) { this.log('info', message, data); }
  warn(message: string, data?: object) { this.log('warn', message, data); }
  error(message: string, data?: object) { this.log('error', message, data); }
  debug(message: string, data?: object) { this.log('debug', message, data); }
}
```

## Analytics Engine Usage

```typescript
interface Env {
  ANALYTICS: AnalyticsEngineDataset;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const start = Date.now();
    const url = new URL(request.url);

    try {
      const response = await handleRequest(request, env);

      // Write success metric
      env.ANALYTICS.writeDataPoint({
        blobs: [request.method, url.pathname, String(response.status)],
        doubles: [Date.now() - start], // Response time in ms
        indexes: [url.pathname.split('/')[1] || 'root'], // Index for fast queries
      });

      return response;
    } catch (error) {
      // Write error metric
      env.ANALYTICS.writeDataPoint({
        blobs: [request.method, url.pathname, 'error', error.message],
        doubles: [Date.now() - start],
        indexes: ['error'],
      });
      throw error;
    }
  }
};
```

## Tail Worker Pattern

```typescript
// tail-worker.ts - Receives logs from other workers
interface TailEvent {
  scriptName: string;
  event: {
    request?: { method: string; url: string };
    response?: { status: number };
  };
  logs: Array<{
    level: string;
    message: unknown[];
    timestamp: number;
  }>;
  exceptions: Array<{
    name: string;
    message: string;
    timestamp: number;
  }>;
  outcome: 'ok' | 'exception' | 'exceededCpu' | 'exceededMemory' | 'canceled';
  eventTimestamp: number;
}

export default {
  async tail(events: TailEvent[], env: Env): Promise<void> {
    for (const event of events) {
      // Filter and forward logs
      const errorLogs = event.logs.filter(l => l.level === 'error');
      const exceptions = event.exceptions;

      if (errorLogs.length > 0 || exceptions.length > 0) {
        // Send to external logging service
        await fetch(env.LOGGING_ENDPOINT, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            scriptName: event.scriptName,
            timestamp: event.eventTimestamp,
            errors: errorLogs,
            exceptions,
            outcome: event.outcome,
          }),
        });
      }
    }
  }
};
```

## When to Load References

Load specific references based on the task:

- **Setting up logging?** → Load `references/logging.md` for structured logging patterns, log levels, redaction
- **Building custom metrics?** → Load `references/analytics-engine.md` for Analytics Engine SQL queries, data modeling
- **Implementing log aggregation?** → Load `references/tail-workers.md` for Tail Worker patterns, external service integration
- **Creating dashboards/tracking?** → Load `references/custom-metrics.md` for business metrics, performance tracking
- **Setting up alerts?** → Load `references/alerting.md` for error rate monitoring, PagerDuty/Slack integration

## Templates

| Template | Purpose | Use When |
|----------|---------|----------|
| `templates/logging-setup.ts` | Production logging class | Setting up new worker with logging |
| `templates/analytics-worker.ts` | Analytics Engine integration | Adding custom metrics |
| `templates/tail-worker.ts` | Complete Tail Worker | Building log aggregation pipeline |

## Scripts

| Script | Purpose | Command |
|--------|---------|---------|
| `scripts/setup-logging.sh` | Configure logging settings | `./setup-logging.sh` |
| `scripts/analyze-logs.sh` | Query and analyze logs | `./analyze-logs.sh --errors --last 1h` |

## Resources

- Workers Observability: https://developers.cloudflare.com/workers/observability/
- Analytics Engine: https://developers.cloudflare.com/analytics/analytics-engine/
- Tail Workers: https://developers.cloudflare.com/workers/observability/tail-workers/
- Logpush: https://developers.cloudflare.com/logs/get-started/

© secondsky · MIT · secondsky/claude-skills

Similar skills

Everything for DevOps and infrastructure →

From the same set89

maz-ui This skill teaches your agent Maz-UI v4: 50+ components, composables and directives for Vue 3 and Nuxt 3. Handy when you build forms, dialogs or data tables. CX Codex +1 369installs woocommerce-dev-cycle A skill that walks you through the WooCommerce dev cycle: run tests, fix code style, sort out linting errors. CX Codex +1 361installs woocommerce-copy-guidelines A skill that sets the rules for user-facing text in WooCommerce: button labels, field names, messages and docs. Useful when more than one person writes the storefront strings. CX Codex +1 363installs cloudflare-workers-testing A skill for writing Cloudflare Workers tests with Vitest and real D1, KV, R2 and Durable Objects bindings. Useful when tests break on mocks or can't see the environment. CX Codex +1 325installs nuxt-seo A skill covering the eight Nuxt SEO v5 modules: robots.txt, sitemaps, OG images, Schema.org and meta tags. Useful when a Nuxt app needs SEO wired up. CX Codex +1 324installs cloudflare-workers-performance A Claude skill for Cloudflare Workers performance: CPU, memory, caching, bundle size. Useful when a worker is slow or hitting limits. CX Codex +1 315installs cloudflare-workers-runtime-apis A skill covering the Cloudflare Workers runtime APIs: Fetch, Streams, Crypto, Cache, WebSockets and text encoding. Handy when a worker trips over requests, streams or cache. CX Codex +1 311installs cloudflare-workers-frameworks Tells your agent how to run Hono, Remix, Next.js, Astro, SvelteKit, Qwik or Nuxt on Cloudflare Workers, and walks through the usual build errors. CX Codex +1 309installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor