【入門編】Sentryの「Custom Tags and Context」を駆使してエラーログの検索性とコンテキストを爆発的に高める方法 – 運用監視・オブザーバビリティ活用バイブル

こんにちは!日々のエラー調査やログの海からの宝探し、本当にお疲れ様です。「またこのエラーか、でも原因はどこだ…?」と、深夜のステージング環境や本番環境で頭を抱えた経験はありませんか?

今回は、Sentryの真骨頂である「Custom Tags(カスタムタグ)」と「Context(コンテキスト)」を駆使して、エラーの検索性を爆発的に高める方法を解説します。

これをマスターすれば、毎日のエラー調査が劇的に楽になり、トラブルシューティングの時間が数分単位で終わるようになりますよ。一緒にその極意を学んでいきましょう!

—

1. なぜ「普通のSentryエラーログ」では物足りないのか?

Sentryを導入すると、とりあえず「何が起きたか(Exception)」と「どこで起きたか(Stacktrace)」が飛んできて感動しますよね。しかし、現実のアプリケーション、特にSaaSやECサイトなどの複雑なビジネスロジックでは、これだけでは不十分です。

例えば、こんなエラーが出たとします。
> `NullPointerException: Cannot read property ‘id’ of undefined`

これを見ただけでは、「どのユーザーの、どのテナントで、どんな操作をしている最中に起きたのか」がさっぱりわかりません。エラーログ単体は「証拠品」に過ぎず、事件の背景(コンテキスト)がごっそり抜け落ちているのです。

ここで登場するのが Custom Tags と Context です。これらを適切に仕込むことで、エラーという「点」を、ユーザーやビジネス文脈という「線」で結びつけることができます。

—

2. 基礎知識:Tags と Extra / Context の違いを整理する

Sentryに付与するメタデータには、いくつかの種類があります。ここを混同すると「検索できないタグを作ってしまった…」という悲劇が起きるので、最初に整理しておきましょう。

1. Tags(タグ)

  • 特徴: 「Indexed(インデックス化される)」ため、検索やフィルタリングのキーとして非常に高速に機能します。
  • 用途: テナントID、プラン、機能フラグ(Feature Flag)、ブラウザの種類など、「この値で絞り込みたい」もの。
  • 制限: 文字列である必要があり、 cardinality(値のバリエーション)が数万程度までのものが推奨されます。

2. Extra / Context(エクストラ / コンテキスト)

  • 特徴: インデックス化されませんが、「構造化された詳細データ(JSON等)」をそのまま添付できます。
  • 用途: 画面の状態、ショッピングカートの中身、APIリクエストのペイロードなど、「エラー発生時の状況証拠をじっくり眺めたい」もの。

—

3. 実装パターン:TypeScript / Node.js で網羅する実践コード

それでは、実際のコードでどのようにこれらを仕込むのか見ていきましょう。今回はモダンなWebアプリケーション(Node.js / Express などを想定)をイメージした TypeScript のコードで解説します。

ステップ1: スコープ(Scope)を使ったリクエストごとの分離

Webサーバー環境では、複数のリクエストが並行して処理されます。そのため、Sentryのコンテキストは「リクエストごとに独立してクリーン(分離)」でなければなりません。ここで `Sentry.withScope()` や `Sentry.configureScope()` が重要になります。

import as Sentry from ‘@sentry/node’;
import { Request, Response, NextFunction } from ‘express’;

// マルチテナントなSaaS環境を想定したミドルウェアの例
export function sentryContextMiddleware(req: Request, res: Response, next: NextFunction) {
// withScopeを使うことで、このリクエスト内だけのコンテキストを安全に作ります
Sentry.withScope((scope) => {

// 1. ユーザー情報の付与(Sentry公式の専用メソッド)
const userId = req.headers[‘x-user-id’] as string;
const userRole = req.headers[‘x-user-role’] as string;

if (userId) {
scope.setUser({
id: userId,
role: userRole,
// IPアドレスなどはプライバシー配慮しつつ必要に応じて
ip_address: req.ip,
});
}

// 2. Custom Tags の付与(検索・絞り込み用)
const tenantId = req.headers[‘x-tenant-id’] as string || ‘default-tenant’;
const planType = req.headers[‘x-plan-type’] as string || ‘free’;

scope.setTag(‘tenant.id’, tenantId);
scope.setTag(‘tenant.plan’, planType);
scope.setTag(‘http.method’, req.method);

// 3. Extra / Context の付与(詳細データ確認用・構造化オブジェクトOK)
scope.setContext(‘request_details’, {
queryParameters: req.query,
// パスワードなどの機密情報は必ずマスク(サニタイズ)すること!
headers: {
‘user-agent’: req.headers[‘user-agent’],
‘content-type’: req.headers[‘content-type’],
},
});

// 次のミドルウェア(またはコントローラー)へ処理を渡す
// このスコープ内で起きたエラーは、自動的に上記のTagsとContextを伴ってSentryに飛びます
next();
});
}

