Django Server Error (500)攻略法【2019 アドカレ】

アドベント・カレンダー初参加です。よろしくお願いしまーす。

## 概要

趣味 Pythonista です。今年 Django に入門し、ウェブサイトを作ってみました。今回は自分とおなじ Django 初心者に向けて、最初に頭を悩ませ悲痛な叫びを上げるであろうコトについて攻略法をざっくり書いてみようとおもいます。ズバリ **Server Error (500)**です。

![Server Error (500)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270842/d6a7635e-04b1-4e60-0948-4de9d379a534.jpeg)

このとき、一時的にでも画面にエラー内容を出したり、ログを出力したり、はたまた Slack にエラー内容を通知したりしてみます。

## Server Error (500)発生

初心者なので views.py のメソッドについついバグを仕込んでしまいました。

```python:views.py
def index(request):

    aaaa  # ←突然の aaaa

    return HttpResponse('<p>Hello world.</p>')
```

settings.py の `DEBUG` が `True` だと、次のようにわかりやすいエラーメッセージが表示されます。

![わかりやすいエラーメッセージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270842/0b54fc24-c62d-b351-288e-8deea2165042.jpeg)

ただし `DEBUG` を `False` に変えた途端に親の顔より見た Server Error (500)が現れると思います。

![Server Error (500)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270842/d6a7635e-04b1-4e60-0948-4de9d379a534.jpeg)

## この画面を出しているのは handler500

サーバエラーが発生したときのハンドラーが [django.conf.urls.\_\_init\_\_.py](https://github.com/django/django/blob/master/django/conf/urls/__init__.py#L9) にありました。

```python:django/conf/urls/__init__.py
handler500 = defaults.server_error
```

サーバでエラーが起こると、この handler500 が実行されます。つまり defaults.server_error 関数が実行されます。 [django.views.defaults.server_error](https://github.com/django/django/blob/master/django/views/defaults.py#L80) の中身を覗くと、この画面を出している処理が記述されていますね。

```python:django/views/defaults.py
@requires_csrf_token
def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
    """
    500 error handler.
    Templates: :template:`500.html`
    Context: None
    """

    # 中略

        return HttpResponseServerError(
            ERROR_PAGE_TEMPLATE % {'title': 'Server Error (500)', 'details': ''},
            content_type='text/html',
        )
```

だったら handler500 に代入する関数を defaults.server_error ではなく違う関数にしてしまえばいいんですね。

## handler500 に違う関数を代入する

1. 自前の server_error 関数を定義
1. handler500 にそれを代入

自前の server_error は views.py に定義してみます。オリジナルのやつが django.views.defaults に定義してあるので。

```python:views.py
@requires_csrf_token
def my_customized_server_error(request, template_name='500.html'):
    return HttpResponseServerError('<h1>Server Error (500)だよー</h1>')
```

handler500 への代入は urls.py で行います。オリジナルの handler500 の定義は django.conf.urls にあるので。

```python:urls.py
handler500 = views.my_customized_server_error
```

Server Error 画面の変更をすることができました。

![django3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270842/61142f87-bee5-0ee7-d74f-c417f21c0de3.jpeg)

あとはもう、 my_customized_server_error に好きなこと書けばいいですね。

## my_customized_server_error の例

普通に `print` するコードです。

```python:views.py
@requires_csrf_token
def my_customized_server_error(request, template_name='500.html'):
    import traceback
    print(traceback.format_exc())
    return HttpResponseServerError('<h1>Server Error (500)だよー</h1>')
```

***

`DEBUG = True` のときと同じエラー画面を出すコードです。これ見やすいですよね。(小並感)

```python:views.py
@requires_csrf_token
def my_customized_server_error(request, template_name='500.html'):
    import sys
    from django.views import debug
    error_html = debug.technical_500_response(request, *sys.exc_info()).content
    return HttpResponseServerError(error_html)
```

***

親の顔より見たデフォルトの Server Error (500)画面を出すコードです。先程見つけ出した django.views.defaults.server_error を使えば OK です。

```python:views.py
@requires_csrf_token
def my_customized_server_error(request, template_name='500.html'):
    from django.views.defaults import server_error
    return server_error(request, template_name)
```

***

みんな大好き Slack に通知を飛ばすコードです。

```python:views.py
@requires_csrf_token
def my_customized_server_error(request, template_name='500.html'):
    import requests
    import json
    import traceback
    requests.post(
        'あなたの Slack Webhook URL',
        data=json.dumps({
            'text': '\n'.join([
                f'Request uri: {request.build_absolute_uri()}',
                traceback.format_exc(),
            ]),
            'username': 'Django エラー通知',
            'icon_emoji': ':jack_o_lantern:',
        })
    )
    return HttpResponseServerError('<h1>Server Error (500)だよー</h1>')
```

飛びました。私のぱそこのディレクトリ構成が表示されてしまうので、モザイクを入れさせてもらっちゃいます。

![django4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270842/b6276a1a-aff3-cbf8-8e93-ca2a98ed8c23.jpeg)

明日の Django アドベント・カレンダーは @boxboxbax さんです。がんばってください!