🚀 Loguru: Pythonロギングをもっと楽しく、もっと簡単に!詳細解説 🐍

技術解説

Pythonでの開発において、ログの取得はデバッグやアプリケーションの監視に不可欠な要素です。しかし、標準のloggingモジュールは設定が少々複雑で、「面倒だからprint()で済ませてしまおう…」と思った経験はありませんか?😅

そんな悩みを解決してくれるのが、今回ご紹介するLoguruです!Loguruは「Pythonのロギングを(驚くほど)簡単に、そして楽しくする」ことを目指して開発されたライブラリです。そのシンプルさと強力な機能で、多くの開発者に支持されています。

このブログでは、Loguruの基本的な使い方から高度な機能まで、具体的なコード例を交えながら徹底的に解説していきます。Loguruを使えば、もうロギングで悩むことはありません!💪

1. Loguruとは? なぜ選ぶべきなのか? 🤔

Loguruは、Pythonのサードパーティ製ロギングライブラリです。標準のloggingモジュールと比較して、以下のような特徴とメリットがあります。

  • 圧倒的なシンプルさ: from loguru import logger と書くだけで、すぐにログ出力が始められます。複雑な設定は不要です。
  • 設定の一元化: 標準loggingのようなHandler, Formatter, Filterといった概念を意識する必要がありません。logger.add()関数一つで、出力先、フォーマット、レベル、フィルタリングなどを設定できます。
  • 豊富な機能: ファイルローテーション、圧縮、保持期間設定、非同期・スレッドセーフ・マルチプロセスセーフなロギング、美しい色付き出力、詳細な例外トレースバック、構造化ログ(JSON)、遅延評価など、モダンなアプリケーション開発に必要な機能が最初から組み込まれています。
  • 優れた例外処理: @logger.catch() デコレータやコンテキストマネージャを使えば、関数やコードブロック内で発生した例外を自動的にキャッチし、詳細なトレースバック情報(変数値を含む!)とともにログに出力できます。これによりデバッグが格段に楽になります。
  • モダンなフォーマット: Python 3.6以降で標準となったf-stringのような{}形式の文字列フォーマットを採用しており、直感的で強力です。
  • 標準loggingとの互換性: 既存のloggingモジュールで書かれたコードと連携することも可能です。

標準loggingとの簡単な比較

標準のloggingでファイルとコンソールに異なるレベルでログを出力する場合、以下のような設定が必要です。


import logging

# ロガーの取得
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# ファイルハンドラの設定
fh = logging.FileHandler("app_standard.log")
fh.setLevel(logging.DEBUG)

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

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

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

logger.info("これは情報レベルのログです(ファイルのみ)")
logger.error("これはエラーレベルのログです(ファイルとコンソール)")
        

一方、Loguruではこれだけです。


from loguru import logger
import sys

# デフォルトのハンドラを削除(任意)
# logger.remove()

# ファイルシンク(出力先)の設定
logger.add("app_loguru.log", level="DEBUG", format="{time} - {name} - {level} - {message}")

# コンソールシンクの設定
logger.add(sys.stderr, level="ERROR", format="{time} - {name} - {level} - {message}")

logger.info("これは情報レベルのログです(ファイルのみ)")
logger.error("これはエラーレベルのログです(ファイルとコンソール)")
        

コードの簡潔さは一目瞭然ですね!✨ Loguruがいかに「ボイラープレート(お決まりのコード)を排除し、すぐに使える」ように設計されているかが分かります。

2. 基本的な使い方 🛠️

インストール

まずはLoguruをインストールしましょう。pipを使えば簡単です。


pip install loguru
        

簡単なログ出力

インストールしたら、すぐにログを出力できます。loggerオブジェクトをインポートして、ログレベルに対応するメソッドを呼び出すだけです。


from loguru import logger

logger.debug("これはデバッグレベルのメッセージです。")
logger.info("これは情報レベルのメッセージです。")
logger.success("これは成功レベルのメッセージです。🎉") # Loguru独自のレベル
logger.warning("これは警告レベルのメッセージです。")
logger.error("これはエラーレベルのメッセージです。")
logger.critical("これは致命的なエラーレベルのメッセージです。")

# 変数を含むメッセージも簡単
user = "Alice"
age = 30
logger.info(f"ユーザー名: {user}, 年齢: {age}")
# {} 形式のフォーマットも使える
logger.info("ユーザー名: {}, 年齢: {}", user, age)
        

