---
title: "Python の logging を正しいログレベルと例外の記録つきで設定する"
description: "Python の logging は、設定するまで WARNING 以上しか出力しません。3 行の設定、5 つのログレベル、例外をトレースバックつきで記録する方法を、Python 3.13 で検証してまとめました。"
canonical: "https://logbrew.co/ja/guides/python-logging"
last_updated: "2026-11-10"
---

# Python の logging を正しいログレベルと例外の記録つきで設定する

Python の `logging` は、設定するまで WARNING 以上しか出力しません。3 行の設定、5 つのログレベル、例外をトレースバックつきで記録する方法を、Python 3.13 で検証してまとめました。

2026年11月10日 に Python 3.13.5、logbrew-sdk 0.1.29 で検証

執筆 [Furkan Erday](https://github.com/furkanerday). Furkan Erday は LogBrew の創業者です。毎日 Claude Code と Codex を使って LogBrew を開発しています。

Canonical: https://logbrew.co/ja/guides/python-logging

## Python の各ログレベルが、設定なしの場合と LogBrew でどう扱われるか

| logging の呼び出し | 設定なしで出力 | LogBrew での独自レベル |
| --- | --- | --- |
| `log.debug()` | 破棄 | `info` に統合 |
| `log.info()` | 破棄 | `info` |
| `log.warning()` | `WARNING:root:` | `warning` |
| `log.error()` | `ERROR:root:` | `error` |
| `log.critical()` | `CRITICAL:root:` | `critical` |

設定なしでルートロガーを 5 回呼び出し、次に同じ 5 回を LogBrew のハンドラー経由で送りました。カップが満杯なら、出力された、または独自のレベルとして保存されたことを示します。

## 3 行で logging を設定する

### 最初のログ呼び出しの前に basicConfig() を 1 回呼び出す

`basicConfig()` は、ルートロガーのレベルと行の書式を設定します。設定がない場合、Python は `WARNING:root:keg below 10%` のように WARNING 以上だけを出力し、DEBUG と INFO は破棄します。

```bash
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
log = logging.getLogger("taproom")

log.debug("keg pressure read")
log.info("tap opened")
log.warning("keg below 10%")
log.error("keg empty")
log.critical("cooler offline")
```

出力

```text
2026-11-10 08:34:04,828 INFO taproom tap opened
2026-11-10 08:34:04,828 WARNING taproom keg below 10%
2026-11-10 08:34:04,828 ERROR taproom keg empty
2026-11-10 08:34:04,828 CRITICAL taproom cooler offline
```

### モジュールごとにロガーに名前を付ける

`logging.getLogger(__name__)` でモジュールごとに専用のロガーを持たせると、各行にどのモジュールから出たかが記録されます。ルートロガーは `basicConfig()` のためだけに使います。

### レベルを環境変数から読み込む

`basicConfig()` はレベル名をテキストで受け取ります。これを `LOG_LEVEL=DEBUG` で実行すると、コードを変えずにデバッグ行が表示されます。

```bash
import logging
import os

logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))
log = logging.getLogger("taproom")
log.debug("keg pressure read")
log.info("tap opened")
```

出力

```text
DEBUG:taproom:keg pressure read
INFO:taproom:tap opened
```

### 再設定するときは force=True を渡す

ルートロガーにすでにハンドラーがあると、Python は 2 回目の `basicConfig()` 呼び出しを無視します。テストなどでハンドラーを置き換えるには `force=True` を渡します。

```bash
import logging

logging.basicConfig(level=logging.WARNING)
logging.basicConfig(level=logging.DEBUG)
logging.debug("second basicConfig: not printed")
logging.basicConfig(level=logging.DEBUG, force=True)
logging.debug("basicConfig with force=True: printed")
```

出力

```text
DEBUG:root:basicConfig with force=True: printed
```

## すべての例外をトレースバックつきで記録する

### except の中で log.exception() を呼び出す

`log.exception()` は ERROR レベルで記録し、トレースバックを添付します。ほかのレベルの呼び出しで同じことをするには `exc_info=True` を渡します。

```bash
import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s %(message)s")
log = logging.getLogger("taproom")
taps = {"ipa": 12}

def pour(beer):
    return taps[beer]

try:
    pour("stout")
except KeyError:
    log.exception("pour failed")
```

出力

```text
ERROR taproom pour failed
Traceback (most recent call last):
  File "app.py", line 11, in <module>
    pour("stout")
    ~~~~^^^^^^^^^
  File "app.py", line 8, in pour
    return taps[beer]
           ~~~~^^^^^^
KeyError: 'stout'
```

### logging ハンドラーで各行を LogBrew に送る

ロガーに `LogBrewLoggingHandler` を追加します。各レコードは、レベル、ロガー名、ファイル、行番号を持つ LogBrew のログになります。例外名とメッセージ、`extra={}` の値も一緒に送られます。

```bash
import logging
import os

from logbrew_sdk import HttpTransport, LogBrewClient, LogBrewLoggingHandler

client = LogBrewClient.create(api_key=os.environ["LOGBREW_SERVER_API_KEY"])
transport = HttpTransport()
log = logging.getLogger("taproom")
log.addHandler(LogBrewLoggingHandler(client, transport, metadata={"service": "taproom"}))
log.setLevel(logging.INFO)

taps = {"ipa": 12}

def pour(beer):
    return taps[beer]

try:
    pour("stout")
except KeyError:
    log.exception("pour failed", extra={"order_id": "ord_42"})

response = client.flush(transport)
print("HTTP", response.status_code, "accepted", response.accepted_events, "logs")
```

出力

```text
HTTP 200 accepted 1 logs
```

### 例外フィールドつきでログを読み返す

`logbrew read logs --severity error --json` は、`exceptionName`、`exceptionMessage`、ファイル、関数、行番号、そして `order_id` を含む行を返します。

```bash
logbrew read logs --project <project_id> --severity error --since 1h --json | jq '.[0] | {level, message, logger: .attributes.logger, metadata: .attributes.metadata}'
```

出力

```text
{
  "level": "error",
  "message": "pour failed",
  "logger": "taproom",
  "metadata": {
    "exceptionMessage": "'stout'",
    "exceptionName": "KeyError",
    "fileName": "send.py",
    "functionName": "<module>",
    "levelName": "ERROR",
    "levelNumber": 40,
    "lineNumber": 20,
    "module": "send",
    "order_id": "ord_42",
    "processName": "MainProcess",
    "service": "taproom",
    "threadName": "MainThread"
  }
}
```

### プロセスを止めるクラッシュを捕捉する

`install_process_error_capture()` は `sys.excepthook` にフックします。どこにも捕捉されなかった例外は、`handled: false` を持つ `critical` レベルの問題として、プロセスが終了コード 1 で終了する前に記録されます。

```bash
import atexit
import json
import os

from logbrew_sdk import LogBrewClient, RecordingTransport, install_process_error_capture

transport = RecordingTransport.always_accept()
client = LogBrewClient.create(api_key=os.environ["LOGBREW_SERVER_API_KEY"], transport=transport)
install_process_error_capture(client, on_capture_error=print)


@atexit.register
def show_captured_issue() -> None:
    issue = json.loads(transport.last_body())["events"][0]["attributes"]
    print(json.dumps({k: issue[k] for k in ("title", "level")}), json.dumps(issue["exception"]))


kegs = {"ipa": 3}
kegs["stout"] -= 1
```

出力

```text
Traceback (most recent call last):
  File "<stdin>", line 19, in <module>
KeyError: 'stout'
{"title": "KeyError", "level": "critical"} {"type": "KeyError", "mechanism": {"type": "python.excepthook", "handled": false}}
```

## 実際に試して確認したこと

設定なしでは、ルートロガーへの 5 回の呼び出しのうち 3 行が stderr に出力され、DEBUG と INFO の呼び出しは破棄されました。既定の行は `WARNING:root:keg below 10%` のように、レベル、ロガー名、メッセージの並びです。

2 回目の `basicConfig()` 呼び出しは、`force=True` を渡すまで何も変えませんでした。LogBrew のハンドラー経由では、DEBUG と INFO のレコードは `info`、WARNING は `warning`、ERROR は `error`、CRITICAL は `critical` として届きました。

`log.exception()` は LogBrew のログに `exceptionName` と `exceptionMessage` を追加しました。ハンドラーに `include_exception_text=True` を渡すと、トレースバック全文が `exceptionText` として届きました。

```bash
import logging

logging.debug("keg pressure read")
logging.info("tap opened")
logging.warning("keg below 10%")
logging.error("keg empty")
logging.critical("cooler offline")
```

出力

```text
WARNING:root:keg below 10%
ERROR:root:keg empty
CRITICAL:root:cooler offline
```

## 5 つのログレベルと使い分け

DEBUG は、ケグの圧力、SQL 文、再試行回数など、バグ修正のときに必要な値のためのレベルです。本番環境では無効にします。

INFO は、タップを開けた、注文が支払われたなど、プログラムが行ったことを記録します。WARNING は、ケグの残量が 10% を下回ったなど、問題が起きたものの対処済みのことを記録します。ERROR は、ケグが空だったなど、失敗したリクエストやジョブを記録します。CRITICAL は、冷却装置が止まったなど、プログラムを停止させた事象を記録します。

各例外は、処理する場所で 1 回だけ記録します。コールスタックの各段で 1 行ずつ記録すると、1 件の失敗に対して 3 件のアラートが飛びます。

```bash
import logging

for name in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
    print(name, logging.getLevelName(name))
print("root logger level:", logging.getLevelName(logging.getLogger().level))
```

出力

```text
DEBUG 10
INFO 20
WARNING 30
ERROR 40
CRITICAL 50
root logger level: WARNING
```

## LogBrew が記録する内容

各レコードは、レベル、ロガー名、ファイル名、関数、行番号、モジュールを持つ 1 件のログになります。`extra={}` のプリミティブ値はフィルターできるフィールドになり、ハンドラーの `metadata={}` は上の `service` のように、すべての行にフィールドを追加します。

例外がある場合、ログには `exceptionName` と `exceptionMessage` が付きます。トレースバックのテキストも保存するには、ハンドラーに `include_exception_text=True` を渡します。

`install_process_error_capture()` は、捕捉されなかった例外を、例外の型、スタックフレーム、`python.excepthook` という仕組みの情報を持つ `critical` レベルの問題に変えます。

## よくある質問

### Python の既定のログレベルは何ですか？

WARNING です。`basicConfig()` を呼び出さない場合、ルートロガーは WARNING、ERROR、CRITICAL を `WARNING:root:message` の形で stderr に出力し、DEBUG と INFO は破棄します。

### logging.basicConfig が何もしないのはなぜですか？

以前の `basicConfig()` 呼び出しやライブラリによって、ルートロガーにすでにハンドラーがあるため、Python はその呼び出しを無視します。ハンドラーを置き換えるには `force=True` を渡します。

### logging.error と logging.exception の違いは何ですか？

`log.exception()` は `exc_info=True` 付きの `log.error()` です。ERROR レベルで記録し、トレースバックを添付します。`except` ブロックの中でだけ呼び出します。

### Python のログレベルの順序は？

DEBUG 10、INFO 20、WARNING 30、ERROR 40、CRITICAL 50 です。ロガーは、設定したレベル以上の番号を持つレコードを通します。

### Python では print() と logging のどちらを使うべきですか？

`logging` を使います。`print()` には、レベル、タイムスタンプ、ロガー名、ハンドラーがありません。`logging` なら、モジュールごとに行を有効にして、ファイルや LogBrew に送れます。

## Python のすべての例外をトレースバックつきで確認する

LogBrew の Python SDK は、各ログ行をレベル、ロガー、ファイル、行番号つきで送り、捕捉されなかった例外をスタックトレースつきの問題に変えます。十分な無料プランで始められ、カードは不要です。

[無料で始める](https://logbrew.co/ja)

## 出典

- [Python docs, Logging HOWTO](https://docs.python.org/3/howto/logging.html)
- [Python docs, logging module](https://docs.python.org/3/library/logging.html)
- [python/cpython#78078](https://github.com/python/cpython/issues/78078)

## エージェントと Python のその他のガイド

- [Claude Code と Codex に MCP サーバーを追加する](https://logbrew.co/ja/guides/mcp-server-claude-code-codex)
- [Claude Code と Codex で AGENTS.md を使う](https://logbrew.co/ja/guides/agents-md)
- [Claude の overloaded エラー 529 とレート制限エラー 429 を解決する](https://logbrew.co/ja/guides/claude-overloaded-rate-limit-error)
- [構造化ログでフィールドから検索できるログの書式とログレベルを整える](https://logbrew.co/ja/guides/structured-logging)
- [Claude Code、Codex、JavaScript、Python のエラーを解決する](https://logbrew.co/ja/guides)
