概要
本番環境でDebug=Falseにすると500エラーの際のログが表示されなくなるのはいいものの、エラーの特定が難しくなります
そこで、500エラーの時はSlackにエラーメッセージを送信する実装について解説します
前提
- loggerについてある程度知っている
ディレクトリ構成
tree
.
├── project
│ ├── __init__.py
│ ├── __pycache__
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── application
├── __init__.py
├── admin.py
├── apps.py
├── utils
│ └── logs.py
└── views
ログの設定
ログの設定を行いますので1つずつ説明します
DjangoはdicConfig format
といってdictを使ってログの設定を行います
filters
今回はDebug=Falseの時もエラーログを送信したいのでfiltersに
require_debug_false
を指定します
handlers
今回はAdminEmailHandlerを継承したSlackHandlerという独自のハンドラークラスを使います
AdminEmailHandlerを継承することでERROR以上のメッセージを全てSlackHandlerで処理します
loggers
django.request
と"level": "ERROR",
を指定することでERROR以上(500系)のログを指定したハンドラー(SlackHandler)に渡すことができます
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {
"()": "django.utils.log.RequireDebugFalse",
},
},
"handlers": {
"slack": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "application.utils.logs.SlackHandler",
}
},
"loggers": {
"django.request": {
"handlers": ["slack"],
"level": "ERROR",
"propagate": True,
},
},
}
# DEBUG=Falseの時に環境変数としてincomming webhookのurlを取得
if not DEBUG:
SLACK_ENDPOINT_URL = os.environ.get("SLACK_ENDPOINT_URL")
設定の詳細は公式ドキュメントに記載されています
Slack通知の設定
Slack通知の設定を行います
今回はSlackのAPIを使ってメッセージをPOSTしたいのでrequestsをインストールします
pip install requests
import json
import requests
from django.utils.log import AdminEmailHandler
from project import settings
class SlackHandler(AdminEmailHandler):
def send_mail(self, subject, message, *args, **kwargs):
webhook_url = settings.SLACK_ENDPOINT_URL
if "Request" in message:
alarm_emoji = ":rotating_light:"
text = alarm_emoji + message.split("COOKIES")[0]
data = json.dumps(
{
"attachments": [{"color": "#e01d5a", "text": text}],
}
)
headers = {"Content-Type": "application/json"}
requests.post(url=webhook_url, data=data, headers=headers)
順番に説明します
今回はsend_mailメソッド内にslackへメッセージをPOSTする処理を記載します
このメソッドが呼ばれるタイミングは
- 500エラーをハンドリングする時
- 500エラーを出した際のhtmlを出力する時
の2回なので500エラーをハンドリングする時(Requestがエラーメッセージ内に入っている時)のみ実行させます
Requestsが入ってる時のエラーメッセージは以下のとおりです
また、Djangoのエラーメッセージ内に環境変数が入ってしまっているのでCOOKIESでsplit()して不要な情報を取り除きます
data = json.dumps(
{
"attachments": [{"color": "#e01d5a", "text": text}],
}
)
を記載することで後述するSlackのメッセージの横に赤い線が入る上に長いメッセージを折り畳めるようになるので見やすくなります
詳細はこちらの公式ドキュメントを参照してください
Incomming Webhookの作成
作成方法についてはかなりわかりやすい記事があるので紹介します
Webhookを作成後、.envファイルに環境変数を記載します
SLACK_ENDPOINT_URL=https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXX
実際に送信してみよう!
500エラーになった後、以下のようにSlackの通知が送られてきたら成功です
Debug=Trueの状態で検証するには?
以下のようにRequireDebugTrueを使用すればDebug=Trueでもエラーログを送信できます
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_true": {
"()": "django.utils.log.RequireDebugTrue",
},
},
"handlers": {
"slack": {
"level": "ERROR",
"filters": ["require_debug_true"],
"class": "application.utils.logs.SlackHandler",
}
},
"loggers": {
"django.request": {
"handlers": ["slack"],
"level": "ERROR",
"propagate": True,
},
},
}
グループにメンションしたい時
SlackのグループIDの取得方法
その他>自分のオーガナイゼーション>メンバーディレクトリ
を選択します


ユーザグループのグループIDを取得できます
実装
グループにメンションした状態でSlackにエラーログを送信したい時は以下のように
`<!subteam^ID>`
を使うと実装できます
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {
"()": "django.utils.log.RequireDebugFalse",
},
},
"handlers": {
"slack": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "application.utils.logs.SlackHandler",
}
},
"loggers": {
"django.request": {
"handlers": ["slack"],
"level": "ERROR",
"propagate": True,
},
},
}
# DEBUG=Falseの時に環境変数としてincomming webhookのurlとSlackのTeamIDを取得
if not DEBUG:
SLACK_ENDPOINT_URL = os.environ.get("SLACK_ENDPOINT_URL")
SLACK_TEAM_ID = os.environ.get("SLACK_TEAM_ID")
import json
import requests
from django.utils.log import AdminEmailHandler
from project import settings
class SlackHandler(AdminEmailHandler):
def send_mail(self, subject, message, *args, **kwargs):
webhook_url = settings.SLACK_ENDPOINT_URL
team_id = settings.SLACK_TEAM_ID
if "Request" in message:
alarm_emoji = ":rotating_light:"
error_msg = message.split("COOKIES")[0]
mention = ""
if team_id:
mention = "<!subteam^" + team_id + ">\n"
text = mention + alarm_emoji + error_msg
data = json.dumps(
{
"attachments": [{"color": "#e01d5a", "text": text}],
}
)
headers = {"Content-Type": "application/json"}
requests.post(url=webhook_url, data=data, headers=headers)
詳細は公式ドキュメントに記載されています
以下のようにメンションされた状態で送信できたら成功です
参考