【Python】Logging関連のまとめ(getLogger/FileHandler/Formatter/addHandler/setLevel)

# 概要
Pythonの`logging`を使っていると色々なオブジェクトやメソッドが出てくるなぁと思ったので、それぞれサンプルコードで整理してまとめました。

# サンプルコード4種の紹介
以下4パターンを紹介します。

1. `logging`のみの場合
2. `logger`を使う＆`FileHandler`を明示的に作成する場合
3. `basicConfig`の外で`FileHandler`を明示的に作成する場合
4. `FileHandler`と`StreamHandler`を利用しそれぞれにログレベルを設定する場合

## 1: `logging`のみの場合

まずは`logging`のみの場合。
ログファイルの保存先は、日時がファイル名に残るように設定されたファイルにしています。

```py
import logging
import os
from datetime import datetime


# ログの保存先とフォーマットを設定
log_filename = 'loggings/log_file_' + datetime.now().strftime("%Y-%m-%d") + '.log'

if not os.path.exists("loggings"):
    os.makedirs("loggings")

log_format = '%(asctime)s,%(msecs)d | %(levelname)s | %(name)s - %(message)s'

logging.basicConfig(filename=log_filename,
                    level=logging.DEBUG, 
                    format=log_format,
                    encoding='utf-8',
                    datefmt='%Y-%m-%d %H:%M:%S')
# ログメッセージを残す
logging.debug('loggingのみの場合のDebug Messageです')
```

出力結果：

```
2023-08-30 21:22:53,930 | DEBUG | root - loggingのみの場合のDebug Messageです
```

### `logging.basicConfig`関数とは？
`logging.basicConfig`は、`logging`モジュールの関数で、ロギングの基本的な動作をカスタマイズできます。一度のみ呼び出されます。最低でも`level`(ログレベル)が必要です。

```py
logging.basicConfig(level=logging.DEBUG)
```

実際に運用する際は、記録として残すことが多いと思うので、上記のように`filename`で指定してあげます。また、出力結果にあるように
```
日時 | ログレベル | ロガー名
```
といった時間形式にしたい場合は、`format`で指定します。


## 2: `logger`を使う＆`FileHandler`を明示的に作成する場合

```py
import logging
import os
from datetime import datetime


# ログの保存先とフォーマットを設定
log_filename = 'loggings/log_file_' + datetime.now().strftime("%Y-%m-%d") + '.log'

if not os.path.exists("loggings"):
    os.makedirs("loggings")

log_format = '%(asctime)s,%(msecs)d | %(levelname)s | %(name)s - %(message)s'

logging.basicConfig(handlers=[logging.FileHandler(
                    filename=log_filename,
                    encoding='utf-8',
                    mode='a')],
                    format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S',
                    level=logging.DEBUG)
logger = logging.getLogger('Apple')

logger.info("AppleロガーのInfo Messageです")
```


出力結果：


```
2023-08-30 21:24:36,423 | INFO | Apple - AppleロガーのInfo Messageです
```

### `FileHandler`オブジェクトとは？
1番目のものから`FileHandler`を明示的に作成しました。1番目のものでも、`FileHandler`を使用していますが、明示的に作成する場合は上記のようにします。

明示的に使用すると、フォーマットやログレベルなどの設定をハンドラーごとにカスタマイズしやすくなります（ここの具体的なコードは後述します）。

ハンドラーとは、ログメッセージの出力先や挙動を制御するためのコンポーネントのことです。
1つのロガーに対して、ハンドラは複数指定できる、というのがポイントです。

### `mode`パラメータとは？
ちなみに、`mode`パラメータは、`FileHandler`で指定する必要があります。
`logging.basicConfig`の引数に入れると、以下のエラーになります。

```
ValueError: Unrecognised argument(s): mode
```

この`mode`パラメータは、ファイルを開く際のモードを指定するための引数です。`mode`パラメータに指定できる主な値は以下の通り。

・`'w'`: 書き込みモード。ファイルが存在する場合は上書きされます。
・`'a'`: 追記モード。ファイルが存在する場合、既存の内容の末尾に追記されます。
・`'x'`: 排他的な書き込みモード。ファイルが存在する場合はエラーとなります。

### `getLogger`関数とは？
また、`logger`の使用を追記しています。これは、`logging`モジュール内のクラスであり、アプリケーション内で特定の名前を持つロギングインスタンスを作成するために使用されます。`logging.getLogger`で特定のロガー名を指定することができます。

## 3: `basicConfig`の外で`FileHandler`を明示的に作成する場合

2番目のものでは、`logging.basicConfig`の中に`FileHandler`があって可読性がちょっと低いですね。。ということで、`basicConfig`の外に定義します。

