【Python】loggingまとめ

この記事はChatGPTを使用して作成しています。

## はじめに
Pythonのloggingモジュールは、単なる`print`文に比べてはるかに柔軟かつ強力なログ管理機能を提供します。  
- **ログレベルの管理:** ログの重要度に応じて、DEBUG、INFO、WARNING、ERROR、CRITICALといったレベルを使い分けることができ、必要な情報だけを抽出できます。  
- **多様な出力先:** コンソール、ファイル、さらには外部サービス（例：Slack）など、複数の出力先を同時に設定可能です。  
- **フォーマットのカスタマイズ:** ログメッセージの形式を自由に設定できるため、後からの解析やデバッグが容易になります。

## loggingの基本的な使い方
以下は、loggerを用いた基本的なログ出力のサンプルコードです。

```python
import logging

# ロガーの生成とレベル設定
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# コンソールハンドラの設定
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# フォーマッタの設定
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)

# ハンドラをロガーに追加
logger.addHandler(ch)

# ログの出力
logger.debug("デバッグメッセージ")
logger.info("情報メッセージ")
logger.warning("警告メッセージ")
logger.error("エラーメッセージ")
logger.critical("致命的なメッセージ")
```

このコード例から分かるように、loggerによるログ出力は大きく分けて以下の4つの流れで行われます。
1. **ロガーの生成とレベル設定**
2. **ハンドラの設定**
3. **フォーマッタの設定**
4. **ログの出力**

これから各流れについて詳しく掘り下げて説明していきます。

---

### 1. ロガーの生成とレベル設定

ロガーの生成は、以下のコードで行います。

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

※ `logging.debug("デバッグメッセージ")`のように、ロガーを生成しなくてもログを生成することはできますが、設定の柔軟性や一貫性の観点から推奨されません。

詳細はこちら

https://qiita.com/amedama/items/b856b2f30c2f38665701

Pythonのloggingモジュールには、主に以下の5つのログレベルがあります。

| レベル    | 値  | 説明                                                         |
|-----------|-----|--------------------------------------------------------------|
| DEBUG     | 10  | 詳細な情報。主に開発や問題解決のために使用。                       |
| INFO      | 20  | 通常の動作状況など、一般的な情報を記録。                            |
| WARNING   | 30  | 警告。予期しない事象や潜在的な問題を示す。                          |
| ERROR     | 40  | エラー。実行時に問題が発生した場合に記録。                          |
| CRITICAL  | 50  | 致命的なエラー。システム全体の障害など、非常に重大な問題を示す。            |

出力レベルのセットは、以下の一行のコードで行います。

```python
logger.setLevel(logging.INFO)
```

これにより、`INFO`未満のレベルのログは出力されなくなります。

---

### 2. ハンドラの設定

Handlerを利用することで、ログの出力先や出力レベルを細かく制御することができます。  
使用するHandlerによってログの出力先が変わります。以下は一般的なHandlerの一覧です。

| Handlerタイプ                | 説明                                          |
|------------------------------|-----------------------------------------------|
| `StreamHandler`              | コンソールなどのストリームに出力します。                |
| `FileHandler`                | ファイルにログを出力します。                        |
| `RotatingFileHandler`        | ファイル出力をローテーションして管理します。              |
| `TimedRotatingFileHandler`   | 時間単位でファイル出力をローテーションします。              |
| `SMTPHandler`                | メールでログを送信します。                          |

Handler単位で出力レベルを制御できます。以下は、Handlerのみを設定するコード例です。

```python
import logging

# コンソールハンドラの設定
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# ファイルハンドラの設定
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
```

以下は、コンソールにINFO以上のレベルのログを出力し、ファイルにDEBUG以上のレベルのログを出力する完全なコード例です。

**入力**
```python
import logging

# ロガーの生成
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)

# コンソールハンドラの設定
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter('%(levelname)s - %(message)s')
console_handler.setFormatter(console_formatter)

# ファイルハンドラの設定
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_formatter)

# ハンドラをロガーに追加
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# 入力例と出力例
logger.debug("デバッグメッセージ")
logger.info("情報メッセージ")
logger.error("エラーメッセージ")
```

