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
モジュールと同様のログレベルに加え、TRACE
とSUCCESS
という独自のレベルを持っています。重要度は低い順に以下の通りです。
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.catch
や logger.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
だけでは不十分な場合があります。標準logging
のQueueHandler
とQueueListener
、あるいはそれを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.catch
やdiagnose=True
でデバッグ効率が大幅向上。 - ✅ モダンな設計:
{}
形式フォーマット、柔軟なシンクとフィルタリング。 - ✅ 標準
logging
との連携: 既存の資産とも統合可能。
Loguruの考慮点:
- ⚠️ サードパーティライブラリ: 標準ライブラリではないため、依存関係が増える。
- ⚠️
diagnose=True
の注意: 本番環境での利用は情報漏洩のリスクがある。 - ⚠️ 真のマルチプロセスロギング:
enqueue=True
だけでは不十分な場合があり、追加の工夫が必要になることがある。
多くの場合、特に新規プロジェクトや、標準logging
の設定に煩わしさを感じている場合には、Loguruは非常に魅力的な選択肢となるでしょう。そのシンプルさと強力な機能は、開発体験を向上させ、アプリケーションの品質を高めるのに役立ちます。
ぜひ、あなたの次のPythonプロジェクトでLoguruを試してみて、その「楽しさ」と「簡単さ」を体験してください!🚀
より詳しい情報や最新情報については、公式ドキュメントをご参照ください。
コメント