デフォルトでは、INFOレベル以上のログが標準エラー出力(sys.stderr)に、色付きで分かりやすく表示されます。DEBUGレベルのメッセージは表示されません。

出力例:


2025-04-02 05:00:00.123 | INFO     | __main__:<module>:5 - これは情報レベルのメッセージです。
2025-04-02 05:00:00.123 | SUCCESS  | __main__:<module>:6 - これは成功レベルのメッセージです。🎉
2025-04-02 05:00:00.123 | WARNING  | __main__:<module>:7 - これは警告レベルのメッセージです。
2025-04-02 05:00:00.123 | ERROR    | __main__:<module>:8 - これはエラーレベルのメッセージです。
2025-04-02 05:00:00.123 | CRITICAL | __main__:<module>:9 - これは致命的なエラーレベルのメッセージです。
2025-04-02 05:00:00.123 | INFO     | __main__:<module>:13 - ユーザー名: Alice, 年齢: 30
2025-04-02 05:00:00.123 | INFO     | __main__:<module>:15 - ユーザー名: Alice, 年齢: 30
        

(時刻やファイル名、行番号は実行環境によって異なります)

ログレベル

Loguruは標準のloggingモジュールと同様のログレベルに加え、TRACESUCCESSという独自のレベルを持っています。重要度は低い順に以下の通りです。

  • TRACE (5)
  • DEBUG (10)
  • INFO (20) – デフォルトレベル
  • SUCCESS (25)
  • WARNING (30)
  • ERROR (40)
  • CRITICAL (50)

ログレベルを変更するには、logger.add()を使います。デフォルトのハンドラ(標準エラー出力)を変更したい場合は、一度logger.remove()で削除してから追加します。


from loguru import logger
import sys

# デフォルトハンドラを削除
logger.remove()

# DEBUGレベル以上を標準エラー出力に表示するハンドラを追加
logger.add(sys.stderr, level="DEBUG")

logger.trace("これはトレースレベルです (表示されない)") # TRACE < DEBUG のため
logger.debug("デバッグメッセージが表示されるようになりました!")
logger.info("もちろんINFOも表示されます。")
        

3. 主要機能の詳細解説 🌟

Loguruの真価はその豊富な機能にあります。ここでは主要な機能を詳しく見ていきましょう。

Sink (出力先): logger.add()の基本

Loguruでは、ログの出力先やその設定を「Sink(シンク)」と呼び、logger.add()メソッドで追加・設定します。このメソッドがLoguruの中心的な役割を担います。

sink引数には様々なタイプの値を指定できます。

  • ファイルパス (文字列 or pathlib.Path): 指定されたファイルにログを出力します。例: logger.add("file.log")
  • ファイルライクオブジェクト: write()メソッドを持つオブジェクト。例: logger.add(sys.stderr) (デフォルト), logger.add(open("other.log", "w"))
  • 関数/メソッド: ログメッセージを受け取る関数を指定できます。例: logger.add(my_custom_sink_function)
  • コルーチン関数 (async def): 非同期処理でログを扱えます。
  • 標準のlogging.Handler: 既存のloggingハンドラも利用可能です。

ファイルへの出力


from loguru import logger

# ログを "app.log" ファイルに出力
logger.add("app.log")

# {time} プレースホルダーでタイムスタンプ付きファイル名も可能
logger.add("logs/app_{time:YYYY-MM-DD}.log") # 実行時の日付でファイル作成

logger.info("このメッセージはコンソール(stderr)とファイルの両方に出力されます。")
logger.warning("警告メッセージも同様です。")
        

デフォルトでは、logger.add()で追加したシンクは、既存のデフォルトシンク(標準エラー出力)に加えて動作します。もしファイルだけに記録したい場合は、logger.remove()を先に呼び出してデフォルトシンクを削除します。


from loguru import logger

logger.remove() # デフォルトのstderrシンクを削除
logger.add("file_only.log")
logger.info("このメッセージは file_only.log にのみ出力されます。")
        

フォーマット (format)

ログメッセージの形式はformat引数でカスタマイズできます。{}で囲まれたプレースホルダーを使って、様々な情報を埋め込めます。


from loguru import logger
import sys

logger.remove()

# カスタムフォーマットで標準エラー出力に追加
custom_format = (
    "{time:YYYY-MM-DD HH:mm:ss.SSS} | "
    "{level: <8} | "
    "{name}:{function}:{line} - {message}"
)
logger.add(sys.stderr, format=custom_format, level="INFO", colorize=True)