**出力(コンソール)**
```plaintext
INFO - 情報メッセージ
ERROR - エラーメッセージ
```

**出力(ファイル: app.log)**
```plaintext
2025-04-04 12:00:00,000 - DEBUG - デバッグメッセージ
2025-04-04 12:00:00,000 - INFO - 情報メッセージ
2025-04-04 12:00:00,000 - ERROR - エラーメッセージ
```

---

### 3. フォーマッタの設定

フォーマッタを使用することで、ログの出力形式を細かくカスタマイズできます。たとえば、ログにタイムスタンプやロガー名、ログレベルを含めることで、後からの解析やデバッグが容易になります。

以下は、フォーマッタで設定可能な主なフォーマット指定子の一覧です。

| 指定子             | 説明                                          | 例                                     |
|-------------------|-----------------------------------------------|----------------------------------------|
| `%(asctime)s`     | ログ出力時刻                                  | 2025-04-04 12:00:00,000                |
| `%(name)s`        | ロガーの名前                                  | my_logger                              |
| `%(levelname)s`   | ログレベル                                    | INFO, DEBUG, ERROR など                |
| `%(message)s`     | ログメッセージの内容                           | 任意のメッセージ                       |
| `%(filename)s`    | ログを出力したファイル名                        | example.py                             |
| `%(lineno)d`      | ログ出力行番号                                  | 42                                     |

フォーマッタをハンドラにセットするコードは以下のようになります。

```python
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
```

---
### 4.ログの出力

ログの出力には以下の関数を使います。

| 関数               | ログレベル   |
|-------------------|--------------|
| logger.debug()    | DEBUG        |
| logger.info()     | INFO         |
| logger.warning()  | WARNING      |
| logger.error()    | ERROR        |
| logger.critical() | CRITICAL     |

ログの出力は、以下のコードで行います。

```python
import logging

# ロガーの生成とレベル設定
logger = logging.getLogger('output_logger')
logger.setLevel(logging.DEBUG)

# 各ログレベルでの出力例
logger.debug("これはDEBUGメッセージです。")
logger.info("これはINFOメッセージです。")
logger.warning("これはWARNINGメッセージです。")
logger.error("これはERRORメッセージです。")
logger.critical("これはCRITICALメッセージです。")
```

## Loggerの主要属性とメソッドの概要

以下は、loggerの主要な属性やメソッドを引数も含めた形でまとめた表です。

| 属性/メソッド             | 引数 (必要な場合)        | 説明                                                         |
|--------------------------|-------------------------|--------------------------------------------------------------|
| `logger.name`            | なし                    | ロガーの名前。通常はモジュール名が設定される。                           |
| `logger.level`           | なし                    | 現在のログレベルを示す。                                            |
| `logger.setLevel()`      | `level`                 | ログレベルを設定する。例: `logger.setLevel(logging.DEBUG)`          |
| `logger.addHandler()`    | `handler`               | ハンドラを追加し、ログの出力先を指定する。                                |
| `logger.removeHandler()` | `handler`               | 既存のハンドラを削除する。                                            |
| `logger.debug()`         | `msg`   | DEBUGレベルのログを出力する。                                       |
| `logger.info()`          | `msg`   | INFOレベルのログを出力する。                                        |
| `logger.warning()`       | `msg`   | WARNINGレベルのログを出力する。                                     |
| `logger.error()`         | `msg`   | ERRORレベルのログを出力する。                                       |
| `logger.critical()`      | `msg`   | CRITICALレベルのログを出力する。                                    |

## 上級

### YAMLによるログ設定の管理
YAMLファイルを用いてログ設定を管理することで、設定内容の見通しが良くなり、環境ごとに柔軟な変更が可能となります。  
以下に、YAML設定ファイルの例と、それを読み込んで実行するコード例を示します。

