ログライブラリを比較すると、どうしても「どれが速いのか」に寄ります。
速さは大事です。遅すぎるロガーは使いにくいです。
でも、ベンチマークを最速ランキングとして読むと、ログ設計の大事な部分を見落とします。
ここでは、D-SafeLogger の公開 BENCHMARK.md を例に、ログライブラリのベンチマークをどう読むかを見ていきます。
この記事で見るベンチマーク
この記事では、D-SafeLogger リポジトリの公開 BENCHMARK.md に載っている選定結果を引用します。手元で再測定した値ではなく、公開時点の集計結果を読み解いていきます。
| 項目 | 内容 |
|---|---|
| 単一プロセス | benchmark_20260506_180018 |
| マルチプロセス性能 | benchmarks_multi_perf_20260506_190518 |
| マルチプロセス障害寄り | benchmarks_multi_resilience_20260523_084326 |
| Python | 3.13 / 3.14、GIL 有効 / 無効 |
| 単一プロセス条件 | messages=100000、repeat=3、threads=8、text / JSON、single-thread / multi-thread |
| D-SafeLogger | 公開リポジトリの pyproject.toml では 0.4.0 |
| OS | BENCHMARK.md には明記なし。この記事では OS 差の主張には使わない |
連載内の Q3 実装例は D-SafeLogger 0.4.1 で確認していますが、この記事のベンチマーク値は BENCHMARK.md の採取時点に合わせて 0.4.0 の結果として見ます。
ベンチマークの数字は、実行環境や設定で変わります。ポイントは「この条件では何が見えるか」という判断材料です。
最初に見るべきは目的
ログライブラリには、それぞれ得意な責務があります。
- 低遅延で出力したい
- 構造化ログを組みたい
- 外部の収集基盤へ出力したい
- ローカルファイルを日次で残したい
- マルチプロセスの子プロセスのログを集約したい
- 障害時に配送状態を説明したい
目的が違うのに、スループットだけで並べると判断を間違えます。
D-SafeLogger が強い数字
単一プロセスでは、D-SafeLogger の async 構成が強い条件があります。
公開 BENCHMARK.md では、単一プロセスの比較可能な 16 条件について、次のように整理されています。
- D-SafeLogger async は D-SafeLogger sync に対し、スループットで 16/16、p50 遅延で 16/16 勝っている
- 全 16 条件のスループット首位は、D-SafeLogger async が 8/16、structlog sync が 5/16、stdlib logging async が 3/16
- D-SafeLogger の最良モードは、p50 遅延で 12/16 条件の最小値を取っている
代表値だけ抜くと、こうです。
| 条件 | D-SafeLogger async | 比較対象 | 読み方 |
|---|---|---|---|
| Python 3.14 / GIL 有効 / single-thread / text | 51,554 msg/s、p50 16.7 us | stdlib logging async: 45,948 msg/s、p50 18.6 us | 単一プロセスの低遅延用途では強い |
| Python 3.14 / GIL 有効 / single-thread / JSON | 52,081 msg/s、p50 16.7 us | stdlib logging async: 46,305 msg/s、p50 18.5 us | JSON でも async 構成は競争力がある |
ここから言えるのは、次の範囲です。
D-SafeLogger の非同期構成は、単一プロセスのローカルファイル出力で競争力がある。
事実としては、これだけです。「常に最速」とは言えません。
stdlib logging が勝つ数字
では、逆方向の数字も見て見ましょう。
マルチプロセス性能では、単純なスループットの最速は D-SafeLogger ではありません。公開 BENCHMARK.md では、マルチプロセス性能プロファイルで stdlib logging が全 measured cells のスループットを取った、と整理されています。
平均スループットの例です。
| パターン | シナリオ | D-SafeLogger avg | stdlib avg | loguru avg |
|---|---|---|---|---|
| root_p1 | text | 797 msg/s | 1,280 msg/s | 995 msg/s |
| root_p1 | JSON | 803 msg/s | 1,253 msg/s | 1,009 msg/s |
| root_p8 | text | 468 msg/s | 852 msg/s | 650 msg/s |
| root_p8 | JSON | 456 msg/s | 802 msg/s | 656 msg/s |
単純なマルチプロセスのスループットだけが目的なら、stdlib logging の方が合う条件があります。
マルチプロセスは、責務で読む
ここで参照している D-SafeLogger は、stdlib logging 互換を保ちながら、追記専用の出力先切り替え、ハッシュ、一覧情報ファイル、マルチプロセス時の親プロセス書き込みを扱う Python のロガーです。
このマルチプロセス構成は、次のコストを払います。
- 子プロセスから書き込み役へのプロセス間通信
- ログレコードの直列化
- 出力先の振り分け
- 書き込み役側での再構成
- 出力先での分類
- 終了時の集計
このコストは、速さだけを見ると不利です。
その代わりに得られるものがあります。
- 子プロセスが共有ログファイルを直接開かない
- 親プロセス側の書き込み役が出力先を所有する
- 出力先の切り替え / 補助ファイル / 一覧情報ファイルを一箇所で扱う
- 配送状態を分類する
- 実行時の警告や終了時の報告を残す
必要なのが単純なスループットだけなら、より単純な構成の方がパフォーマンスは上がります。
障害寄りのベンチマークを見る
ログ運用で本当に気になるのは、正常系だけではありません。
- 出力先が一時的に使えない
- キューが詰まる
- 子プロセスが落ちる
- 終了処理が混ざる
こういう条件で、何が起きたかを説明できるかが重要です。
公開 BENCHMARK.md では、マルチプロセス障害寄りの選定結果について、D-SafeLogger が 16/16 の summary rows で分類項目を出し、16/16 行を説明可能として扱えた、と整理されています。これは、上で見た単一プロセスの 16 条件とは別の集計です。
ここでいう 16 行は、代表的には次の組み合わせです。
| 軸 | 内容 |
|---|---|
| Python / GIL | Python 3.13 GIL 有効、3.13 GIL 無効、3.14 GIL 有効、3.14 GIL 無効 |
| 障害シナリオ |
burst_backpressure、ipc_forced_disconnect、rolling_restart_mixed_shutdown、sink_temporarily_unavailable
|
「説明可能」は、「障害が起きない」という意味ではありません。
少なくとも Writer が受け入れたレコードについて、delivered、partial_delivered、known_rejected、known_dropped、unexplained_lost のような終端状態で分類が閉じる、という意味です。rolling_restart_mixed_shutdown のように worker が途中で落ちる条件では、attempted 側の報告が不完全になることもあります。そこまで含めて、「成功率」ではなく「何を説明できたか」に着目します。
代表行だけ抜くと、こうです。
| シナリオ | attempted | accepted | delivered | known_rejected | known_dropped | unexplained_lost | 読み方 |
|---|---|---|---|---|---|---|---|
burst_backpressure |
100 | 100 | 100 | 0 | 0 | 0 | 負荷集中でも分類付きで閉じた |
sink_temporarily_unavailable |
100 | 100 | 0 | 100 | 0 | 0 | 出力先拒否として数えられた |
rolling_restart_mixed_shutdown |
50 | 62 | 62 | 0 | 0 | 0 | worker crash により attempted 側が過少計上され、accepted を下回る |
これは「失わない」という主張ではありません。
障害時に、既知の拒否、既知の取りこぼし、説明できない欠落などとして観測できる、という主張です。
最小の再現手順
ベンチマークを手元で動かすなら、公開 BENCHMARK.md では次の流れになっています。
uv sync --group benchmark
uv run python benchmarks/run_benchmark.py
マルチプロセスの障害寄りプロファイルだけを見るなら、次のコマンドです。
uv sync --group benchmark
uv run python benchmarks/run_multiprocess_compare_v23a.py --repo-root . --profile resilience_profile --messages 100 --repeat 1
これはハーネスの動作確認用で、公開値そのものを再生成するコマンドではありません。
選定済み summary を再生成する場合は、manifest に従って次を実行します。
uv run python benchmarks/update_summary.py
ただし、新しい実行結果が出たからといって、自動的に公開 BENCHMARK.md の主張が更新されるわけではありません。どのセッションを代表値として採用するかを manifest と本文の両方で確認する必要があります。
採用判断の表
| 条件 | 見るべき点 |
|---|---|
| 単一プロセスでローカルファイルに出す | 遅延、設定の分かりやすさ |
| 標準出力 / 標準エラーに出す | 収集基盤側の責務 |
| マルチプロセスの子プロセスがいる | ファイル所有と終了処理 |
| 監査寄りにファイルを残す | 出力先の切り替え、ハッシュ、一覧情報ファイル |
| 単純なスループットが最優先 | 最速の実装を別途比較 |
| 障害調査を重視 | 拒否、取りこぼし、欠落の追いやすさ |
ベンチマークは、目的に合わせて読むものです。速度ランキングではなく、そのロガーが自分の要件に合うかを見る材料として扱う方が実務では役に立ちます。
自分なら、まず次の順番で見ます。
- 自分の用途が単一プロセスなのか、マルチプロセスなのか
- ローカルファイルを成果物として残す必要があるのか
- 速度が少し落ちても、配送状態を追いたいのか
- 障害時に「何が起きたか」を説明する必要があるのか
この順番で見ると、ベンチマークの数字が「勝ち負け」ではなく、採用条件の確認に近づきます。
まとめ
ログライブラリのベンチマークは、最速ランキングではなく設計判断の材料として読む方が役に立ちます。
D-SafeLogger を例にすると、単一プロセスの非同期構成は競争力があります。一方でマルチプロセスの単純なスループットは stdlib logging が勝つ条件があります。
だから、数字の前に「何を担当させたいのか」を決める方が大事です。速さだけを見るのか、ローカルファイルの所有まで任せるのか、障害時の配送状態まで追いたいのかで、同じベンチマークの読み方は変わります。
参考
- D-SafeLogger BENCHMARK.md: https://github.com/nightmarewalker/D-SafeLogger/blob/main/BENCHMARK.md
- Python logging: https://docs.python.org/3/library/logging.html
関連記事
Qiita 連載:
- #1 ログローテーションで active file を rename するの、本当に安全ですか?
- #2 logger.info(...) を書き換えずに、ログ運用だけ変えたい
- #3 子プロセスに同じログファイルを開かせる前に考えること
- #4 ログライブラリのベンチマークを「速い順」で読んではいけない(本記事)
Zenn 連載:
- #1 ログファイルの切り替えは、rename ではなく「次の書き先の選択」として設計する
- #2 ログ設定は初期化コードではなく、運用契約として設計する
- #3 ログ欠落は、防ぐものではなく「説明できるもの」として設計する
- #4 stdoutに出せば済むログと、ローカルファイルに残すべきログ(近日公開予定)