実際の問題:深夜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(ユーザーが気づく前に把握する)の違いだ。

