Python loggingの基本的な使い方を抑える

## この記事で説明すること
Pythonのloggingを使ったログ処理について、業務上扱うのに先に抑えておくべきだったなと思ったことを紹介します。  

## 先に結論的な
* loggerにはsetLevelがあり、logger毎に扱うログのレベルを設定可能
* 各loggerにはhandlerがありhandlerごとにログの出力先の設定や、handler自体にもsetLevelを設定可能
* ルートロガーは特別なloggerで、デフォルトで使用すると以下の設定となる
  * loggerのsetLevel：WARNING
  * handler：lastResort(実質のところStreamHandler)
  * handlerの出力先：標準エラー出力
  * handlerのsetLevel：WARNING


## loggerについて
Pythonのloggingモジュールでは、以下のようにしてlogger(ロガー、ログを出力してくれるもの)インスタンスを生成します。  
生成したインスタンスを利用し、infoやerrorなどのログを出力することができます。
```python
import logging
logger = logging.getLogger()
logger.info("infomation message")
logger.error("error message")
```
上記のような使い方でも、個人開発でローカル環境ならそこまで問題ないかもしれません。  
ただ、上記例だと**info()でログが表示されない状態となっています。**
 
なぜそのようになっているかというと、  
**上記ではルートロガーを生成しており、ルートロガーのデフォルトのsetLevelがWARNINGだからです。**

### logger自体のLevel
loggerにはLevelという、インスタンス変数があります。  
インスタンス変数なので、logger毎に設定が可能です。 
先ほどの例でいうと
```python
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
```
とすることでそのloggerが扱うLevelを設定することができます。  
ここでいうLevelとは、そのloggerが扱うログの重要度のことであり、設定したLevel未満のログはloggerによって無視されます。  
Levelは以下があります（[公式](https://docs.python.org/ja/3/library/logging.html#levels)より画像引用）。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1918417/e91d9799-d01c-6439-75f7-e5cc6d32873e.png)

たとえば、
```python
logger.setLevel(logging.INFO)
logger.debug("debug message")
```
の場合には、loggerのLevel(INFO)よりも、DEBUGのLevelが低いため、上記の"debug message"はloggerが無視し、処理しないことになります。

### NOT SETだったら全部のLevelが処理されるはず
先述の
```python
import logging
logger = logging.getLogger()
logger.info("infomation message")
logger.error("error message")
```
では**info()でログが表示されない状態**と記載しました。  
上記のコードであればLevelを指定していないので、NOT SETとして設定されていそうな気がしますよね。
しかし、例によってルートロガーは特別にLevelのデフォルト値がWARNINGになっているために、loggerがINFOレベルのログを無視していたのでした。

### loggerには名前空間があり、名前空間ごとにLevelを設定できる
loggerには名前空間があります。
名前空間はある範囲での名前が重複せずに、一意の名前であるように識別するものです。  
例えば以下の場合だと、各々3つの名前空間に対してlogger自身のLevelも設定できますし、logger毎にhandlerとhandleのLevelも設定可能です。 

* sample
* sample.child
* sample.child.grandchild

名前空間では **.** (ドット)で親子関係を表現できます。上記の例であればsampleが親、childが子、grandchildが孫となります。  
子ロガーでログ出力の際、handlerが設定されていなかった場合には親ロガーへログ処理が委譲されます（デフォルト設定のままなら）。  
例ではgrandchildロガーがhandlerを持っていなければ、childに委譲され、さらになければsampleでログ処理が行わます。  
さらにsampleにもhandlerがなければ、最終的にルートロガーに委譲され処理が行われます。  
ただし明確な理由がなければ、基本的にルートロガーのみににLevelやhandlerを設定を設定し、子ロガーでは処理をルートロガーに委譲する使い方でも良いと思います。

## handlerとは
handler（ハンドラ、ハンドラー）とは、実際のログ処理を

* どこに出力するか
* handler自身ではどのLevelを扱うか

などについて設定できます。  

### handlerの種類
handlerにはいくつかの種類があり、それによってログの出力先を変更できます。
いくつか例を挙げると  

* StreamHandle
    * Python実行環境の標準出力または標準エラー出力へ出力
    * **デフォルトだと標準エラー出力が設定されている**
* FileHandler
    * ディスク上のファイルへ出力
* HTTPHandler  
    * URLを指定したWebサーバー上でPOSTなどで出力

などがあります(記事を書くにあたって調べたら、他にもたくさんあって面白かったです)。  

### handlerにもLevelがある
loggerのLevel同様、handlerにもLevelを設定できます。  
考え方的にはlogger自体に設定するものと一緒です。

## 各loggerとhandlerの処理順序
loggerとhandlerのそれぞれにLevelを設定できます。  
しかしそれぞれのLevelが一致していない場合、どちらが優先されるのでしょうか。  
答えはloggerが先に判定し、そのあとにhandlerが最終的に処理するかどうかを決めます。  
例えば以下の場合、loggerのLevelよりもhandlerのLevelが高いため、結果的にDEBUGログを出力できません。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1918417/afb8d3ba-439b-2f1c-06c5-38fda2040fe7.png)


また、loggerのレベル未満のログはhandlerに届くことすらありません。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1918417/2b1ee32d-3b98-2077-cdb7-1772bcd944d6.png)


handlerは複数設定することもできるため、loggerが扱うLevelの範囲であれば、  
以下のようなこともできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1918417/676da85d-90ee-a013-b44c-637ae5a697c2.png)


## 他にもFormatterとかあるけど
ここまでの内容ですでに3200文字を超えているため、この記事は一旦ここまでにします。  
logger自体のLevelとhandlerの使い方がわかれば、まずは最低限意図したログを出せるようになるかなと思います。

## 余談（ほんとは一番言いたかったこと）
ルートロガーをデフォルトのまま使っていたせいで、Azure App Serviceの監視をしているLogAnalytics上でINFOログが全部ERRORで出力していたことが今回の記事のきっかけでした。  
そもそもルートロガーをそのまま使うなよとかありそうですが、今回良い勉強になったのでヨシ！！！