# ファイルにも別のシンプルなフォーマットで出力
logger.add("simple.log", format="{time:HH:mm:ss} | {level} | {message}", level="DEBUG")

def my_function():
    logger.debug("デバッグ情報をファイルに記録")
    logger.info("情報をコンソールとファイルに記録")
    logger.warning("警告も両方に")

my_function()
        

主なフォーマット変数:

  • {time}: ログ発生時刻 (書式指定可能: 例 {time:YYYY-MM-DD HH:mm:ss})
  • {level}: ログレベル名 (書式指定可能: 例 {level: <8} 左寄せ8文字)
  • {level.icon}: ログレベルアイコン (絵文字)
  • {level.no}: ログレベル番号
  • {message}: ログメッセージ本体
  • {name}: ロガー名 (通常はモジュール名)
  • {file}: ファイル名
  • {file.path}: フルパス
  • {line}: 行番号
  • {function}: 関数名
  • {module}: モジュール名
  • {thread}: スレッドID
  • {thread.name}: スレッド名
  • {process}: プロセスID
  • {process.name}: プロセス名
  • {elapsed}: プログラム開始からの経過時間
  • {exception}: 例外情報 (トレースバック)
  • {extra}: logger.bind() で追加されたカスタムデータ (後述)

colorize=True を指定すると、ターミナル出力時にフォーマット文字列内の<level>, <green>などのタグに基づいて色が適用されます。

ファイルローテーション (rotation)

ログファイルが際限なく大きくなるのを防ぐため、Loguruは簡単にファイルローテーションを設定できます。


from loguru import logger
import time

logger.remove()

# サイズベースのローテーション: 10MBを超えたら新しいファイルを作成
logger.add("size_rotated.log", rotation="10 MB")

# 時間ベースのローテーション: 毎日正午に新しいファイルを作成
logger.add("time_rotated.log", rotation="12:00")

# 期間ベースのローテーション: 1週間ごとに新しいファイルを作成
logger.add("weekly_rotated_{time}.log", rotation="1 week")

# 組み合わせ: 100KBごと、または毎週月曜日にローテーション
# logger.add("complex_rotated.log", rotation="100 KB", filter=lambda record: record["extra"].get("name") == "complex")
# logger.add("complex_rotated.log", rotation="Monday", filter=lambda record: record["extra"].get("name") == "complex")
# ※注: 同一ファイルへの複数ローテーション条件は工夫が必要な場合があります

for i in range(5):
    logger.info(f"サイズローテーションテスト {i}")
    time.sleep(0.1)

for i in range(5):
    logger.info(f"時間・期間ローテーションテスト {i}")
    time.sleep(0.1)
        

rotation 引数には以下の形式で指定できます。

  • サイズ: "100 MB", "0.5 GB" のような文字列。
  • 時間間隔: "1 day", "1 week", "2 months" のような文字列。datetime.timedelta オブジェクトも可。
  • 時刻: "12:00", "00:00" のような文字列。datetime.time オブジェクトも可。
  • 曜日: "Monday", "Sunday at 00:00" のような文字列。
  • Callable (関数): ファイルオブジェクトとログレコードを引数に取り、ローテーションすべき場合に True を返す関数。

ログファイルの保持 (retention)

ローテーションされた古いログファイルを自動的に削除する機能です。


from loguru import logger

logger.remove()

# 古いログを10日間保持
logger.add("retained_10days.log", rotation="1 day", retention="10 days")

# 最新の5ファイルを保持
logger.add("retained_5files.log", rotation="10 MB", retention=5)

# Callable も指定可能
def custom_retention(logs):
    # 例: ".zip" 拡張子のファイルは保持する
    to_keep = [log for log in logs if log.endswith(".zip")]
    for log in logs:
        if log not in to_keep:
            # 削除処理 (os.removeなど)
            pass # ここでは省略
# logger.add("custom_retained.log", rotation="1 day", retention=custom_retention)

logger.info("保持期間テスト")
        

retention 引数には以下の形式で指定できます。

  • 期間: "1 month", "3 weeks" のような文字列。datetime.timedelta オブジェクトも可。
  • ファイル数 (整数): 保持する最新のファイル数を指定。
  • Callable (関数): ログファイルパスのリストを受け取り、削除すべきファイルのリストを返す関数。