#### YAML設定ファイル例 (`logging_config.yaml`)
```yaml
version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: simple
    stream: ext://sys.stdout
  file:
    class: logging.FileHandler
    level: DEBUG
    formatter: simple
    filename: app.yaml.log
loggers:
  my_logger:
    level: DEBUG
    handlers: [console, file]
    propagate: no
root:
  level: WARNING
  handlers: [console]
```

#### YAML設定を用いたコード例
```python
import logging
import logging.config
import yaml

with open('logging_config.yaml', 'r') as f:
    config = yaml.safe_load(f)
    logging.config.dictConfig(config)

logger = logging.getLogger('my_logger')
logger.debug("YAML設定によるデバッグメッセージ")
logger.info("YAML設定による情報メッセージ")
```

`import yaml`の実行には、事前に`pip install pyyaml`が必要であることに注意してください。


**出力(コンソール)**
```plaintext
INFO - YAML設定による情報メッセージ
```

**出力(ファイル: app.yaml.log)**
```plaintext
2025-04-04 12:00:00,000 - DEBUG - YAML設定によるデバッグメッセージ
2025-04-04 12:00:00,000 - INFO - YAML設定による情報メッセージ
```

--- 

#### YAML設定キーの概要
YAML設定ファイルで使用される主要なキーを以下の表にまとめます。

| キー         | 説明                                                           |
|--------------|----------------------------------------------------------------|
| version      | 設定のバージョン。通常は `1` を指定。                                  |
| formatters   | ログメッセージのフォーマットを定義。                                    |
| handlers     | ログの出力先（例：コンソール、ファイル）を定義。                           |
| loggers      | 個別のロガー設定を定義。                                          |
| root         | ルートロガーの設定。                                             |
| class        | ハンドラのクラス名。例：`logging.StreamHandler`、`logging.FileHandler`。 |
| level        | 各ハンドラまたはロガーのログレベルを指定。                                 |
| formatter    | 使用するフォーマッタのキーを指定。                                     |
| filename     | ファイルハンドラの場合、出力先ファイル名を指定。                            |
| stream       | ストリームハンドラの場合、出力先を指定（例：`ext://sys.stdout`）。           |


### カスタムハンドラの作成：Slack通知ハンドラ
以下は、SlackのWebhook URLを使用してエラー発生時にアラートを送信するカスタムハンドラの例です。  
環境変数や設定ファイルで`SLACK_WEBHOOK_URL`などを管理することを推奨します。

```python
import logging
import json
import requests

class SlackHandler(logging.Handler):
    def __init__(self, webhook_url, level=logging.NOTSET):
        super().__init__(level)
        self.webhook_url = webhook_url

    def emit(self, record):
        log_entry = self.format(record)
        payload = {"text": log_entry}
        try:
            response = requests.post(
                self.webhook_url,
                data=json.dumps(payload),
                headers={'Content-Type': 'application/json'}
            )
            response.raise_for_status()
        except Exception:
            self.handleError(record)

# 使用例
logger = logging.getLogger('slack_logger')
logger.setLevel(logging.ERROR)  # エラー以上のログのみ通知

# 環境変数等からWebhook URLを取得する例
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
slack_handler = SlackHandler(SLACK_WEBHOOK_URL)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
slack_handler.setFormatter(formatter)
logger.addHandler(slack_handler)

logger.error("Slackに送信されるエラーメッセージ")
```

#### スラック通知の出力例
実際にエラーが発生すると、設定したSlackのWebhook URLに対して以下のようなペイロードが送信されます。

```json
{
    "text": "2025-04-04 12:00:00,000 - ERROR - Slackに送信されるエラーメッセージ"
}
``` 

# 参考サイト

https://docs.python.org/ja/3/howto/logging.html

https://qiita.com/amedama/items/b856b2f30c2f38665701

https://youtu.be/T4eh_HZ0cW8?si=mUH5LbGosqMMhh2X

https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19

https://zenn.dev/dencyu/articles/2b58f669bcd473