【Django】ログ出力機能について簡単にまとめる

#はじめに
DjangoのLogging機能って初見だとよくわからない内容もありますよね。特に設定ファイル。
簡単・簡潔にまとめていきます。

#本番環境想定の設定ファイル
ログの設定ですが、自分で設定を行わない場合（settings.pyにLOGGINGの定義が無い場合）[Djangoソース内に含まれているデフォルト設定](https://github.com/django/django/blob/master/django/utils/log.py)が使用されます。
しかし、出力されるログがあまり見やすくないので自分で設定する場合がほとんどです。
以下、本番環境を想定した設定例です。settings.pyに以下内容を追記します。

```settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    # ログ出力フォーマットの設定
    'formatters': {
        'production': {
            'format': '%(asctime)s [%(levelname)s] %(process)d %(thread)d '
                      '%(pathname)s:%(lineno)d %(message)s'
        },
    },
    # ハンドラの設定
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/var/log/{}/app.log'.format(PROJECT_NAME),
            'formatter': 'production',
        },
    },
    # ロガーの設定
    'loggers': {
        # 自分で追加したアプリケーション全般のログを拾うロガー
        '': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False,
        },
        # Django自身が出力するログ全般を拾うロガー
        'django': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}
```

#使用例

```views.py
import logging

logger = logging.getLogger(__name__)
logger.info("log info test!")
logger.error("log error test!")
```

ログには重要度に応じた5段階のレベルが設定されています。
上記使用例のようにloggerにログレベルと同名のメソッドを実行させることでログを出力することができます。

|名前|用途
|:--|:--|
|DEBUG|デバッグの記録|
|INFO|正常動作の記録
|WARNING|警告の記録
|ERROR|エラーなど重大な問題
|CRITICAL|システム自体の停止など致命的な問題

#主要設定項目を見ていく
###disable_existing_loggers
Tureの場合は設定ファイルで定義されていないloggerが全て無効化されます。
この段階だと「設定ファイルで定義されてないloggerってなに？」と思うかもしれませんが、意味としては後述する**logger**項目で定義されていない名称のloggerが全て無効化されることになります。
**基本的にFalseでOK**だと思いますが[こちらの記事](https://qiita.com/tkygtr6/items/134f1cb63d2868c7959a)で詳しく検証されているので気になる方はご確認ください。


###formatters
ログ出力の書式設定です。
ここできちんと設定しておくとログの出力フォーマットが揃ってログが見やすくなるので、**設定必須**と思っても良いです。
%(asctime)や%(levelname)が何を意味しているかは[公式ドキュメント](https://docs.python.org/ja/3.7/library/logging.html#logrecord-attributes)に日本語でわかりやすく記載されています。

###handlers
ログの出力方法についての設定です。

 * **level**  
ここで設定したログレベル以下のログは出力されなくなります。
 * **class**  
ログ出力に使用するクラスです。この例ではファイルに出力するクラスを指定しています。  
ログファイルをローテーションしたい（ファイルサイズや日付単位でファイルが分かれて欲しい）場合は'logging.handlers.RotatingFileHandler'や'logging.handlers.TimedRotatingFileHandler'を使用すると便利です(実際のやり方は[こちらの記事](https://marsquai.com/745ca65e-e38b-4a8e-8d59-55421be50f7e/05f253f8-c11b-4c91-8091-989eb2600a7b/b2deae90-5afc-4bcf-9159-722dfd1aadec/)に詳しく書かれています)。  
他には例えば'logging.StreamHandler'を指定するとコンソールに出力されます。
 * **filename**  
ログ出力するファイルのパスとファイル名を指定できます。
 * **formatter**  
先に定義したformatterのうちどれを使用するかを指定します。

###loggers
実際にアプリから使用するloggerの設定です。
「''」で定義しているロガーは出力例にならった記述で

```python
import logging
logger = logging.getLogger(__name__)
```

各アプリケーションから取得できます。
基本的にはこれとdjango自身が出力するloggerの設定があれば十分です。

* **handlers**  
先に定義したhandlersのうちどれを使用するかを指定します。
* **level**  
handlersでの定義同様、ここで設定したログレベル以下のログは出力されなくなります。
* **propagate**  
これは＋αの内容になるので興味が無い方は**基本にFalseでOK**ぐらいに思っておいても問題無いです。

以下、propagateについての解説です。
loggerは実は**名前空間を持つことができます**。
例として下記のような'logA'と'logA.logB'を定義します。

```python
    'logger': {
        'logA': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False,
        },
        'logA.logB': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False,
        },
```

そしてアプリケーションから以下のようにloggerを取得します。

```views.py
import logging

logger = logging.getLogger('logA.logB.logC')
logger.info("log info test!")
```

この時logCは定義されていないので結果として**logBが取得されます**。
指定されたloggerが無い場合は上の名前空間に上がっていって、最初に見つけたものを取得するイメージです。

ここで、もしlogBとlogCも定義されており、かつpropagateがTrueだった場合、logA,logB,logC全てに対して

```python
logger.info("log info test!")
```

が実行されます。つまり、logA,logB,logCの設定が名前以外同じだった場合、**全く同じ内容のログが3重に出力されます**。
propagate(伝播)の名前通り、上の階層のloggerにもログ出力命令が伝播するんですね。
使いどころとしてはlogAにファイル出力用の設定、logBにコンソール出力用の設定をしておいて、ファイルだけで良い場合はlogAを取得、開発環境等でコンソールにも出したい場合はlogBを取得するようなイメージでしょうか（使ったこと無いですが…）。

　
　

  
以上です。最後は少し込み入った内容になってしまいましたが、複雑なことをしない限りこの記事に書いてある内容だけで基本的な設定はOKだと思います。
お疲れ様でした:cat2: