1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Django] Debug=FalseでもSlackでエラーログを送信しよう!

Last updated at Posted at 2023-09-08

概要

本番環境で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)に渡すことができます

settings.py
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
application.utils.logs.py
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が入ってる時のエラーメッセージは以下のとおりです

スクリーンショット 2023-09-08 19.40.08.png
スクリーンショット 2023-09-08 19.41.06.png

また、Djangoのエラーメッセージ内に環境変数が入ってしまっているのでCOOKIESでsplit()して不要な情報を取り除きます

data = json.dumps(
    {
        "attachments": [{"color": "#e01d5a", "text": text}],
    }
)

を記載することで後述するSlackのメッセージの横に赤い線が入る上に長いメッセージを折り畳めるようになるので見やすくなります
詳細はこちらの公式ドキュメントを参照してください

Incomming Webhookの作成

作成方法についてはかなりわかりやすい記事があるので紹介します

Webhookを作成後、.envファイルに環境変数を記載します

.env
SLACK_ENDPOINT_URL=https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXX

実際に送信してみよう!

500エラーになった後、以下のようにSlackの通知が送られてきたら成功です
スクリーンショット 2023-09-08 19.47.46.png

詳細を表示させると以下のようになります
スクリーンショット 2023-09-08 19.48.19.png
スクリーンショット 2023-09-08 19.49.33.png

Debug=Trueの状態で検証するには?

以下のようにRequireDebugTrueを使用すればDebug=Trueでもエラーログを送信できます

settings.py
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を取得できます

スクリーンショット 2024-04-28 19.13.49.png

実装

グループにメンションした状態でSlackにエラーログを送信したい時は以下のように

`<!subteam^ID>`

を使うと実装できます

settings.py
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")
application.utils.logs.py
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)

詳細は公式ドキュメントに記載されています

以下のようにメンションされた状態で送信できたら成功です

スクリーンショット 2024-02-02 11.41.20.png

参考

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?