いつも忘れてしまうので・・・
簡単なスクリプトを書くときでも、きちんとlogging
しておくとあとあと便利だと思うのですが、いつもやり方を忘れてしまいます。
なので、Loggingクックブックを参考に、ロガー作成の要素を整理してみました。また、毎回ファイルの冒頭に同じことを書くのもアレなので、setup_logger
という関数にしました
def setup_logger(name, logfile='LOGFILENAME.txt'):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# create file handler which logs even DEBUG messages
fh = logging.FileHandler(logfile)
fh.setLevel(logging.DEBUG)
fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
fh.setFormatter(fh_formatter)
# create console handler with a INFO log level
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%Y-%m-%d %H:%M:%S')
ch.setFormatter(ch_formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
return logger
# 各モジュールの最初のほうで、グローバルに宣言しておく
logger = setup_logger(__name__)
ロガー作成のポイント
自分なりに理解したポイントは以下の3点。
-
logging
というルートにあるロガーではなく、getLogger
して個別のを作るほうがよい -
logger
全体のレベルを設定(とりあえずDEBUG
に設定) -
logger
にハンドラーを追加する
この3つの手順を覚えておき、あとは必要なハンドラ(logging.handlers
)を設定し追加していくだけです。おぉ、整理してみると簡単!。
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
...
logger.addHandler(fh)
logger.addHandler(ch)
ハンドラ作成のポイント
ハンドラ作成時のポイントは以下の3点。
- ハンドラを選ぶ : logging.handlers から自分に必要なものを探す
- ハンドラのログレベルを設定する : ログレベルとその使い時は、基本ロギングチュートリアルを参照
- ハンドラの
formatter
を設定する :logging.Formatter
を使う
ログに表示する内容のテンプレートの詳細な設定は フォーマッタオブジェクトを読むとよいでしょう。
テンプレートに使える変数はLogRecord属性の表から選びます。
また、2つめの引数に日付フォーマット(datefmt
)を指定できることも知っておくと便利。
今回は、ファイル用のログ出力は冗長にし、コンソール用のログ出力はシンプルにしたかったので、それぞれ別のフォーマッタを設定しました。
# create file handler which logs even DEBUG messages
fh = logging.FileHandler(logfile)
fh.setLevel(logging.DEBUG)
fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
fh.setFormatter(fh_formatter)
# create console handler with a INFO log level
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%Y-%m-%d %H:%M:%S')
ch.setFormatter(ch_formatter)
ログの出力結果
実際のログ出力を載せておきます。
## Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
2019-06-03 11:12:38,692 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 16 - 1.82 MB
2019-06-03 11:12:40,860 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 17 - 1.82 MB
2019-06-03 11:12:42,832 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 18 - 1.82 MB
2019-06-03 11:12:45,220 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 19 - 1.82 MB
2019-06-03 11:12:47,006 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 20 - 1.82 MB
2019-06-03 11:12:48,396 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 21 - 1.82 MB
2019-06-03 11:12:49,767 - DEBUG - parsers.py - __main__ - parse_chunk - Parsed : Chunk# 22 - 1.82 MB
## Formatter('%(asctime)s - %(levelname)s - %(message)s', '%Y-%m-%d %H:%M:%S')
2019-06-03 11:11:58 - INFO - --------------------------------------------------
2019-06-03 11:11:58 - INFO - In : data/weblogs/ssl_access_log.20190601.gz
2019-06-03 11:11:58 - INFO - Out : data/parsed/ssl_access_log_20190601.parsed.gz
2019-06-03 11:11:58 - INFO - --------------------------------------------------
2019-06-03 11:11:58 - INFO - Read data/weblogs/ccprod02_ssl_access_log.20190601.gz with chunks (size=10000)
おまけ:ログファイルのローテーション
プログラムを作成&デバッグしている段階から、そこそこ常用する段階になってくると、(正常時はほとんど確認する必要のない)ログファイルがどんどんと肥大化してしまうのが少し気になっていました。これまでは、気づいたときにログファイルを削除して、プログラムを実行していたのですが、今回、ハンドラについて理解したので、きちんとログファイルをローテーションするように変更しました。
今回はRotatingFileHandler
を使って100kB
のファイルを10個
まで保管するようにしました。
-
RotatingFileHandler
はlogging.handlers
の下にあるのでimport logging.handlers
を追記 - ファイル出力用ハンドラ
fh
をlogging.FileHandler
からlogging.handlers.RotatingFileHnadler
に変更 - ログファイルのサイズを
100kB
(maxBytes=100000
)に設定 - ログファイルを保存する数は10個(
backupCount=10
)に設定
import logging.handlers
#fh = logging.FileHandler(logfile)
fh = logging.handlers.RotatingFileHandler(logfile, maxBytes=100000, backupCount=10)
fh.setLevel(logging.DEBUG)
fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
fh.setFormatter(fh_formatter)
下は何回かログを出力させた結果です。
最も新しいのがlog_rotate.txt
で、100kBを目安にファイルがバックアップが作成されていることが確認できました。
95K Jun 4 09:45 log_rotate.txt
98K Jun 4 08:26 log_rotate.txt.1
98K Jun 4 09:08 log_rotate.txt.2
98K Jun 3 16:06 log_rotate.txt.3
98K Jun 3 14:49 log_rotate.txt.4
おわりに
よし、これでロギングも怖くない!
おわりのおまけ
この記事がぽつりぽつりとストック/いいねされているようなので、最近はもっぱらLoguruを使っていることを追記しておきます。(2023/06/22)
from loguru import logger
logger.debug("これだけでいいかんじ!")
この記事の設定はfrom loguru import logger
のたった一行にほぼ置き換えられるくらい簡単で便利なパッケージです。ありがたや✨