```py
import logging
import os
from datetime import datetime


# ログの保存先とフォーマットを設定
log_filename = 'loggings/log_file_' + datetime.now().strftime("%Y-%m-%d") + '.log'

if not os.path.exists("loggings"):
    os.makedirs("loggings")

log_format = '%(asctime)s,%(msecs)d | %(levelname)s | %(name)s - %(message)s'

# ファイルハンドラーを定義
file_handler = logging.FileHandler(filename=log_filename, encoding='utf-8', mode='a')
formatter = logging.Formatter(fmt=log_format, datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(formatter)

# ルートロガーの設定
logging.basicConfig(handlers=[file_handler], level=logging.DEBUG)
logger = logging.getLogger('Banana')

logger.warning("BananaロガーのWarning Messageです")
```

出力結果：

```
2023-08-30 21:26:02,550 | WARNING | Banana - BananaロガーのWarning Messageです
```

ちなみに、ただ外に定義するだけでは以下のエラーになります。

```
TypeError: 'FileHandler' object is not iterableとなる
```

外に定義すると、このように`Formatter`とか`setFormatter`が必要になります。

### `Formatter`オブジェクトとは？
ログメッセージの表示形式を制御するために使われます。
日付、レベル、メッセージなどの要素を含むログエントリのフォーマットを指定できます(上記でいう`log_format`の内容)。

`Formatter`のフォーマット文字列内にはプレースホルダーを使用することが可能です。その場合、上記コードのように変数を渡すのが一般的かなと思います。（上記`fmt=`は省略可能）

ちなみに、`logging.Formatter`のコンストラクタに指定できる引数は表示フォーマットのほか、以下のものがあります。

・`fmt`: 表示フォーマット
・`datefmt`: 日付フォーマット
・`style`: フォーマット文字列のスタイル
・`validate`: バリデーション
・`defaults`: デフォルト値

### `setFormatter`メソッドとは？
`Handler`に`Formatter`を設定するために使用されます。

## 4: `FileHandler`と`StreamHandler`を利用しそれぞれにログレベルを設定する場合

最後に、`FileHandler`を明示的に作成するメリットを加えます。

```py
import logging
import os
from datetime import datetime


# ログの保存先とフォーマットを設定
log_filename = 'loggings/log_file_' + datetime.now().strftime("%Y-%m-%d") + '.log'

if not os.path.exists("loggings"):
    os.makedirs("loggings")

log_format = '%(asctime)s,%(msecs)d | %(levelname)s | %(name)s - %(message)s'

# ロガーを作成
logger = logging.getLogger('Lemon')
logger.setLevel(logging.DEBUG)  # ログレベルを設定

# ファイルハンドラーを作成してロガーに追加
file_handler = logging.FileHandler(filename=log_filename, encoding='utf-8', mode='a')
file_handler.setLevel(logging.DEBUG)  # ファイルハンドラーのログレベルを設定
formatter = logging.Formatter(log_format, datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# コンソールハンドラーを作成してロガーに追加。コンソールに出力するStreamHandlerを利用
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)  # コンソールハンドラーのログレベルを設定
console_formatter = logging.Formatter(log_format, datefmt='%Y-%m-%d %H:%M:%S')
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)

# ログメッセージを残す
logger.debug('Debugレベルのメッセージ')
logger.info('Infoレベルのメッセージ')
```

出力結果：

```
2023-08-30 21:26:42,598 | DEBUG | Lemon - Debugレベルのメッセージ
2023-08-30 21:26:42,598 | INFO | Lemon - Infoレベルのメッセージ
```

上記のコードでは、`logger`に対して`addHandler`メソッドを使用して、ファイルハンドラーとコンソールハンドラーをそれぞれ追加しています。

・ファイルハンドラー：`DEBUG`レベルのメッセージを受け付け、ログファイルに書き込む
・コンソールハンドラー：`INFO`レベルのメッセージを受け付け、コンソールに表示する


### `addHandler`メソッドとは？
`addHandler`メソッドは、特定のロガーにハンドラー（ログの出力先）を追加するために使用されます。これにより、同一のロガーに複数のハンドラーを追加して、異なる出力先にログメッセージを送信することができます。

上記の例で言えば、ログファイルとコンソール（ターミナル）に表示することができました。

### `setLevel`メソッドとは？
ロガー（`Logger`）やハンドラー（`Handler）`のログレベルを設定するために使われます。どちらにも使えます。
これにより、特定のログレベル以上のメッセージだけを記録するように設定できます。不要なログメッセージをフィルタリングする時に役立ちますね。

今更ですが、ログレベルは以下の重要度の順位で定義されています。

```
CRITICAL: 重大なエラーや致命的な問題
ERROR: エラー
WARNING: 警告や注意
INFO: 情報メッセージ
DEBUG: デバッグ情報
NOTSET: デフォルトの状態
```