概要
今回はサーバーエラー(ステータスコード500)が発生してしまった際に、管理人へメールを送信する機能を利用してみたいと思います。
サーバーエラーは何かしらの処理を実行させようとして発生するエラーなので、もしもユーザーがそこに出くわしてしまうとサイトの信用度はガタ落ちになるはずです。
ユニットテスト等をしっかり行っていれば防げることかと思いますが、人間が作るものなので多かれ少なかれミスは起こります。
ということで、SMTPサーバーの設定と送信者の設定等をするだけで「Server Error(500)」が起こった際にメールを送信させてみる内容です。
他にも送信メールの件名やメッセージに対する変更や追加設定、フィルター機能を使って特定のエラーによってはスキップする実装もします。
実行環境 |
---|
Windows Subsystem for Linux |
Python 3.6.9 |
使用ライブラリ | ライセンス |
---|---|
Django==2.2.28 | BSD |
エラーが起こった際に実行される処理を確認してみます。
アクセスログやエラーログはPython標準ライブラリであるLoggingというモジュールを使用しており、Djangoはそのloggingモジュールをラップした独自のモジュールを持っています。
Djangoのデフォルトデフォルトロギングの設定は以下のようになっています。
# django/utils/log.py
DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'formatters': {
'django.server': {
'()': 'django.utils.log.ServerFormatter',
'format': '[{server_time}] {message}',
'style': '{',
}
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
},
'django.server': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'django.server',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
},
'django.server': {
'handlers': ['django.server'],
'level': 'INFO',
'propagate': False,
},
}
}
handlers
というキーで設定されている辞書は、各種辞書で定義されているlevel
によって発生された時に実行される処理です。
"level":"ERROR"
‘が発生した場合はmail_admins
が実行されます。
すでに管理者にメール送信するための設定はmail_admins
キーで定義されているので、ここは何も手を加えなくても大丈夫です(カスタマイズする際は手を加えます。)。
DEFAULT_LOGGING = {
...
'handlers': {
...
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
...
}
filters
キーに定義されているrequire_debug_false
は、「DEBUG=False」の時に実行されるという設定です。
class
キーにはAdminEmailHandler
が設定されており、管理者用にメール送信するsend_admins
が内部で使われています。
実際は「DEBUG=False」の状態で実行される仕組みなので、メール送信機能を試すにはrequire_debug_false
のフィルターを外すか、DEBUGを「False」に設定する必要があります。
# settings.py
# DEBUG=Trueでサーバーエラー時にメールを送信する場合
# from django.utils.log import DEFAULT_LOGGING as LOGGING
# LOGGING['handlers']['mail_admins']['filters'] = []
DEBUG = False
# DEBUG=Falseの場合はHOSTヘッダーインジェクションを防止するため必須
ALLOWED_HOSTS = ['127.0.0.1']
あとはSMTPサーバーの設定と送信先(管理者)宛てのリストを作成するだけです。
SMTP(Simple Mail Transfer Protocol)サーバーとは、送信専用のプロトコル(規則)を持ったサーバーで、メールを送受信するいくつかのメールサーバーの内の1つです。
詳しくはNTT東日本のサイトで説明してくれています。
DjangoでSMTPサーバーの設定をするには、設定ファイルの「settings.py」で必要な変数に使用する企業のSMTPを定義していきます。
ここではGoogleの「Gmail」を設定していきます。
# settings.py
...
# SMTPサーバーの設定
# 以下はGmailのSMTPを設定しています。
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'exsample'
# EMAIL_HOST_USER = 'exsample@gmail.com'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
上記は最低限必要とされる設定で、設定ファイル内の分かりやすい場所に定義しています。
各種変数の概要です。
変数 | 概要 |
---|---|
EMAIL_HOST | メーカーのSMTP |
EMAIL_HOST_USER | EMAIL_HOSTで設定したメーカーのアドレス名(@example.comは無くても動きました。) |
EMAIL_HOST_PASSWORD | EMAIL_HOSTで設定したメーカーのパスワード(取得方法はこちら |
EMAIL_PORT | EMAIL_HOSTに設定したメーカーに定められたポート番号 |
EMAIL_USE_TLS | 通信するときに TLS (セキュア) 接続を使用するかどうか。 |
送信先(管理者)宛てのリストを作成するには、Djangoの設定ファイルに「ADMINS」変数を定義します。
Djangoでは既に「ADMINS」変数を保持していますが、デフォルト値は空のリストです。
リストの中にタプルかリストで管理者のメールアドレスと宛名を代入します。
ADMINS = [('name', 'address@exsample.com'),]
ADMINS = [['name', 'address@exsample.com'],]
複数に送信したい場合は、リストの中にタプルかリストを追加していきます。
ADMINS = [('name', 'address@gmail.com'), ('name', 'address@yahoo.co.jp')]
Djangoの設定ファイルの全体像(管理者メール関連)です。
# settings.py
INSTALLED_APPS = [
...
send_admin, # アプリ名
]
DEBUG = False
# DEBUG=Falseの場合はHOSTヘッダー必須
ALLOWED_HOSTS = ['127.0.0.1']
import os
# SMTPサーバーの設定
# セキュリティ対策のため、サーバーの環境変数から取得
EMAIL_HOST = os.getenv('EMAIL_HOST')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
EMAIL_PORT = 587
EMAIL_USE_TLS = True
# 500エラーが発生した際のメール送信先
ADMINS = [('管理者', 'address@exsample.com'),]
サーバーエラー時のメール送信に使用する「views.py」です。
# send_admin/views.py
from django.core.exceptions import ViewDoesNotExist
from django.http import Http404, HttpResponseServerError
from django.views.generic import TemplateView
class Index(TemplateView):
template_name = 'send_admin/index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
req_dict = self.request.GET
if req_dict:
if req_dict['name'] == '500':
raise HttpResponseServerError
elif req_dict['name'] == '404':
raise Http404
elif req_dict['name'] == 'view':
raise ViewDoesNotExist
else:
context["requ"] = req_dict['name']
return context
アプリに使用する静的ファイルは以下です。
<!-- send_admin/templates/send_admin/index.html -->
<html lang="ja">
<head>
<!-- Webサイトタイトル -->
<title>リクエスト</title>
</head>
<body>
<h1>【Django】ServerError(500)になったら管理者にメール送信</h1>
<br>
<form method="GET">
<fieldset>
<legend>どのステータスコードを表示しますか?</legend>
<div>
<input type="radio" id="500" name="name" value="500" />
<label for="huey">500</label>
</div>
<div>
<input type="radio" id="404" name="name" value="404" />
<label for="huey">404</label>
</div>
<div>
<input type="radio" id="view" name="name" value="view" />
<label for="huey">Viewエラー</label>
</div>
<div>
<input type="radio" id="200" name="name" value="200" checked/>
<label for="dewey">200</label>
</div>
<input type="submit" value="送信" />
</fieldset>
</form>
<h1>{{ requ }}</h1>
</body>
</html>
ステータスコード500で送信すると、サーバーエラーとなりメールが送信されます。
サーバーエラー時に送られてくるメールの件名は「[Django]ERROR (EXTERNAL IP): Internal Server Error: /」となっていますが、頭文字「[Django]」の部分がデフォルト値です。
デフォルト値を変更する方法は、EMAIL_SUBJECT_PREFIX
に文字列を代入します。
# メール件名のプレフィックスを変更
EMAIL_SUBJECT_PREFIX = '[Send_Mail]'
Webページのコンテンツをそのままメッセージで表示するには、Logging設定値のmail_admins
キーにinclude_html
キーを追加します。
Loggingの設定値は、django.utils.log.DEFAULT_LOGGING
にあります。
DEFAULT_LOGGING = {
...
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True, # 追加
}
},
...
}
Loggingの設定値にDjangoのコールバックフィルターを設定することで、特定のサーバーエラー時はメールを送信しないようにすることができます。
Loggingの設定値は、django.utils.log.DEFAULT_LOGGING
にあります。
似たようなフィルターで、require_debug_false
が使用されています。
DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
...
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'], # DEBUG=Falseの際に実行される
'class': 'django.utils.log.AdminEmailHandler',
}
},
...
}
上記のrequire_debug_false
フィルターと同じように、filters
キーに新しくdjango.utils.log.CallbackFilter
クラスと自作のコールバック関数を追加します。
from django.core.exceptions import ViewDoesNotExist
# 自作のコールバック関数例
def original_callback_func(record):
if record.exc_info:
exc_type, exc_value = record.exc_info[:2]
if isinstance(exc_value, ViewDoesNotExist):
# Viewが存在しない場合のエラーはFalseを返す
return False
return True
DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'original_callback_func': { # 自作のコールバック関数
'()': 'django.utils.log.CallbackFilter',
'callback': original_callback_func,
},
...
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false', 'original_callback_func'], # 追加
'class': 'django.utils.log.AdminEmailHandler'
}
},
...
}
自作のコールバック関数を「True」の返り値で通過するとメールが送信され、「False」の返り値で通過すると除外、詰まりメールは送信されません。
# 自作のコールバック関数例
def original_callback_func(record):
if record.exc_info:
exc_type, exc_value = record.exc_info[:2]
if isinstance(exc_value, ViewDoesNotExist):
# Viewが存在しない場合のエラーはFalseを返す
return False
return True
最後までご覧いただきありがとうございました。