【あったら便利】loggingは大事なのに書き方をいつも忘れてしまうのでメモ

# 目的
pythonのlogging機能を使ってエラーが起きたときなどにエラーログ等をターミナルに出力、ファイルに出力したいと思ったが、自分からするとちょっと複雑でなかなか覚えることができないため、ここに簡単に頻繁に使うものを記述していきたい。

#概要
### logging
下記全てのlogの親となるようなもの。
このloggingの設定はグローバル設定のようなもので、**この設定を変更すると下記のloggerなどの設定よりもとにかく一番優先される**
このloggingは複数作らない。

### loggers
loggingから派生されたloggingの子供、クラス(logging)に対する各インスタンスのようなもの。

### handlers
loggerの設定を扱う設定親のようなもの。
例えばconsoleに出力する用のlogger、ログファイルに出力する用のloggerと設定を変更したい場合に分けると良い。
ログファイルにはwarning/errorのみ出力するが、consoleにはdebugやinfoなども出力したい場合などにはAのloggerにconsole_handler,file_handlerなどをアタッチするイメージ

### formaters
handlerにアタッチするための、出力するログのフォーマット詳細を設定するもの。

# 二つのloggingの設定方法
#### ログ出力する文字などと同じファイルにログ設定を書く方法
下記のようにすることでシンプルに素早くログの実装が可能となる。


```python
import logging

# loggingの基本的な設定
logging.basicConfig(
  level = logging.WARNING, filename='sample.log',
  filemode='w', format='%(asctime)s-%(process)s-%(levelname)s-%(message)s'
)

# 例
obj = self.get_object() 
# loggingを利用して上記のobjの中身を調べるために出力する
logging.info(obj)
```

#### setting.pyにログ設定を書く方法
実装部分にログの設定を書くと複雑になり可読性が落ちるため設定だけsettings.pyに記述し、実際の実装部分には最低限のログ出力実装しか記述しない。

```python
# 例
obj = self.get_object() 
# test_loggerの設定を呼び出す
logger = logging.getLogger('test_logger')
# test設定でobjをログ出力する
logger.info(obj)
```

```settings.py
LOGGING = {
    'version': 1,
    'formatters': { # 全体に適用されるフォーマットを指定
    },
　　　# handlerの設定
    'handlers': { 
　　　　　# file_handlerの設定 
        'file': {  
            
        },
        # console_handlerの設定
        'console': { # consoleへの出力設定
            
        },
    },
    'loggers': { 
        # loggingの子としてchildというloggerを定義
        'child': {  
            # child_loggerが利用するhandlerと出力するlogのレベルを記載
            'handlers': ['file', 'console'],  
            # debug以上のレベルのログを出力する
            'level': 'DEBUG',
        },
    },
}
```

そして実際に出力したいログ実装ファイルは下記である。今回はdjango_restframeworkのviews.pyでログ出力しようと考えているため、下記のようなログ出力にしている。

```views.py
def error_handling(self, request, pk=None):
        try:
            # とあるテーブルのobject(レコード)を取得。何が入っているか常に知りたい。
            obj = self.get_object()
            # test_loggerのログ設定を利用
            logger = logging.getLogger('test_logger')
            # test_loggerの設定でobjを出力
            # test_loggerではdebug以上のログは出力するように設定されているため下記のinfoログも出力される 
            logger.info(obj)
            serializer = self.get_serializer(obj)
            return Response(serializer.data)
        except:
            raise NotFound
```
# 具体例(setting.pyにログ設定を書く方法)
やはりログ出力の**設定**であるし、実装ファイルを複雑にしたくないためsetting.pyにログ出力設定は記述したいため、基本的にはsetting.pyにログ設定を書くこととする。

```setting.py
LOGGING = {
    'version': 1,
    'formatters': { # 全てのloggerに適用されるフォーマットを指定
        'all': {    # このフォーマット名は後にhandlerによって利用される
            'format': '\t'.join([
                "[%(levelname)s]",
                "asctime:%(asctime)s",
                "module:%(module)s",
                "message:%(message)s",
                "process:%(process)d",
                "thread:%(thread)d",
            ])
        },
    },
    'handlers': {  
        # ファイル出力設定
        'file': { 
            'level': 'DEBUG',  # DEBUG以上のレベル設定のログを出力する
            'class': 'logging.FileHandler',  # 元々用意されているファイル出力のためのクラス
            'filename': os.path.join(BASE_DIR, 'test.log'),  # 出力先のファイルパス
            'formatter': 'all',  # 初めに記述したallというformatで出力
        },
        'console': { 
            # console出力設定
            'level': 'DEBUG',
            # 元々用意されているconsole出力のためのクラス
            'class': 'logging.StreamHandler',
            # 初めに記述したallというformatで出力 
            'formatter': 'all'
        },
    },
    'loggers': {
　　　　　# testという名前のloggerをloggingから定義
        'test': {  
            # 上に記述した二つのhandlerをloggerにアタッチする
            'handlers': ['file', 'console'],  
            # グローバルが優先されるがloggerごとにもログ出力レベルを定義
            'level': 'DEBUG',
        },
    },
}
```

# 参考文献
[pythonのlogger奮闘記 ～簡単な使い方から複数ファイルを跨る使い方まで～](https://qiita.com/mimitaro/items/9fa7e054d60290d13bfc)