ログファイルの圧縮 (compression)

ローテーション時にログファイルを圧縮してディスクスペースを節約できます。


from loguru import logger

logger.remove()

# ローテーション時にgzip形式で圧縮
logger.add("compressed_log.gz", rotation="1 MB", compression="gz")

# zip形式で圧縮
logger.add("compressed_log.zip", rotation="1 MB", compression="zip")

# 他にも bz2, xz, tar, tar.gz, tar.bz2, tar.xz が利用可能

# Callableも指定可能 (カスタム圧縮処理)
def custom_compressor(filepath):
    # 例: 独自の圧縮処理を実行
    print(f"Compressing {filepath} with custom logic...")
    # shutil.make_archive(...) など
    # os.remove(filepath) # 元ファイルを削除
# logger.add("custom_compressed.log", rotation="1 MB", compression=custom_compressor)

logger.info("圧縮テスト")
        

compression 引数には、対応する拡張子("gz", "zip", "bz2", "xz", "tar", "tar.gz", "tar.bz2", "tar.xz")またはカスタム圧縮関数を指定します。

シリアライズ (serialize) – 構造化ログ

ログメッセージをJSON形式で出力したい場合にserialize=Trueを指定します。これにより、ログデータを機械的に処理・分析しやすくなります。


from loguru import logger
import json

logger.remove()

# 標準エラー出力にJSON形式で出力
logger.add(sys.stderr, serialize=True)

# ファイルにもJSON形式で出力
logger.add("structured.json", serialize=True, rotation="10 KB")

data = {"user_id": 123, "action": "login", "status": "success"}
logger.info("ユーザーログイン", extra=data) # extraを使って構造化データを渡す

try:
    result = 10 / 0
except ZeroDivisionError:
    logger.exception("計算エラー発生", extra={"input": 0})

        

出力例 (stderr):


{"text": "2025-04-02 05:00:00.456 | INFO     | __main__:<module>:11 - ユーザーログイン\n", "record": {"elapsed": {"repr": "0:00:00.012345", "seconds": 0.012345}, "exception": null, "extra": {"user_id": 123, "action": "login", "status": "success"}, "file": {"name": "main.py", "path": "/path/to/your/main.py"}, "function": "<module>", "level": {"icon": "\u2139\ufe0f", "name": "INFO", "no": 20}, "line": 11, "message": "ユーザーログイン", "module": "main", "name": "__main__", "process": {"id": 12345, "name": "MainProcess"}, "thread": {"id": 67890, "name": "MainThread"}, "time": {"repr": "2025-04-02T05:00:00.456789+00:00", "timestamp": 1743493200.456789}}}
{"text": "2025-04-02 05:00:00.457 | ERROR    | __main__:<module>:15 - 計算エラー発生\nTraceback (most recent call last):\n...\nZeroDivisionError: division by zero\n", "record": {"elapsed": {"repr": "0:00:00.013456", "seconds": 0.013456}, "exception": {"type": "ZeroDivisionError", "value": "division by zero", "traceback": true}, "extra": {"input": 0}, "file": {"name": "main.py", "path": "/path/to/your/main.py"}, "function": "<module>", "level": {"icon": "\u26d4", "name": "ERROR", "no": 40}, "line": 15, "message": "計算エラー発生", "module": "main", "name": "__main__", "process": {"id": 12345, "name": "MainProcess"}, "thread": {"id": 67890, "name": "MainThread"}, "time": {"repr": "2025-04-02T05:00:00.457890+00:00", "timestamp": 1743493200.457890}}}
        

recordフィールド内に詳細なコンテキスト情報が含まれているのがわかります。

例外のフォーマット (backtrace, diagnose)

Loguruの強力な機能の一つが、例外発生時の詳細なトレースバック出力です。logger.exception()メソッドや@logger.catch()デコレータを使うと自動的に有効になりますが、logger.add()で設定することも可能です。

  • backtrace=True (デフォルト: True): 例外が発生した箇所だけでなく、呼び出し元のスタックトレースも表示します。
  • diagnose=True (デフォルト: True): スタックトレース内の各フレームにおける変数の値を表示します。デバッグに非常に役立ちますが、本番環境では機密情報が漏洩する可能性があるためFalseに設定することを強く推奨します。

from loguru import logger
import sys

logger.remove()

