Sentry SDK Python統合:本番エラーの自動キャプチャ、APM監視、リアルタイムアラート

Python tutorial - IT technology blog
Python tutorial - IT technology blog

実際の問題:深夜2時、本番サーバーがクラッシュ

深夜2時17分。スマートフォンが振動した。日本のお客様から決済ページが表示されないとの報告が入ってきた。

サーバーにSSHで接続し、journalctl -u myapp -n 200を実行すると、tracebackが山積みになっていた。しかし本当の疑問はここだ——このエラーはいつから起きているのか?何人のユーザーが影響を受けているのか?どのリクエストがトリガーになったのか?

ログファイルには何千行もの記録があった。手動でgrepして問題を特定するのに20分かかった。そのとき初めて気づいた——自分は本番環境の状態について完全に盲目だったと。生のログとユーザーからの報告を待つことしかできなかった。

これがSentryを真剣に使い始めたきっかけだ。

問題はログが足りないことではなく、コンテキストが足りないこと

ログは十分にあった。読みきれないほどに。問題はコンテキストの欠如——そして、それらを意味のある情報に集約する手段がないことだ。本番インシデントで実際に必要なのはこういった情報だ:

  • 完全なスタックトレース(最後の5行だけでなく)
  • リクエストURL、HTTPメソッド、リクエストボディ
  • 影響を受けたユーザー(user_id、メールアドレス)
  • 実行環境:Pythonバージョン、サーバー名、デプロイバージョン
  • このエラーがいつ最初に発生し、どの程度の頻度で起きているか
  • 直近のデプロイと関連しているかどうか

logging.error(str(e))ではこういった情報は得られない。テキスト1行が出力されるだけだ。

もう一つある。パフォーマンス監視だ。Sentryを導入する前は、どのエンドポイントが遅いのか、どのクエリがリソースを食っているのかわからなかった——ユーザーから苦情が来るか、サーバーがOOMになるまで。

この問題を解決する2つのアプローチ

方法1:logging + Slackで自前アラートシステムを構築する

多くのチームがエラーをSlackに送るミドルウェアを自前で書いている:

import traceback
import requests

def notify_slack(error_msg):
    webhook = "https://hooks.slack.com/services/..."
    requests.post(webhook, json={"text": f"🚨 Error: {error_msg}"})

try:
    process_order(order_id)
except Exception as e:
    notify_slack(traceback.format_exc())
    raise

この方法は動作する。しかし問題がある。重複排除がないため、同じエラーが数分で1000件のSlackメッセージを発火させる可能性がある。タイプ別のグループ化もない。パフォーマンスプロファイリングもない。そして、そのパイプライン全体を自分でメンテナンスしなければならない。

方法2:Sentry SDKを使う

Sentryはただ一つのことに特化して作られている:エラー監視だ。セルフホスト(無料)とクラウドの両方がある。Pythonへの統合はpip installと数行のコードだけで済む:

pip install sentry-sdk
import sentry_sdk

sentry_sdk.init(
    dsn="https://[email protected]/project-id",
    traces_sample_rate=1.0,
    environment="production",
    release="[email protected]",
)

これで最初のステップは完了だ。これ以降、すべての未処理例外はSentryが自動的にキャプチャする。

基本からproduction対応までのSentry統合

1. logging統合を含む完全な設定

import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
import logging

logging_integration = LoggingIntegration(
    level=logging.INFO,        # INFOレベルからbreadcrumbをキャプチャ
    event_level=logging.ERROR  # ERRORレベルからのみSentryイベントを作成
)

sentry_sdk.init(
    dsn="https://[email protected]/project-id",
    integrations=[logging_integration],
    traces_sample_rate=0.1,   # APM用にリクエストの10%をサンプリング
    profiles_sample_rate=0.1,
    environment="production",
    release="[email protected]",
    send_default_pii=False,    # PII(個人情報)を送信しない
)

2. 各リクエストにユーザーコンテキストを付与する

エラーをデバッグする際、「誰がエラーに遭遇したか」を知ることは「何のエラーか」を知ることと同じくらい重要だ:

from sentry_sdk import set_user, set_tag, set_context

# 認証完了後に呼び出す
def set_sentry_user(user):
    set_user({
        "id": user.id,
        "email": user.email,
        "username": user.username,
    })
    set_tag("plan", user.plan)
    set_tag("region", user.region)

# カスタムコンテキストは無制限に追加可能
set_context("order", {
    "order_id": order.id,
    "total": order.total,
    "items_count": len(order.items),
})

3. breadcrumbsを使った手動例外キャプチャ

breadcrumbはエラー発生前にユーザーが行った各ステップを記録する。完全なタイムラインでインシデントを確認することで、推測ではなく正確にバグを再現できる:

