workers-observability

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Workers Observability

Cloudflare Workers 可观测性

Production-grade observability for Cloudflare Workers: logging, metrics, tracing, and alerting.
面向Cloudflare Workers的生产级可观测性方案:包含日志、指标、追踪与告警功能。

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() })),
  };
}
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
  1. 始终使用结构化JSON日志 - 纯文本日志难以解析和聚合
  2. 包含请求上下文 - 每条日志条目都要包含请求ID、方法、路径
  3. 切勿记录敏感数据 - 从日志中屏蔽令牌、密码、个人可识别信息(PII)
  4. 使用合适的日志级别 - ERROR用于故障场景,WARN用于可恢复问题,INFO用于常规操作
  5. 对高流量日志进行采样 - 生产环境中对请求日志采用1-10%的采样率

Observability Components

可观测性组件

ComponentPurposeWhen to Use
console.log
Basic 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
组件用途使用场景
console.log
基础日志记录开发、调试阶段
Tail Workers实时日志流生产环境日志聚合
Analytics Engine自定义指标/分析业务指标统计、性能追踪
Logpush日志导出至外部服务长期存储、合规需求
Workers Trace Events分布式追踪请求链路调试

Top 8 Errors Prevented

可预防的8类常见错误

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
错误类型症状预防措施
日志不显示控制台无输出在wrangler.jsonc中启用「Standard」日志模式
日志截断消息在128KB处被截断拆分大 payload,使用采样机制
Tail Worker 未接收日志无事件被处理检查绑定名称与wrangler.jsonc中的配置一致
Analytics Engine 写入失败数据未被记录验证AE绑定配置,检查Blob格式
日志中包含PII违反安全/合规要求实现数据屏蔽中间件
缺少请求上下文无法关联日志为所有日志条目添加requestId
日志量爆炸成本过高、噪音大对高频事件实现采样机制
告警覆盖不全未检测到故障配置错误率阈值监控器

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"
    }
  ]
}
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); }
}
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

Analytics Engine 使用示例

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;
    }
  }
};
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

Tail Worker 模式

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,
          }),
        });
      }
    }
  }
};
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
根据任务需求加载对应参考文档:
  • 配置日志? → 加载
    references/logging.md
    获取结构化日志模式、日志级别、数据屏蔽相关内容
  • 构建自定义指标? → 加载
    references/analytics-engine.md
    获取Analytics Engine SQL查询、数据建模相关内容
  • 实现日志聚合? → 加载
    references/tail-workers.md
    获取Tail Worker模式、外部服务集成相关内容
  • 创建仪表盘/追踪? → 加载
    references/custom-metrics.md
    获取业务指标、性能追踪相关内容
  • 配置告警? → 加载
    references/alerting.md
    获取错误率监控、PagerDuty/Slack集成相关内容

Templates

模板

TemplatePurposeUse When
templates/logging-setup.ts
Production logging classSetting up new worker with logging
templates/analytics-worker.ts
Analytics Engine integrationAdding custom metrics
templates/tail-worker.ts
Complete Tail WorkerBuilding log aggregation pipeline
模板用途使用场景
templates/logging-setup.ts
生产级日志类为新Worker配置日志功能
templates/analytics-worker.ts
Analytics Engine 集成添加自定义指标
templates/tail-worker.ts
完整Tail Worker实现构建日志聚合流水线

Scripts

脚本

ScriptPurposeCommand
scripts/setup-logging.sh
Configure logging settings
./setup-logging.sh
scripts/analyze-logs.sh
Query and analyze logs
./analyze-logs.sh --errors --last 1h
脚本用途命令
scripts/setup-logging.sh
配置日志设置
./setup-logging.sh
scripts/analyze-logs.sh
查询与分析日志
./analyze-logs.sh --errors --last 1h

Resources

资源链接