# デバッグ用設定: 詳細なトレースバックをコンソールに出力
# 本番環境では diagnose=False にすること!
logger.add(sys.stderr, backtrace=True, diagnose=True, level="DEBUG", colorize=True)

# 本番用ファイル設定: 変数値は記録しない
logger.add("prod_error.log", backtrace=True, diagnose=False, level="ERROR", rotation="1 MB")

def func_a(x):
    value = x * 2
    return func_b(value)

def func_b(y):
    result = 100 / (y - 4) # y=4 の場合に ZeroDivisionError
    return result

@logger.catch # デコレータを使えば logger.add の設定に関わらず詳細トレースバックが出力される
def main_task(input_val):
    logger.info(f"処理開始: input={input_val}")
    try:
        result = func_a(input_val)
        logger.success(f"処理成功: result={result}")
    except Exception as e:
        # logger.exception() を使うと add() の設定が適用される
        logger.exception("メインタスクでエラー発生")
        # 代わりに @logger.catch を使うのが一般的

main_task(5) # 成功するケース
main_task(2) # func_b でエラーが発生するケース
        

@logger.catchlogger.exception() を使用すると、エラー発生時の状況(どの関数で、どの行で、どの変数がどんな値だったか)が一目瞭然となり、デバッグ効率が大幅に向上します。🚀

非同期・スレッドセーフ・マルチプロセスセーフ (enqueue)

Loguruのシンクはデフォルトでスレッドセーフです。複数のスレッドから同時にログを書き込んでも、メッセージが壊れることはありません。

しかし、マルチプロセスセーフではありません。複数のプロセスから同じファイルに書き込むと、ログが破損する可能性があります。

また、ログの書き込み処理(特にファイルI/O)は、メインスレッドの処理をブロックする可能性があります。

これらの問題を解決するのがenqueue=Trueオプションです。


from loguru import logger
import time
import threading
from multiprocessing import Process

# enqueue=True を指定すると、ログメッセージは内部キューに入れられ、
# バックグラウンドのスレッド/プロセスが実際の書き込み処理を行う。
# これにより、非同期・マルチプロセスセーフなロギングが実現できる。
logger.add("queued_log.log", enqueue=True, rotation="1 MB")

def worker_thread(thread_id):
    for i in range(3):
        logger.info(f"スレッド {thread_id} からのメッセージ {i}")
        time.sleep(0.05)

def worker_process(process_id):
    # プロセスごとにloggerをインポートしなおすか、適切に渡す必要がある場合も
    from loguru import logger
    # 重要: enqueue=True で設定されたシンクはプロセス間で共有されるわけではない。
    # 各プロセスが独立してキューイングし、最終的に1つのファイルに書き込むような
    # 構成が必要な場合は、QueueHandlerなど標準loggingの仕組みや
    # それをLoguruから利用するカスタムシンクを検討する必要がある。
    # ここでは単純化のため、各プロセスが独立してログを試みる例を示す。
    # logger.add("process_log.log", enqueue=True) # これだとプロセスごとにファイルが作られる可能性
    for i in range(3):
        # 実際には共有ログファイルへの書き込みはより複雑な設定が必要
        logger.info(f"プロセス {process_id} からのメッセージ {i} (queued_log.log へ)")
        time.sleep(0.1)

