mcpField
O
observability-and-instrumentation

observability-and-instrumentation

github.com/afonsoft開発1345 回インストール2 日前に更新
リポジトリ

observability-and-instrumentation について

Observability and Instrumentationは、本番機能の開発と並行してテレメトリーを設計するためのソフトウェア開発スキルです。構造化ログ、メトリクス、分散トレース、実行可能なアラート、検証を扱い、運用上の問いへの回答と機密データ・メトリクスのカーディナリティ抑制を重視します。

主な機能

  1. 01テレメトリーをオンコールの問いに対応付け
  2. 02構造化ログと相関IDの指針
  3. 03RED・USEメトリクスの実践方法
  4. 04OpenTelemetryによる分散トレース
  5. 05実行可能なアラートとランブックの基準
  6. 06テストシナリオによるテレメトリー検証

活用シーン

  1. 01本番機能へのオブザーバビリティ追加
  2. 02新サービスや外部連携の計装
  3. 03プルリクエストでのテレメトリーとアラートの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/afonsoft/skills --skill observability-and-instrumentation

インストール後、Claude Code で/observability-and-instrumentationを入力して読み込まれたか確認します。

同梱ファイルreferences/observability-checklist.md

詳細

オブザーバビリティと計装

概要

観測できないコードは運用できません。オブザーバビリティとは、コードが出力するテレメトリを使い、外部から「システムは何をしていて、その理由は何か?」に答えられる能力です。計装はリリース後に追加するものではなく、テストと同じように機能と並行して実装します。テレメトリなしで機能をリリースすると、ユーザーから最初に報告されたバグは、クエリで調べるのではなく、考古学のような調査になってしまいます。

使用するタイミング

  • 本番環境で実行される機能を構築するとき
  • 新しいサービス、エンドポイント、バックグラウンドジョブ、外部連携を追加するとき
  • 本番環境のインシデントの診断に時間がかかりすぎたとき(「何が起きたのか分からなかった」)
  • アラートルールを設定またはレビューするとき
  • I/O、リトライ、キュー、サービス間呼び出しを追加する PR をレビューするとき

対象外:

  • 今まさに発生している障害の診断 — デバッグスキルを使ってください(オブザーバビリティがあれば、次回のデバッグが速くなります)

  • 計測済みの遅さのプロファイリングと最適化 — パフォーマンス最適化スキルを使ってください

  • リリース当日の監視チェックリストとロールバックの条件 — リリース/ローンチスキルを使ってください。このスキルでは、それらに必要な計装を扱います

  • ユーザーがこのスキルを英語で依頼または言及した場合(例:「/observability-and-instrumentation を使って」「observability-and-instrumentation を実行して」)。

  • ユーザーがこのスキルをポルトガル語で依頼または言及した場合(例:「use /observability-and-instrumentation」、「execute observability-and-instrumentation」)。

プロセス

1. 計装の前に「正常に動作している状態」を定義する

問いのないテレメトリはノイズです。計装を追加する前に、オンコールエンジニアがこの機能について尋ねるであろう質問を2〜4個書き出します。

FEATURE: checkout payment retry
QUESTIONS ON-CALL WILL ASK:
1. What fraction of payments succeed on first attempt vs after retry?
2. When a payment fails permanently, why? (provider error? timeout? validation?)
3. Is the payment provider slower than usual?
→ Every signal below must help answer one of these.

質問を挙げられないなら、計装を始める準備ができていません。すべてをログに記録しても、何も分からないでしょう。

2. 質問ごとに適切なシグナルを選ぶ
シグナル分かることコスト特性例
構造化ログ「この特定のケースで何が起きたか?」イベント単位。トラフィックに応じて増加プロバイダーのエラーコードを含む payment_failed
メトリクス「全体として、どのくらいの頻度で/速さで起きるか?」時系列ごとに一定。クエリコストが低いプロバイダー呼び出しの p99 レイテンシ
トレース「サービス間でどこに時間がかかったか?」リクエスト単位。通常はサンプリングされる処理区間ごとに内訳を示した、遅いチェックアウト1件

経験則:メトリクスは何かがおかしいことを、トレースはどこがおかしいかを、ログはなぜおかしいかを教えてくれます。

3. 構造化ログ

文章ではなくイベントをログに記録します。すべてのログ行を、安定したイベント名と機械可読なフィールドを含む JSON オブジェクトにします。

// BAD: string interpolation — unqueryable, inconsistent
logger.info(`Payment ${id} failed for user ${userId} after ${n} retries`);

