
cloudflare-observability
by 2Rds
SKILL.md
name: Cloudflare Observability description: This skill should be used when the user asks to "debug a Worker", "view logs", "check analytics", "monitor performance", "trace errors", "setup logging", "wrangler tail", "check metrics", or needs to troubleshoot Cloudflare issues. Provides comprehensive observability and debugging guidance. version: 1.0.0
Cloudflare Observability
Monitoring, logging, analytics, and debugging for Cloudflare Workers and infrastructure.
Live Logging with Wrangler Tail
Basic Usage
# Tail production logs
wrangler tail
# Pretty formatted
wrangler tail --format=pretty
# Filter by status code
wrangler tail --status=error
# Search for specific text
wrangler tail --search="ERROR"
# Specific environment
wrangler tail --env=production
Console Logging in Workers
export default {
async fetch(request, env) {
console.log('Request received:', {
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers)
});
try {
const response = await handleRequest(request, env);
console.log('Response sent:', response.status);
return response;
} catch (error) {
console.error('Error occurred:', {
message: error.message,
stack: error.stack
});
throw error;
}
}
};
Workers Analytics
Dashboard Analytics
Access: Workers Dashboard → Your Worker → Analytics
Key Metrics:
- Requests: Total requests over time
- Errors: 4xx and 5xx responses
- CPU Time: Execution duration
- Status Codes: Breakdown of response codes
Analytics Engine (Custom Metrics)
Send custom metrics:
export default {
async fetch(request, env) {
const start = Date.now();
const response = await handleRequest(request, env);
// Send custom metrics
env.ANALYTICS.writeDataPoint({
doubles: [Date.now() - start], // Response time
blobs: [request.url, response.status.toString()]
});
return response;
}
};
Query metrics:
# Via GraphQL API
curl -X POST "https://api.cloudflare.com/client/v4/graphql" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-d '{
"query": "query { viewer { accounts(filter: {accountTag: \"$ACCOUNT_ID\"}) { analyticsEngineDatasets { ... } } } }"
}'
Logpush (Export Logs)
Setup Logpush to R2
Create Logpush job:
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/logpush/jobs" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-d '{
"destination_conf": "r2://{bucket}/{path}",
"dataset": "http_requests",
"logpull_options": "fields=ClientIP,ClientRequestHost,ClientRequestMethod,EdgeResponseStatus,RayID×tamps=rfc3339"
}'
Query Exported Logs
// Read logs from R2
export default {
async fetch(request, env) {
const { objects } = await env.LOGS_BUCKET.list({
prefix: '2024/01/15/'
});
for (const obj of objects) {
const log = await env.LOGS_BUCKET.get(obj.key);
const text = await log.text();
// Parse and analyze logs
}
}
};
Error Tracking
Structured Error Logging
async function logError(error, context, env) {
const errorLog = {
timestamp: new Date().toISOString(),
message: error.message,
stack: error.stack,
context: {
url: context.request?.url,
method: context.request?.method,
userAgent: context.request?.headers.get('User-Agent'),
ip: context.request?.headers.get('CF-Connecting-IP')
}
};
// Store in KV or D1
await env.ERRORS.put(
`error:${Date.now()}:${crypto.randomUUID()}`,
JSON.stringify(errorLog),
{ expirationTtl: 86400 * 7 } // Keep for 7 days
);
// Or send to external service (Sentry, etc.)
await fetch('https://your-error-tracker.com/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorLog)
});
}
export default {
async fetch(request, env, ctx) {
try {
return await handleRequest(request, env);
} catch (error) {
ctx.waitUntil(logError(error, { request }, env));
return new Response('Internal Server Error', { status: 500 });
}
}
};
Query Error Logs
// List recent errors
const { keys } = await env.ERRORS.list({ prefix: 'error:' });
// Parse and analyze
const errors = await Promise.all(
keys.map(async (key) => {
const data = await env.ERRORS.get(key.name);
return JSON.parse(data);
})
);
// Group by error type
const errorGroups = errors.reduce((acc, err) => {
acc[err.message] = (acc[err.message] || 0) + 1;
return acc;
}, {});
Performance Monitoring
CPU Time Tracking
export default {
async fetch(request, env) {
const start = Date.now();
const response = await handleRequest(request, env);
const duration = Date.now() - start;
// Log slow requests
if (duration > 100) {
console.warn('Slow request:', {
url: request.url,
duration: `${duration}ms`
});
}
return response;
}
};
Identify Bottlenecks
async function timed(name, fn) {
const start = Date.now();
const result = await fn();
console.log(`${name}: ${Date.now() - start}ms`);
return result;
}
export default {
async fetch(request, env) {
// Track each operation
const user = await timed('fetch-user', () =>
env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first()
);
const items = await timed('fetch-items', () =>
env.DB.prepare('SELECT * FROM items WHERE user_id = ?').bind(userId).all()
);
const response = await timed('render', () =>
renderTemplate({ user, items })
);
return new Response(response);
}
};
Request Tracing
Cloudflare Ray ID
Every request gets a unique Ray ID for tracing:
export default {
async fetch(request, env) {
const rayId = request.headers.get('CF-Ray');
console.log('Processing request:', rayId);
// Include in responses for debugging
return new Response('Success', {
headers: { 'CF-Ray': rayId }
});
}
};
Use Ray ID: Support tickets, debugging, correlating logs
Custom Trace IDs
export default {
async fetch(request, env) {
const traceId = request.headers.get('X-Trace-ID') || crypto.randomUUID();
console.log('Trace ID:', traceId);
// Pass to subrequests
const response = await fetch('https://api.example.com', {
headers: { 'X-Trace-ID': traceId }
});
return new Response(await response.text(), {
headers: { 'X-Trace-ID': traceId }
});
}
};
Debugging Techniques
Local Development
# Start dev server with debugging
wrangler dev --local
# Use browser DevTools
# Set breakpoints in Workers code
# Inspect variables and requests
Remote Debugging
// Debug flag in production
export default {
async fetch(request, env) {
const url = new URL(request.url);
const debug = url.searchParams.get('debug') === 'true';
if (debug) {
return new Response(JSON.stringify({
env: Object.keys(env),
headers: Object.fromEntries(request.headers),
url: request.url,
method: request.method
}), {
headers: { 'Content-Type': 'application/json' }
});
}
return handleRequest(request, env);
}
};
Health Check Endpoint
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/health') {
// Check all dependencies
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
checks: {}
};
// Check KV
try {
await env.KV.get('health-check');
health.checks.kv = 'ok';
} catch (e) {
health.checks.kv = 'error';
health.status = 'degraded';
}
// Check D1
try {
await env.DB.prepare('SELECT 1').first();
health.checks.d1 = 'ok';
} catch (e) {
health.checks.d1 = 'error';
health.status = 'degraded';
}
return new Response(JSON.stringify(health), {
headers: { 'Content-Type': 'application/json' }
});
}
return handleRequest(request, env);
}
};
Common Issues & Solutions
Issue: High Error Rate
Diagnose:
# Check error logs
wrangler tail --status=error
# View analytics
# Dashboard → Analytics → Errors tab
Common causes:
- Missing environment variables
- Database connection issues
- Timeout errors (CPU limit exceeded)
- Invalid input/requests
Issue: Slow Performance
Diagnose:
# Check CPU time
wrangler tail --format=pretty
# Look for "CPU Time" field
# View analytics
# Dashboard → Analytics → CPU Time
Solutions:
- Cache responses
- Optimize database queries
- Stream large responses
- Upgrade to paid plan (30s CPU limit)
Issue: Requests Not Reaching Worker
Diagnose:
- Check DNS settings
- Verify Worker route configuration
- Check WAF rules (not blocking legitimate traffic)
Fix:
# List routes
wrangler routes list
# Check zone settings
curl "https://api.cloudflare.com/client/v4/zones/{zone_id}/workers/routes"
Issue: Missing Environment Variables
Diagnose:
export default {
async fetch(request, env) {
// Log available bindings
console.log('Available env:', Object.keys(env));
if (!env.MY_VAR) {
return new Response('MY_VAR not configured', { status: 500 });
}
}
};
Fix:
# wrangler.toml
[vars]
MY_VAR = "value"
# Or for secrets:
# wrangler secret put MY_SECRET
MCP Server Observability Tools
Use integrated MCP servers for observability:
cf-observability: Real-time metrics and alerts cf-logs: Query and analyze Logpush data cf-builds: Deployment history and status
Access via /mcp command in Claude Code.
Monitoring Best Practices
✅ DO:
- Set up health check endpoints
- Log errors with structured data
- Monitor key metrics (errors, latency, throughput)
- Use Ray IDs for request tracing
- Export logs to R2 for analysis
- Set up billing alerts
❌ DON'T:
- Log sensitive data (passwords, tokens)
- Over-log (causes performance issues)
- Ignore error patterns
- Skip monitoring in production
Dashboard Setup
Key Dashboards to Monitor:
- Workers Analytics: Request volume, errors, CPU time
- Security Events: WAF blocks, bot challenges
- Cache Analytics: Hit ratio, bandwidth saved
- Billing Usage: Current spend vs limits
Set up alerts:
- High error rate (>5%)
- CPU time spikes
- Unusual traffic patterns
- Cost thresholds
For cost monitoring specifically, see cloudflare-cost-optimization skill. For security events, see cloudflare-security skill.
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です