はじめに
ファイルにログを残すためにlogger.~
を使い、コンソールにログを出すためにprint()
を使う。
面倒臭いと思ったことはないでしょうか。
会社でツールを作る際にコンソールとファイル両方にログを出力したいと思い、loggingモジュールを調べていたらコンソールにログを出力するStreamHandler
とファイルに出力するFileHandler
の両方使用すれば、コンソール出力とファイル出力の両方が簡単にできることがわかったので、その時の備忘録を残します。
参考文献
環境
- python 3.8.5
ロガーのセットアップ
logging.basicConfig()
でファイルやコンソールのどちらかに出力すれば良い場合はfilename=
やstream=
を指定すれば良いですが、ファイルとコンソール両方に出力したい場合などのハンドラを複数使用するときは指定せずにhandlers=
にハンドラをリストで与えて初期化します。
ファイルハンドラは保存パスに存在しないフォルダを指定するとエラーになってしまうので、ない場合は作成する処理を入れます。
import os
import sys
from datetime import datetime
import logging
from logging import StreamHandler, FileHandler, Formatter
from logging import INFO, DEBUG, NOTSET
# ストリームハンドラの設定
stream_handler = StreamHandler()
stream_handler.setLevel(INFO)
stream_handler.setFormatter(Formatter("%(message)s"))
# 保存先の有無チェック
if not os.path.isdir('./Log'):
os.makedirs('./Log', exist_ok=True)
# ファイルハンドラの設定
file_handler = FileHandler(
f"./Log/log{datetime.now():%Y%m%d%H%M%S}.log"
)
file_handler.setLevel(DEBUG)
file_handler.setFormatter(
Formatter("%(asctime)s@ %(name)s [%(levelname)s] %(funcName)s: %(message)s")
)
# ルートロガーの設定
logging.basicConfig(level=NOTSET, handlers=[stream_handler, file_handler])
これでloggingモジュールをコンソールとファイル両方に出力する設定ができました。
注意するのはlogging.basicConfig()
で設定する出力レベルです。ログはルートロガー→ハンドラの順に渡されます。なので、ルートロガーの設定であるlogging.basicConfig()
で出力レベルをINFO
等にしてしまうと、ルートロガーの時点で出力レベルがDEBUG
のログは弾かれてしまいます。そのため、ファイルハンドラの設定で出力レベルをDEBUG
にしても出力レベルがDEBUG
のログがファイルに保存されません。
ここではプログラムをベタで書いていますが、なにか適当に関数に入れておくと良いと思います。ほかでこれらのオブジェクトは使用する場所がないので。
ロガーを使用する
ロガーを使用する場合は、logging.getLogger(__name__)
でロガーオブジェクトを取得して使用します。
また、関数やファイルをまたぐ場合でも、一度ルートロガーの設定をしてしまえば、どこでも使えるので、使用したい場所でlogging.getLogger(__name__)
を呼び出してあげれば使用できます。
logger = logging.getLogger(__name__)
logger.debug("debug")
logger.info("info")
logger.warn("warn")
logger.error("error")
logger.critical("critical")
ファイルには全部のログが、コンソールにはinfoからのログが出力されていると思います。
コンソール出力をリッチにする
ついでにrichを使用して、コンソール出力をリッチにします。方法は簡単でlogging.StreamHandler
をrich.logging.RichHandler
に変えてあげるだけです。
richをインストールします。
$ pip install rich
インストールしたら、ロガーのセットアップの章のStreamHandler
をRichHandler
に書き換えます。
from rich.logging import RichHandler
# ストリームハンドラの設定
rich_handler: RichHandler = RichHandler(rich_tracebacks=True)
rich_handler.setLevel(INFO)
rich_handler.setFormatter(Formatter("%(message)s"))
# ファイルハンドラの設定
# 省略
# ルートロガーの設定
logging.basicConfig(level=NOTSET, handlers=[rich_handler, file_handler])
出力がリッチになりましたね。