// GOOD: stable event name + structured fields
logger.warn({
  event: 'payment_failed',
  paymentId: id,
  provider: 'stripe',
  errorCode: err.code,
  attempt: n,
}, 'payment failed');

ログレベル — 一貫して使います:

レベル意味オンコール時の対応
error不変条件が破られており、対応が必要な場合があります調査する
warn劣化しているものの対処済み(リトライ成功、フォールバック使用)傾向を監視する
info重要なビジネスイベント(注文確定、ジョブ完了)なし
debug診断用の詳細情報本番環境ではデフォルトで無効

相関 ID は必須です。 システム境界でリクエスト ID を生成(または受け入れ)し、すべてのログ行、スパン、送信先への呼び出しに付与します。相関 ID がなければ、インターリーブしたログから単一のリクエストを再構成できません。

// Express: child logger per request, ID propagated downstream
app.use((req, res, next) => {
  req.id = req.headers['x-request-id'] ?? crypto.randomUUID();
  req.log = logger.child({ requestId: req.id });
  res.setHeader('x-request-id', req.id);
  next();
});

シークレット、トークン、パスワード、完全な PII は絶対にログに記録しないでください。 テレメトリパイプラインは、典型的なデータ漏えい経路です。フィールドは許可リストで管理し、リクエストボディ全体を記録しないでください。セキュリティチェックリストの全文は ../code-review-and-quality/references/security-checklist.md を参照してください。

4. メトリクス

リクエスト駆動型サービスでは、すべてのエンドポイントと外部依存先に RED を設定します。Rate(リクエスト数/秒)、Errors(エラー率)、Duration(平均値ではなく、レイテンシーのヒストグラム)です。リソース(キュー、プール、ホスト)には USE を使用します。Utilization(使用率)、Saturation(飽和度)、Errors(エラー)です。

トレースと同様に、ベンダー中立の方法は OpenTelemetry のメトリクス API(ステップ 5 と同じ SDK とコンテキスト)です。以下の例では Prometheus の prom-client を使用しています。これは一般的なバックエンドの選択肢の 1 つであり、唯一の選択肢ではありません。RED/USE とカーディナリティに関するルールは、どちらを選んでも同じです。

import { Histogram } from 'prom-client';

const httpDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration',
  labelNames: ['method', 'route', 'status_class'],  // '2xx', not '200'
  buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});

カーディナリティは障害の原因になります。 ラベルの組み合わせが異なるたびに、別の時系列データが作成されます。ラベルには、小さく固定された値の集合(ルートテンプレート、ステータスクラス、プロバイダー名)を使用してください。ユーザー ID、生の URL、エラーメッセージなど、値の数に上限がないものはラベルに使用しないでください。これらはログやトレースに記録するものです。

OK as label:    route="/api/tasks/:id"   status_class="5xx"   provider="stripe"
NEVER a label:  user_id, email, request_id, full URL, error message text

平均値は決して使わず、常にパーセンタイルを追跡してください。平均値では、ひどい体験をしている 1% のユーザーが見えなくなります。ヒストグラムを使用し、p50/p95/p99 を確認してください。

5. 分散トレーシング

OpenTelemetry を使用してください。ベンダー中立の標準であり、自動計装によって HTTP、gRPC、一般的な DB クライアントを、コードをほとんど追加せずにカバーできます。

// tracing.ts — must be imported before anything else
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';