# スレッドのテスト
threads = []
for i in range(3):
    t = threading.Thread(target=worker_thread, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

# マルチプロセスのテスト (注意点あり)
# if __name__ == "__main__": # マルチプロセスでは必須
#     processes = []
#     for i in range(2):
#         p = Process(target=worker_process, args=(i,))
#         processes.append(p)
#         p.start()
#
#     for p in processes:
#         p.join()

# 非同期処理 (asyncioなど) で logger を使用する場合も enqueue=True が推奨される
# await logger.complete() を呼び出してキューが空になるのを待つことができる
async def async_task():
    logger.info("非同期タスク開始")
    # await asyncio.sleep(0.1)
    logger.info("非同期タスク終了")

# import asyncio
# asyncio.run(async_task())
# logger.complete() # キューの完了を待つ

print("メイン処理完了")
        

enqueue=Trueを使用する際の注意点:

  • ログメッセージは一度メモリ上のキューに格納されます。アプリケーションがクラッシュした場合、キューに残っていたメッセージは失われる可能性があります。
  • 非同期処理でasync defなシンク関数を使う場合や、プログラム終了時に確実に全てのログが書き込まれるようにしたい場合は、logger.complete()を呼び出してキューが空になるのを待つ必要があります。
  • 真のマルチプロセスロギング(複数プロセスが一つのログファイルに安全に書き込む)を実現するには、enqueue=Trueだけでは不十分な場合があります。標準loggingQueueHandlerQueueListener、あるいはそれをLoguruから利用するカスタムシンクの実装が必要になることがあります。

4. 高度な使い方とTips ✨

コンテキスト情報の追加 (bind, patch, contextualize)

ログメッセージに、リクエストID、ユーザーID、セッションIDなどの動的なコンテキスト情報を追加したい場合があります。

logger.bind()

bind()メソッドを使うと、特定のコンテキスト情報を持つ新しいロガーオブジェクトを作成できます。この情報はそのロガーから出力される全てのメッセージのextra辞書に追加されます。


from loguru import logger
import uuid

logger.remove()
# extra情報をフォーマットに含める
logger.add(sys.stderr, format="{time} | {level} | {extra[request_id]} | {message}", level="INFO")

def handle_request(request_data):
    request_id = str(uuid.uuid4())[:8]
    # request_id を束縛した新しいロガーを作成
    bound_logger = logger.bind(request_id=request_id)

    bound_logger.info(f"リクエスト処理開始: {request_data}")
    # ... 処理 ...
    if "error" in request_data:
        bound_logger.error("リクエスト処理中にエラー発生")
    else:
        bound_logger.success("リクエスト処理完了")

handle_request({"data": "some info"})
handle_request({"error": "something went wrong"})
        

出力例:


2025-04-02 05:00:00.789 | INFO     | a1b2c3d4 | リクエスト処理開始: {'data': 'some info'}
2025-04-02 05:00:00.789 | SUCCESS  | a1b2c3d4 | リクエスト処理完了
2025-04-02 05:00:00.789 | INFO     | e5f6a7b8 | リクエスト処理開始: {'error': 'something went wrong'}
2025-04-02 05:00:00.789 | ERROR    | e5f6a7b8 | リクエスト処理中にエラー発生
        

logger.patch()

patch()メソッドを使うと、全てのログレコードに対して動的に情報を追加する関数を登録できます。これは、例えば現在のスレッド名やプロセス名を常にextraに追加したい場合に便利です。


import threading
from loguru import logger

# スレッド名を extra に追加するパッチャー関数
def thread_patcher(record):
    record["extra"]["thread_name"] = threading.current_thread().name

logger.remove()
logger.patch(thread_patcher)
logger.add(sys.stderr, format="{time} | {level} | {extra[thread_name]} | {message}", level="INFO")

def task():
    logger.info("スレッド固有のタスクを実行中")

logger.info("メインスレッドからログ")
thread = threading.Thread(target=task, name="WorkerThread-1")
thread.start()
thread.join()
        

出力例:


2025-04-02 05:00:00.901 | INFO     | MainThread | メインスレッドからログ
2025-04-02 05:00:00.901 | INFO     | WorkerThread-1 | スレッド固有のタスクを実行中
        

Loguru v0.7.0以降、patch()は累積的に適用されるようになりました。

logger.contextualize()

withステートメントと組み合わせて、特定のコードブロック内でのみ有効なコンテキスト情報を設定します。


from loguru import logger

logger.remove()
logger.add(sys.stderr, format="{time} | {level} | {extra} | {message}", level="INFO")

logger.info("コンテキスト外のログ")

with logger.contextualize(task_id="TASK-XYZ"):
    logger.info("コンテキスト内のログ (task_id付き)")
    with logger.contextualize(user_id="user123"):
        logger.info("ネストされたコンテキスト内のログ (task_idとuser_id付き)")
    logger.info("内側のコンテキストを抜けたログ (task_id付き)")

logger.info("コンテキスト外のログ (再度)")
        

出力例:


2025-04-02 05:00:01.012 | INFO     | {} | コンテキスト外のログ
2025-04-02 05:00:01.012 | INFO     | {'task_id': 'TASK-XYZ'} | コンテキスト内のログ (task_id付き)
2025-04-02 05:00:01.012 | INFO     | {'task_id': 'TASK-XYZ', 'user_id': 'user123'} | ネストされたコンテキスト内のログ (task_idとuser_id付き)
2025-04-02 05:00:01.012 | INFO     | {'task_id': 'TASK-XYZ'} | 内側のコンテキストを抜けたログ (task_id付き)
2025-04-02 05:00:01.012 | INFO     | {} | コンテキスト外のログ (再度)
        

フィルタリング (filter)

特定の条件に基づいてログメッセージを選択的に出力・非出力にする機能です。


from loguru import logger
import sys

logger.remove()

# 特定のモジュールからのログのみをファイルに出力
# filter引数にはモジュール名(文字列)を指定できる
logger.add("module_a.log", filter="my_app.module_a", level="DEBUG")

# 特定のextraデータを持つログのみをコンソールに出力
# filter引数には辞書を指定できる (extra内のキーと値が一致するか)
logger.add(sys.stderr, filter={"sensitive": False}, level="INFO", format="{message}")

# 関数を使って複雑なフィルタリング
def high_priority_filter(record):
    # "priority" extra が "high" またはレベルが ERROR 以上の場合に通す
    return record["extra"].get("priority") == "high" or record["level"].no >= logger.level("ERROR").no

logger.add("high_priority.log", filter=high_priority_filter, level="DEBUG")

# --- ログ出力の例 ---
# my_app/module_a.py 内でのログ (module_a.log に出力される)
logger.debug("Module A のデバッグ情報")

# my_app/module_b.py 内でのログ (どこにも出力されない)
logger.info("Module B の情報")

# 通常のログ (コンソールに出力)
logger.bind(sensitive=False).info("これは公開可能な情報です。")

# 機密情報を含むログ (コンソールには出力されない)
logger.bind(sensitive=True).info("これは機密情報です。")

# 優先度付きログ
logger.bind(priority="low").info("低優先度タスクのログ (high_priority.log には出力されない)")
logger.bind(priority="high").info("高優先度タスクのログ (high_priority.log に出力される)")
logger.error("エラー発生 (high_priority.log に出力される)")
        

filter引数には以下の形式で指定できます。

  • 文字列: 指定された名前(またはそのサブモジュール)からのログのみを許可します。例: "my_module"
  • 辞書: ログレコードのextra辞書が、指定された辞書の全てのキーと値を含む場合に許可します。例: {"user_id": 123}
  • 関数/Callable: ログレコードを引数に取り、許可する場合はTrue、拒否する場合はFalseを返す関数。

カスタムログレベル

独自のログレベルを定義することも可能です。


from loguru import logger
import sys

# 新しいレベル 'NOTICE' を定義 (INFOとWARNINGの間)
# level()メソッドで名前、重要度(数値)、色、アイコン(任意)を指定
logger.level("NOTICE", no=27, color="", icon="📢")

logger.remove()
logger.add(sys.stderr, level="NOTICE", colorize=True, format="{level.icon} {message}")

logger.info("これは通常の情報レベル (表示されない)") # NOTICE > INFO
logger.notice("カスタムレベルの通知メッセージです!") # 新しく定義したメソッドで出力
logger.warning("警告メッセージ (表示される)")
        

遅延評価 (lazy=True)

ログメッセージの生成自体にコストがかかる場合(例: 大きなデータ構造を整形するなど)、そのレベルのログが実際に出力されない場合は、メッセージ生成のコストを避けたいことがあります。opt(lazy=True) を使うと、メッセージ生成が遅延評価されます。


from loguru import logger
import time

logger.remove()
logger.add(sys.stderr, level="INFO") # INFOレベル以上のみ出力

def get_expensive_data():
    print("(時間のかかるデータ取得処理を実行中...)")
    time.sleep(1)
    return {"a": 1, "b": [1, 2, 3] * 10}

# 通常のログ: DEBUGレベルは出力されないが、get_expensive_data()は呼ばれてしまう
logger.debug(f"デバッグ情報: {get_expensive_data()}")

# 遅延評価: DEBUGレベルが出力されない場合、get_expensive_data()は呼ばれない
logger.opt(lazy=True).debug("遅延評価デバッグ情報: {}", lambda: get_expensive_data())

logger.info("INFOレベルのログ")
        

opt(lazy=True)を使う場合、フォーマット文字列の引数は、値を返す関数 (lambdaなど) として渡す必要があります。

標準loggingとの連携

既存のloggingモジュールベースのライブラリやコードとLoguruを連携させることができます。

標準loggingからのログをLoguruで受け取る

InterceptHandler を使うと、標準loggingモジュールへのログ出力を傍受し、Loguruのシンクにリダイレクトできます。


import logging
from loguru import logger
import sys

# Loguru の設定
logger.remove()
logger.add(sys.stderr, level="DEBUG", format="Loguru: {level} - {message}")

# 標準 logging の設定
# logging.basicConfig(level=logging.DEBUG) # これは不要になることが多い

# 標準 logging のルートロガーを取得
std_logger = logging.getLogger()
std_logger.setLevel(logging.DEBUG) # ルートロガーのレベル設定は必要

# InterceptHandler の実装例 (Loguruドキュメントより)
class InterceptHandler(logging.Handler):
    def emit(self, record):
        # logging のレベル名を Loguru のレベル名に変換
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # logging のコンテキスト情報を取得
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

# 標準ロガーに InterceptHandler を追加
std_logger.addHandler(InterceptHandler())

# --- テスト ---
logger.info("これはLoguruからのログ")
logging.warning("これは標準loggingからのログ (Loguruが受け取る)")
logging.getLogger("my_library").debug("ライブラリからのデバッグログ (Loguruが受け取る)")

try:
    1 / 0
except ZeroDivisionError:
    logging.error("標準loggingでの例外", exc_info=True) # exc_infoもLoguruで処理される
        

Loguruから標準loggingへログを送る

逆に、Loguruのログを標準loggingのハンドラに送ることも可能です。logging.Handlerを継承したクラスをLoguruのシンクとして使います。


import logging
from loguru import logger
import sys

# 標準 logging の設定 (例: ファイル出力)
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(filename='from_loguru.log', level=logging.DEBUG, format=log_format)

# Loguru のログを標準 logging に送るシンク
class PropagateHandler(logging.Handler):
    def emit(self, record):
        logging.getLogger(record.name).handle(record)

# Loguru に PropagateHandler を追加
# logger.add(PropagateHandler(), format="{message}") # formatはlogging側で行う

# ただし、Loguruレコードを直接logging.Handlerに渡すのは非推奨
# 代わりに、標準ハンドラを直接シンクとして使う
# (内部でLoguruがよしなに対応してくれる)
std_file_handler = logging.FileHandler('from_loguru_direct.log')
std_file_handler.setLevel(logging.DEBUG)
std_file_handler.setFormatter(logging.Formatter(log_format))

logger.add(std_file_handler, format="{message}", level="DEBUG") # formatは単純なものが良い

# --- テスト ---
logger.info("Loguruから標準loggingへ送信 (INFO)")
logger.warning("Loguruから標準loggingへ送信 (WARNING)")
        

このように、Loguruは既存のロギングエコシステムとも柔軟に連携できます。

5. まとめ 📝

Loguruは、Pythonにおけるロギングを劇的に簡素化し、同時に強力な機能を提供してくれる素晴らしいライブラリです。😊

Loguruのメリット:

  • 導入が非常に簡単: import logger ですぐに使える。
  • 設定がシンプル: add() 関数一つで出力先、フォーマット、レベル、ローテーション等を設定可能。
  • 豊富な機能: ファイルローテーション、圧縮、保持、色付き出力、構造化ログ(JSON)、非同期対応など。
  • 優れた例外トレースバック: @logger.catchdiagnose=True でデバッグ効率が大幅向上。
  • モダンな設計: {}形式フォーマット、柔軟なシンクとフィルタリング。
  • 標準loggingとの連携: 既存の資産とも統合可能。

Loguruの考慮点:

  • ⚠️ サードパーティライブラリ: 標準ライブラリではないため、依存関係が増える。
  • ⚠️ diagnose=True の注意: 本番環境での利用は情報漏洩のリスクがある。
  • ⚠️ 真のマルチプロセスロギング: enqueue=True だけでは不十分な場合があり、追加の工夫が必要になることがある。

多くの場合、特に新規プロジェクトや、標準loggingの設定に煩わしさを感じている場合には、Loguruは非常に魅力的な選択肢となるでしょう。そのシンプルさと強力な機能は、開発体験を向上させ、アプリケーションの品質を高めるのに役立ちます。

ぜひ、あなたの次のPythonプロジェクトでLoguruを試してみて、その「楽しさ」と「簡単さ」を体験してください!🚀

より詳しい情報や最新情報については、公式ドキュメントをご参照ください。

Loguru公式ドキュメント (Read the Docs)

Loguru GitHubリポジトリ

コメント

タイトルとURLをコピーしました