workers-observability について
Cloudflare Workers のログ、メトリクス、トレース、アラートを扱う可観測性実装向けのスキルです。本番監視に役立つ設定ガイドやパターン、テンプレート、スクリプトを提供します。
主な機能
- 01構造化 JSON ログのパターン
- 02Tail Workers によるログストリーミング
- 03Analytics Engine のメトリクス
- 04Logpush とトレースイベントのガイド
- 05アラートとログ分析の資料
活用シーン
- 01ログへのリクエストコンテキスト追加
- 02Worker ログの集約
- 03カスタムパフォーマンス指標の記録
- 04エラー率とインシデントの監視
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill workers-observabilityインストール後、Claude Code で/workers-observabilityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/alerting.mdanalytics-engine.mdcustom-metrics.mdlogging.mdtail-workers.mdscripts/analyze-logs.shsetup-logging.shtemplates/analytics-worker.tslogging-setup.tstail-worker.ts詳細
Cloudflare Workers のオブザーバビリティ
Cloudflare Workers 向けの本番環境対応オブザーバビリティ:ログ、メトリクス、トレース、アラート。
クイックスタート
// 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() })),
};
}
重要なルール
- 常に構造化 JSON ログを使用する - プレーンテキストのログは解析や集計が困難です
- リクエストコンテキストを含める - すべてのログエントリにリクエスト ID、メソッド、パスを含めます
- 機密データをログに記録しない - ログ内のトークン、パスワード、PII をマスキングします
- 適切なログレベルを使用する - 失敗には ERROR、回復可能な問題には WARN、操作には INFO を使用します
- 大量のログをサンプリングする - 本番環境のリクエストログには 1~10% のサンプリングを使用します
オブザーバビリティのコンポーネント
| コンポーネント | 目的 | 使用する場面 |
|---|---|---|
console.log | 基本的なログ記録 | 開発、デバッグ |
| Tail Workers | リアルタイムのログストリーミング | 本番環境でのログ集約 |
| Analytics Engine | カスタムメトリクス/分析 | ビジネスメトリクス、パフォーマンス追跡 |
| Logpush | 外部サービスへのログエクスポート | 長期保存、コンプライアンス |
| Workers Trace Events | 分散トレーシング | リクエストフローのデバッグ |
防止できる主な 8 つのエラー
| エラー | 症状 | 防止策 |
|---|---|---|
| ログが表示されない | ダッシュボードに出力がない | wrangler.jsonc で「Standard」ロギングを有効にする |
| ログの切り詰め | メッセージが 128KB で切れる | 大きなペイロードを分割し、サンプリングを使用する |
| Tail Worker が受信しない | 処理されたイベントがない | バインディング名が wrangler.jsonc と一致するか確認する |
| Analytics Engine への書き込みに失敗 | データが記録されない | AE バインディングを確認し、blobs の形式を確認する |
| ログ内の PII | セキュリティ/コンプライアンス違反 | マスキングミドルウェアを実装する |
| リクエストコンテキストの欠落 | ログを関連付けられない | すべてのログエントリに requestId を追加する |
| ログ量の急増 | 高コスト、ノイズ | 高頻度イベントにサンプリングを実装する |
| アラートの抜け漏れ | インシデントを検出できない | エラー率のしきい値に対するモニターを設定する |
ロギングの設定
wrangler.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"
}
]
}
構造化ロギングのパターン
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 の使用方法
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 のパターン
// 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,
}),
});
}
}
}
};
リファレンスを読み込むタイミング
タスクに応じて、該当するリファレンスを読み込みます:
- ロギングを設定しますか? → 構造化ロギングのパターン、ログレベル、マスキングについては
references/logging.mdを読み込みます - カスタムメトリクスを作成しますか? → Analytics Engine の SQL クエリとデータモデリングについては
references/analytics-engine.mdを読み込みます - ログ集約を実装しますか? → Tail Worker のパターンと外部サービスとの連携については
references/tail-workers.mdを読み込みます - ダッシュボード/トラッキングを作成しますか? → ビジネスメトリクスとパフォーマンス追跡については
references/custom-metrics.mdを読み込みます - アラートを設定しますか? → エラー率の監視と PagerDuty/Slack との連携については
references/alerting.mdを読み込みます
テンプレート
| テンプレート | 目的 | 使用する場面 |
|---|---|---|
templates/logging-setup.ts | 本番環境向けロギングクラス | ロギングを使用する新しい Worker の設定 |
templates/analytics-worker.ts | Analytics Engine との連携 | カスタムメトリクスの追加 |
templates/tail-worker.ts | 完全な Tail Worker | ログ集約パイプラインの構築 |
スクリプト
| スクリプト | 目的 | コマンド |
|---|---|---|
scripts/setup-logging.sh | ロギング設定の構成 | ./setup-logging.sh |
scripts/analyze-logs.sh | ログのクエリと分析 | ./analyze-logs.sh --errors --last 1h |
リソース
- Workers のオブザーバビリティ:https://developers.cloudflare.com/workers/observability/
- Analytics Engine:https://developers.cloudflare.com/analytics/analytics-engine/
- Tail Workers:https://developers.cloudflare.com/workers/observability/tail-workers/
- Logpush:https://developers.cloudflare.com/logs/get-started/
コメント
Markdown 対応