const sdk = new NodeSDK({
  serviceName: 'checkout-service',
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

手動でスパンを追加するのは、意味のある内部処理単位(例:applyDiscounts、`chargeProvider)の周辺に限り、オンコール担当者がフィルタリングに使う属性を付与します。すべての非同期境界(HTTP ヘッダー、キューメッセージのメタデータなど)を越えてコンテキストを伝播してください。そうしないと、トレースは境界で途切れます。デフォルトでは低いレートでヘッドベースサンプリングを行い、バックエンドがテールサンプリングに対応している場合は、エラーを 100% 保持してください。

6. アラート

原因ではなく、ユーザーが感じる症状に対してアラートを設定します。

SYMPTOM (page-worthy):           CAUSE (dashboard, not a page):
error rate > 1% for 5 min        CPU at 85%
p99 latency > 2s                 one pod restarted
queue age > 10 min               disk at 70%

原因ベースのアラートは、何も問題がないときに発報し、予測していなかった障害を見逃します。症状ベースのアラートは、原因に関係なく、ユーザーに影響が及んだときに正確に発報します。

作成するすべてのアラートには、次のルールを適用してください。

  1. 対応可能であること。 対応が「無視する。自動で回復する」であれば、そのアラートは削除してください。
  2. ランブックへのリンクがあること。3 行だけでも構いません。意味、最初に実行するクエリ、エスカレーション先を記載します。
  3. しきい値と継続時間があること。推測ではなく、SLO または過去のデータに基づいて設定します。
  4. 重大度は page(ユーザーに影響、今すぐ対応)と ticket(劣化、今週中に対応)の 2 つだけを使用します。3 段階目はノイズとなり、あらゆる通知を無視する習慣を招きます。
7. テレメトリー自体を検証する

計装もコードの一部であり、間違っている可能性があります。作業を完了とする前に、対象の処理を実行し、実際の出力を確認してください。

  • ステージング環境でエラーを強制的に発生させる → ログから requestId で見つけ、フィールドが構造化されていること([object Object] ではないこと)を確認する
  • テストトラフィックを送信する → 期待したラベルと妥当な値でメトリクスの時系列データが作成されることを確認する
  • トレーシング UI で 1 つのリクエストをサービス間で追跡する → スパンが途切れていないことを確認する
  • 新しいアラートをそれぞれ 1 回発報させる(しきい値を一時的に下げる) → 正しい通知先に届き、ランブックのリンクが機能することを確認する

よくある合理化

合理化実態
「動いてからログを追加すればいい」「後で」は「最初のインシデントの後」に変わります。何も見えないと気づくには、最も代償の大きいタイミングです。構築しながら計装しましょう。
「ログが多いほど可観測性が高い」構造化されていないノイズは、インシデント対応を速めるどころか遅らせます。検索可能なイベント3件は、散文形式のログ300行に勝ります。
「今は console.log で問題ない」構造化されていない出力は、フィルタリング、関連付け、アラート通知ができません。構造化ロガーを導入する手間は、最初の5分だけです。
「問題が起きたらダッシュボードを見ればいい」問いが定義されていないまま作られたダッシュボードには、答え以外のあらゆる情報が表示されます。オンコール時の疑問から始めましょう。
「重要なものはすべてアラートにして、後で調整すればいい」騒がしいページャーは、担当者に通知を無視するよう教えてしまいます。調整は結局行われず、実際のページャーを見逃すことになります。
「ユーザーIDをメトリクスのラベルにすればデバッグしやすい」その一方で、メトリクスのバックエンドがダウンします。カーディナリティの高い検索は、ログとトレースに任せましょう。
「サービスが2つしかないのにトレーシングは大げさだ」サービスが2つあれば、ログでは答えられないサービス間のレイテンシに関する疑問がすでに生じます。自動計装を使えば、コストはごくわずかです。

危険信号

  • リトライ、キュー、外部呼び出しを伴う機能のPRなのに、新しいテレメトリーが一切ない
  • 構造化フィールドではなく、文字列補間で組み立てられたログ行
  • 相関ID/リクエストIDがなく、各ログ行が孤立している
  • ユーザーID、生のURL、エラーメッセージのテキストがラベル付けされたメトリクス(カーディナリティの爆弾)
  • レイテンシをパーセンタイルなしで平均値として追跡している
  • 毎日発火し、対応されないまま確認済みにされるアラート
  • ユーザーに影響するエラー率を監視せず、原因(CPU、メモリ)に対するアラートで担当者を呼び出している
  • シークレット、トークン、リクエスト本文全体がログに記録されている
  • 本番環境の機能が正常であることの唯一の根拠が「自分のマシンでは動く」

検証

機能に計装を追加した後、次の項目を確認してください。

  • この機能に関するオンコール時の疑問が文書化され、それぞれに対応するシグナルがある
  • すべてのログ出力が構造化されている(JSON)。イベント名が安定しており、すべての行に相関IDがある
  • いずれのログ行にもシークレット、トークン、マスキングされていないPIIが含まれていない(実際の出力を抜き取り確認する)
  • すべての新しいエンドポイントと外部依存関係にREDメトリクスがあり、ラベルの種類が上限内に収まっている
  • レイテンシがヒストグラムであり、p95/p99をクエリできる
  • トレーシングUIで、スパンの欠落なく単一のリクエストをエンドツーエンドで追跡できる
  • 新しいアラートはすべて症状ベースで、ランブックへのリンクがあり、一度テスト発火させている
  • ステージング環境で発生させた障害を、ソースコードを読まずにテレメトリーだけで特定できた

このリストをリリース前の計装ゲートも含めてひと目で確認できる形式については、references/observability-checklist.md を参照してください。

コメント