Workers Logs is for last week; wrangler tail is for right now

observability.enabled stores every invocation's logs for 7 days, queryable by field; head_sampling_rate is the cost dial. Log JSON objects, use console.error.

Observability

· Chapter

44

·

3

min read

The answer. wrangler tail streams whatever the Worker prints while you watch, sampled under load, gone when you close the terminal. Workers Logs is the durable one: with observability.enabled = true (on by default for new Workers) every invocation's console.* lines, exceptions and metadata (status, CPU time, wall time, version id, cron expression) are stored for seven days and searchable in the dashboard or through the observability API. Two habits make it useful. Log one JSON object per event so fields become filters ("show me every article.publish with status >= 500"), and use console.error and console.warn so severity is correct. On a busy Worker, head_sampling_rate below 1 keeps a fraction of requests, all of whose logs are kept together.

The pattern.

{ "observability": { "enabled": true,
    "logs":   { "invocation_logs": true, "head_sampling_rate": 1 },
    "traces": { "enabled": true, "head_sampling_rate": 0.05 } } }
console.log(JSON.stringify({ event: "article.publish", site: siteId, ms: Date.now() - t0, status: 200 }));
console.error(JSON.stringify({ event: "article.publish", site: siteId, error: String(err) }));
npx wrangler tail --env production --status error --format pretty     # live, filtered

Watch out.

  • Error trackers that wrap fetch see nothing from scheduled() or queue(); log there explicitly or wrap those handlers too.
  • Logs over 256 KB are truncated. Never log a whole request body or an LLM response.
  • Seven days is the retention. For longer, export with OpenTelemetry (recommended) or Logpush; a Tail Worker still runs when your Worker throws.

Related: cron-heartbeat-dead-mans-switch · gradual-deployments-split-traffic-between-two-versions · analytics-engine-cookieless-first-party-telemetry