from sentry_sdk import add_breadcrumb, capture_exception

def process_payment(order_id: str, amount: float):
    add_breadcrumb(
        category="payment",
        message=f"Starting payment for order {order_id}",
        level="info",
        data={"amount": amount}
    )

    try:
        result = payment_gateway.charge(amount)
        add_breadcrumb(
            category="payment",
            message="Gateway responded",
            data={"status": result.status}
        )
        return result
    except PaymentGatewayError as e:
        capture_exception(e)  # キャプチャするがre-raiseはしない
        return {"success": False, "error": "Payment failed"}
    except Exception as e:
        capture_exception(e)
        raise  # 予期しないエラー — アラートのためre-raiseする

4. FastAPIとSQLAlchemyのAPM設定

integrationsリストに追加するだけで、Sentryがフレームワーク全体を自動インストルメントする——各エンドポイントを個別に修正する必要はない:

from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration

sentry_sdk.init(
    dsn="...",
    integrations=[
        StarletteIntegration(transaction_style="endpoint"),
        FastApiIntegration(transaction_style="endpoint"),
        SqlalchemyIntegration(),  # DBクエリを自動的にトレース
    ],
    traces_sample_rate=0.05,  # トラフィックが多いアプリ向けに5%
)

設定後、Sentryは各リクエストのウォーターフォールチャートを表示する:処理時間、N+1クエリ、スローエンドポイント。初めてダッシュボードを確認したとき、あるエンドポイントが1回のJOINクエリではなく47回の個別クエリを実行していることを発見した——ログでは絶対に気づけないことだ。

5. アラートノイズを避けるためのイベントフィルタリング

すべての例外が深夜2時に起こす必要があるわけではない。ボットスキャナーからの404エラー、通常のタイムアウト、ユーザーのバリデーションエラーは不要だ:

import re

def before_send(event, hint):
    # ヘルスチェックエンドポイントをスキップ
    if "request" in event:
        url = event["request"].get("url", "")
        if re.search(r"/(health|ping|metrics)", url):
            return None  # None = Sentryにイベントを送信しない

    # 特定の例外タイプをスキップ
    if "exc_info" in hint:
        exc_type, exc_value, _ = hint["exc_info"]
        if isinstance(exc_value, (KeyboardInterrupt, BrokenPipeError)):
            return None

    return event

sentry_sdk.init(
    dsn="...",
    before_send=before_send,
)

before_sendでより複雑なURLフィルタリングパターンを書くときはregexをよく使う。コードに組み込む前にパターンを素早くテストするには、toolcraft.app/ja/tools/developer/regex-testerのregex testerを使っている——ブラウザで直接動作し、インストール不要で、デバッグ時間をかなり節約できる。

6. CI/CDと連携したリリーストラッキング

リリーストラッキングにより、Sentryはどのコミットからエラーが発生したか、誰がそのコード行を変更したかを把握できる:

# CIパイプライン内、デプロイ後に実行
export SENTRY_AUTH_TOKEN=your-auth-token
export SENTRY_ORG=your-org
export SENTRY_PROJECT=your-project

pip install sentry-cli
sentry-cli releases new "myapp@${GIT_SHA}"
sentry-cli releases set-commits "myapp@${GIT_SHA}" --auto
sentry-cli releases finalize "myapp@${GIT_SHA}"

7. ダッシュボードのアラートルール設定

SentryダッシュボードでAlerts → Create Alert Ruleに移動する:

  • Issue Alert:新しいエラーが初めて発生したとき、またはresolve後にregressionが起きたときにトリガー
  • Metric Alert:エラーレートが閾値を超えたときにトリガー(例:5分間でリクエストの>5%がエラー)
  • Performance Alert:あるエンドポイントのP95レイテンシが急増したときにトリガー

最も難しい部分は設定ではなく、適切な閾値を選ぶことだ。敏感すぎるとアラート疲れを引き起こし、緩すぎると本当のインシデントを見逃す。

8. データを管理したいチーム向けのセルフホスト

# 約4GBのRAMが必要
git clone https://github.com/getsentry/self-hosted.git
cd self-hosted
./install.sh
docker-compose up -d

コンプライアンス要件がある場合はセルフホストが適している。無料のクラウドプランでは月5,000エラーまで対応——小中規模のプロジェクトには十分だ。

あの深夜2時のインシデント以来、すべての本番アプリにSentryを設定した。次にエラーが発生したとき、Sentryは数秒以内に完全なコンテキスト付きでSlack通知を送ってくれた——どのユーザーが影響を受けたか、どのエンドポイントか、どのデプロイが原因かが即座にわかった。MTTRが45分から8分に短縮された。これがreactive(ユーザーからの報告を待つ)とproactive(ユーザーが気づく前に把握する)の違いだ。

Share: