mcpField
W
workers-observability

workers-observability

github.com/secondskyCloud & DevOps24081 installsUpdated 5w ago
Repository

About workers-observability

A skill for implementing observability in Cloudflare Workers, covering logs, metrics, tracing, and alerting. It provides configuration guidance, reusable patterns, templates, and scripts for production monitoring.

Key features

  1. 01Structured JSON logging patterns
  2. 02Tail Workers log streaming
  3. 03Analytics Engine metrics
  4. 04Logpush and trace event guidance
  5. 05Alerting and log analysis resources

Use cases

  1. 01Adding request context to logs
  2. 02Aggregating Worker logs
  3. 03Recording custom performance metrics
  4. 04Monitoring error rates and incidents

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/secondsky/claude-skills --skill workers-observability

After installing, type/workers-observabilityin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
alerting.md
analytics-engine.md
custom-metrics.md
logging.md
tail-workers.md
scripts/
analyze-logs.sh
setup-logging.sh
templates/
analytics-worker.ts
logging-setup.ts
tail-worker.ts

Details

From the project README

Cloudflare Workers Observability

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

Quick Start

// 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

ComponentPurposeWhen to Use
console.logBasic loggingDevelopment, debugging
Tail WorkersReal-time log streamingProduction log aggregation
Analytics EngineCustom metrics/analyticsBusiness metrics, performance tracking
LogpushLog export to external servicesLong-term storage, compliance
Workers Trace EventsDistributed tracingRequest flow debugging

Top 8 Errors Prevented

ErrorSymptomPrevention
Logs not appearingNo output in dashboardEnable "Standard" logging in wrangler.jsonc
Log truncationMessages cut off at 128KBChunk large payloads, use sampling
Tail Worker not receivingNo events processedCheck binding name matches wrangler.jsonc
Analytics Engine write failsData not recordedVerify AE binding, check blobs format
PII in logsSecurity/compliance violationImplement redaction middleware
Missing request contextCan't correlate logsAdd requestId to all log entries
Log volume explosionHigh costs, noiseImplement sampling for high-frequency events
Alerting gapsIncidents not detectedConfigure monitors for error rate thresholds

Logging Configuration

wrangler.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

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

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

// 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

TemplatePurposeUse When
templates/logging-setup.tsProduction logging classSetting up new worker with logging
templates/analytics-worker.tsAnalytics Engine integrationAdding custom metrics
templates/tail-worker.tsComplete Tail WorkerBuilding log aggregation pipeline

Scripts

ScriptPurposeCommand
scripts/setup-logging.shConfigure logging settings./setup-logging.sh
scripts/analyze-logs.shQuery and analyze logs./analyze-logs.sh --errors --last 1h

Resources

Comments

Markdown supported