Query the CF Workers Observability API to retrieve structured logs from any Workers, Durable Objects, Workflows, Queues, and Cron Triggers in your Cloudflare account.
Set these environment variables (e.g. in your project's .env or shell profile):
CF_OBSERVABILITY_ACCOUNT_ID — your Cloudflare account IDCF_OBSERVABILITY_API_TOKEN — API token with Workers Observability read permissiontext
POST https://api.cloudflare.com/client/v4/accounts/{accountId}/workers/observability/telemetry/query
Authorization: Bearer {apiToken}
Content-Type: application/json
json
{
"queryId": "cc-{timestamp}",
"timeframe": {
"from": "<unix_ms_start>",
"to": "<unix_ms_end>"
},
"view": "events",
"limit": 50,
"parameters": {
"filters": [
{"key": "<field>", "operation": "<op>", "type": "<type>", "value": "<value>"}
],
"filterCombination": "and",
"calculations": [],
"groupBys": [],
"needle": {"value": "<search_text>", "isRegex": false, "matchCase": false},
"limit": 50
}
}
eq, neq, includes, doesNotInclude, startsWith, regex, exists, doesNotExisteq, neq, gt, gte, lt, lte, exists, doesNotExistFieldTypeDescription$workers.scriptNamestringWorker script name$workers.outcomestringok / exception``$workers.entrypointstringEntrypoint class (Worker, DO, Workflow)$workers.eventTypestringfetch, rpc, queue, scheduled, alarm``msgstringLog messagelevelstringLog level ( log, info, warn, error)errorstringError messagestatusnumberHTTP status code
Any custom fields logged via console.log({ key: value }) are also queryable as top-level fields.
Use needle.value for free-text search across all fields. Useful when you don't know which field contains the value.
Use Bash with curl to call the API. Do NOT use WebFetch (it processes through an AI model and loses structure).
Read CF_OBSERVABILITY_ACCOUNT_ID and CF_OBSERVABILITY_API_TOKEN from environment variables. If not set in the shell, search for them in project .env files:
bash
grep -r 'CF_OBSERVABILITY_' --include='.env' --include='.env.*' . 2>/dev/null
Construct the curl command based on the user's request. Default time range: last 1 hour. Default limit: 30.
Parse the JSON response and format as a timeline:
text
{timestamp} [{level}] [{scriptName}/{entrypoint}] {msg}
{extra fields if present: error=, status=, eventType=}
Events are in result.events.events[]. Each event has:
source: structured log fields (msg, level, plus any custom fields)$workers: Worker metadata (scriptName, outcome, eventType, entrypoint)$metadata: system metadata (timestamp, requestId)timestamp: event timestamp in unix msSort events by timestamp ascending for chronological view.
json
{"filters": [{"key": "$workers.scriptName", "operation": "eq", "type": "string", "value": "my-worker"}]}
json
{"filters": [{"key": "level", "operation": "eq", "type": "string", "value": "error"}]}
json
{"filters": [{"key": "$workers.entrypoint", "operation": "eq", "type": "string", "value": "MyDurableObject"}]}
json
{"filters": [{"key": "$workers.eventType", "operation": "eq", "type": "string", "value": "alarm"}]}
json
{"filters": [{"key": "$workers.outcome", "operation": "eq", "type": "string", "value": "exception"}]}
json
{"filters": [{"key": "userId", "operation": "eq", "type": "string", "value": "user_123"}]}
json
{"needle": {"value": "search text here", "isRegex": false, "matchCase": false}}
json
{
"filters": [
{"key": "$workers.scriptName", "operation": "eq", "type": "string", "value": "my-worker"},
{"key": "level", "operation": "eq", "type": "string", "value": "error"}
],
"filterCombination": "and"
}
When invoked as /cf-workers-logs, parse $ARGUMENTS for:
worker=my-worker → filter by $workers.scriptNamelevel=error → filter by levelentrypoint=MyDO → filter by $workers.entrypointevent=alarm → filter by $workers.eventTypesearch=xxx → needle search<key>=<value> → filter by custom fieldlast=1h / last=30m / last=24h → time range (default: 1h)limit=N → result limit (default: 30)Multiple arguments can be combined: /cf-workers-logs worker=my-api level=error last=24h