Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/tail-workers/api.md
1# Tail Workers API Reference23## Handler Signature45```typescript6export default {7async tail(8events: TraceItem[],9env: Env,10ctx: ExecutionContext11): Promise<void> {12// Process events13}14} satisfies ExportedHandler<Env>;15```1617**Parameters:**18- `events`: Array of `TraceItem` objects (one per producer invocation)19- `env`: Bindings (KV, D1, R2, env vars, etc.)20- `ctx`: Context with `waitUntil()` for async work2122**CRITICAL:** Tail handlers don't return values. Use `ctx.waitUntil()` for async operations.2324## TraceItem Type2526```typescript27interface TraceItem {28scriptName: string; // Producer Worker name29eventTimestamp: number; // Epoch milliseconds30outcome: 'ok' | 'exception' | 'exceededCpu' | 'exceededMemory'31| 'canceled' | 'scriptNotFound' | 'responseStreamDisconnected' | 'unknown';3233event?: {34request?: {35url: string; // Redacted by default36method: string;37headers: Record<string, string>; // Sensitive headers redacted38cf?: IncomingRequestCfProperties;39getUnredacted(): TraceRequest; // Bypass redaction (use carefully)40};41response?: {42status: number;43};44};4546logs: Array<{47timestamp: number; // Epoch milliseconds48level: 'debug' | 'info' | 'log' | 'warn' | 'error';49message: unknown[]; // Args passed to console function50}>;5152exceptions: Array<{53timestamp: number; // Epoch milliseconds54name: string; // Error type (Error, TypeError, etc.)55message: string; // Error description56}>;5758diagnosticsChannelEvents: Array<{59channel: string;60message: unknown;61timestamp: number; // Epoch milliseconds62}>;63}64```6566**Note:** Official SDK uses `TraceItem`, not `TailItem`. Use `@cloudflare/workers-types` for accurate types.6768## Timestamp Handling6970All timestamps are **epoch milliseconds**, not seconds:7172```typescript73// ✅ CORRECT - use directly with Date74const date = new Date(event.eventTimestamp);7576// ❌ WRONG - don't multiply by 100077const date = new Date(event.eventTimestamp * 1000);78```7980## Automatic Redaction8182By default, sensitive data is redacted from `TraceRequest`:8384### Header Redaction8586Headers containing these substrings (case-insensitive):87- `auth`, `key`, `secret`, `token`, `jwt`88- `cookie`, `set-cookie`8990Redacted values show as `"REDACTED"`.9192### URL Redaction9394- **Hex IDs:** 32+ hex digits → `"REDACTED"`95- **Base-64 IDs:** 21+ chars with 2+ upper, 2+ lower, 2+ digits → `"REDACTED"`9697## Bypassing Redaction9899```typescript100export default {101async tail(events, env, ctx) {102for (const event of events) {103// ⚠️ Use with extreme caution104const unredacted = event.event?.request?.getUnredacted();105// unredacted.url and unredacted.headers contain raw values106}107}108};109```110111**Best practices:**112- Only call `getUnredacted()` when absolutely necessary113- Never log unredacted sensitive data114- Implement additional filtering before external transmission115- Use environment variables for API keys, never hardcode116117## Type-Safe Handler118119```typescript120interface Env {121LOGS_KV: KVNamespace;122ANALYTICS: AnalyticsEngineDataset;123LOG_ENDPOINT: string;124API_TOKEN: string;125}126127export default {128async tail(129events: TraceItem[],130env: Env,131ctx: ExecutionContext132): Promise<void> {133const payload = events.map(event => ({134script: event.scriptName,135timestamp: event.eventTimestamp,136outcome: event.outcome,137url: event.event?.request?.url,138status: event.event?.response?.status,139}));140141ctx.waitUntil(142fetch(env.LOG_ENDPOINT, {143method: "POST",144headers: { "Content-Type": "application/json" },145body: JSON.stringify(payload),146})147);148}149} satisfies ExportedHandler<Env>;150```151152## Outcome vs HTTP Status153154**IMPORTANT:** `outcome` is script execution status, NOT HTTP status.155156- Worker returns 500 → `outcome='ok'` if script completed successfully157- Uncaught exception → `outcome='exception'` regardless of HTTP status158- CPU limit exceeded → `outcome='exceededCpu'`159160```typescript161// ✅ Check outcome for script execution status162if (event.outcome === 'exception') {163// Script threw uncaught exception164}165166// ✅ Check HTTP status separately167if (event.event?.response?.status === 500) {168// HTTP 500 returned (script may have handled error)169}170```171172## Serialization Considerations173174`log.message` is `unknown[]` and may contain non-serializable objects:175176```typescript177// ❌ May fail with circular references or BigInt178JSON.stringify(events);179180// ✅ Safe serialization181const safePayload = events.map(event => ({182...event,183logs: event.logs.map(log => ({184...log,185message: log.message.map(m => {186try {187return JSON.parse(JSON.stringify(m));188} catch {189return String(m);190}191})192}))193}));194```195196**Common serialization issues:**197- Circular references in logged objects198- `BigInt` values (not JSON-serializable)199- Functions or symbols in console.log arguments200- Large objects exceeding body size limits201