ステップ2: 複雑なビジネスロジック内でのピンポイントな追加

コントローラーやサービスクラスの深部で、特定の条件分岐やサードパーティAPIの呼び出しに失敗したとき、その場限りのコンテキストを追加したい場合があります。そんな時は `Sentry.captureException` や現在のスコープへのマージを使います。

async function processPayment(paymentData: { amount: number; currency: string; gateway: string }, userId: string) {
try {
// 決済処理のシミュレーション
if (paymentData.amount > 1000000) {
throw new Error(‘Amount exceeds single transaction limit’);
}
} catch (error) {
// エラー発生時に動的にタグやコンテキストを追加する
Sentry.withScope((scope) => {
// この決済処理固有のタグを追加
scope.setTag(‘payment.gateway’, paymentData.gateway);
scope.setTag(‘payment.currency’, paymentData.currency);

// 失敗時の詳細なペイロードをContextとして残す(金額などの数値を渡す)
scope.setContext(‘payment_failure_payload’, {
attemptedAmount: paymentData.amount,
targetUser: userId,
timestamp: new Date().toISOString(),
});

// 明示的にSentryへ送信
Sentry.captureException(error);
});

// 呼び出し元へエラーを再送出
throw error;
}
}

—

4. 検索フィルターの極意:Sentry UIを使い倒す

さて、上記のようにリッチなタグとコンテキストを仕込むと、Sentryのダッシュボード(Issues画面)での検索性が劇的に変わります。実務で即座に使える強力な検索クエリ(Search Queries)の書き方をご紹介します。

パターンA: 特定の「有料プラン」で起きているエラーだけを抽出する

> `is:unresolved tag:tenant.plan:enterprise`

  • 解説: 未解決(unresolved)の課題の中から、エンタープライズプランの顧客で発生しているものだけに絞り込みます。ビジネスインパクトが大きい順に優先度をつけるときに最強です。

パターンB: 特定のテナントの阿鼻叫喚を追跡する

> `tag:tenant.id:org_123456789`

  • 解説: 「特定の顧客から『システムがおかしい』と言われた」とき、このクエリを打つだけで、その顧客の環境で起きた全てのエラーがタイムラインで浮かび上がります。

パターンC: 決済ゲートウェイごとのエラー傾向を見る

> `error.type:Error tag:payment.gateway:stripe`

  • 解説: 外部API(Stripeなど)側の障害なのか、自社コードの問題なのかを切り分けるために、タグでフィルタリングしてスタックトレースの共通点を探します。

—

5. 現場で絶対に守るべき「3つのアンチパターン」

最後に、コンテキストやタグを設計する上で、絶対にやってはいけない罠をお伝えします。これだけは避けてください。

1. 機密情報をタグやコンテキストに含めない(Personal Data / Secrets)

  • パスワード、クレジットカード番号、APIキー、個人情報(PII)をそのまま `setTag` や `setContext` に突っ込まないでください。コンプライアンス違反やセキュリティインシデントにつながります。必ずマスク処理を挟みましょう。

2. カーディナリティ(値の種類)が無限のものをタグにしない

  • 例えば、タイムスタンプ(ミリ秒単位)や、UUIDそのものを `setTag` すると、Sentry側のインデックスが爆発し、検索パフォーマンスの低下や課金プランの圧迫を引き起こします。「タグは有限のカテゴリ(ステータス、プラン、IDなど)」、「コンテキストは自由記述」と使い分けましょう。

3. グローバルスコープを汚染し続ける

  • `Sentry.setTag()` をリクエストの分離を行わずにグローバルに呼び出すと、Aさんのリクエストで設定したタグが、次に処理されるBさんのリクエストに引き継がれてしまう(コンテキストのリーク)という恐ろしいバグを生みます。必ず `withScope` を利用するか、リクエストスコープで安全に管理してください。

—

まとめ

Sentryは、ただのエラーロガーではありません。「アプリケーションの状態を多次元で捉えるオブザーバビリティ・プラットフォーム」です。

今回ご紹介した `Custom Tags` と `Context` を適切に配置すれば、「エラーが発生した場所」だけでなく、「誰の、どんなビジネス文脈で、どの機能を使っているときに起きたのか」がひと目でわかるようになります。

「ログの海からエラーを探す作業」から卒業し、「一瞬で原因を特定して優雅に修正をマージするエンジニア」へ。今日からの開発に、ぜひ取り入れてみてください!

タイトルとURLをコピーしました