Pythonには便利なログ出力ライブラリが用意されています。
しかし、使い方を間違うとデバッグにとって逆効果になります。
しかも、他のWebサイトでもこの点については触れられていないことも多いのが現状です。
そこで、ログ出力の使い方とツボを踏まないための注意点をまとめました。
これからログ出力を本格的に使おうと思う方には必見です。
準備
ログ出力には logging というライブラリが必要ですが、これは Python 標準であるため、特にインストールする必要はありません。
プログラムの先頭に import logging と記述しておくだけで使えます。
1 |
import logging |
最初に知っておくべき注意点
ネットでは、 'import logging' によりインポートした logging クラスを使ってログ出力をするサンプルがよく登場しますが、これには注意が必要です。。
このlogging クラスはログ機能の最も根底(ルート)に位置するものでるため、第三者のライブラリ(標準ライブラリも含む)を import している場合、それにも影響を与えてしまいます。
例えば、自作プログラムでログの出力レベルを変更した途端、importしている他のライブラリからも大量にログが出力されて、必要なログが見つけ難くなるといったことも発生します。
推奨される方法は、logging.getLogerメソッドで loggin のインスタンス(オブジェクト)を生成し、これを使ってログを出力する方法です。
’from logging import getLogger logging’ という記述で import すると、logging 自身が使えなくなるので、誤って logging クラスのメソッドを直接使うことを避けることが出来ます。
ただし、初期設定で使う場合は問題ありませんが、ログの出力フォーマットを変更する場合のメソッドは、loggin クラスを直接使う必要があるので、その点もご注意ください。
いずれにせよ、少し面倒かもしれませんが、必ず logging.getLoger メソッドでインスタンス(オブジェクト)を生成し、そのメソッドを使ってログを出力するよう心掛けましょう。
logger の構造
logging.getLoger メソッドで取得したインスタンスを使ってログ出力を行うには、あらかじめ出力先の指定と出力フォーマットを指定する必要があります。
ログ出力のインスタンス(ロガー=Logger)、出力先の指定(ハンドラ=Handler)、出力フォーマット(フォーマッタ=Formatter)の関係は以下の様になります。
1つのロガーに対して、ハンドラは複数指定できます。
例えば標準出力と指定ファイルの両方に出力する場合、標準出力用のハンドラとファイル出力用のハンドラを作成し、ロガーに登録します。
ログ出力メソッドでログを出力すると、LogRecordオブジェクトが生成されます。
このLogRecordオブジェクトにはログに関する様々な属性を持っており、ログ出力メソッドで指定した文字列に加え、指定したフォーマットで属性情報を付加することが可能です。
この「指定したフォーマットで属性情報を付加」するものがフォーマッタです。
ちなみに、ログ出力メソッドで指定した文字列だけログに出力すれば良いのであれば、フォーマッタは指定しなくても問題ありません。
ログ出力の手順
ログを出力する手順は次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import logging #ロガーの生成 logger = logging.getLogger('mylog') #出力レベルの設定 logger.setLevel(logging.DEBUG) #ハンドラの生成 handler = logging.FileHandler('p:/mylog.log') #ロガーにハンドラを登録 logger.addHandler(handler) #フォーマッタの生成 fmt = logging.Formatter('%(asctime)s %(message)s') ハンドラにフォーマッタを登録 handler.setFormatter(fmt) #ログの出力 logger.debug("デバッグ") |
getLoggerの引数には名前を指定できますが、この名前はログ出力時に出力されます。
この名前を適切に使うことで、ログへの出力の度に、いちいち識別用の名前をメッセージに含める必要がなくなります。
ログの出力レベル
ログには出力レベルという概念が存在します。
ログを出力するためのメソッドは、それが持つ意味ごと用意されており、それぞれにレベルを持っています。
種類 | レベル | 内容 | 対応するメソッド |
---|---|---|---|
DEBUG | 10 | デバッグに必要な情報 | debug() |
INFO | 20 | 想定される処理が行われた事を知らせる | info() |
WARNING | 30 | 想定外の事が起きた場合、もしくはその予兆を知らせる | warning() |
ERROR | 40 | ある機能がエラーで実行できなかったことを知らせる | error() exception() |
CRITICAL | 50 | 何らかの原因(エラー含む)で処理が継続できず 中断したことを知らせる | critical() |
setLevelメソッドでレベルを指定することで、そのレベル以降のログのみ出力させるような制御が行えます。
レベルを適切に設定することで、例えばプログラム中に仕込んだデバッグ情報については、本番稼働時にはログ出力しない等の制御が可能になります。
ログ出力メソッド
ログ出力メソッドには次のようなメソッドが用意されています。
logメソッドでレベルを指定することも可能ですが、ソースコードの可読性が低下しますのであまりお勧めは出来ません。
極力 debugやinfo、warning 等、直感的に何のログを出力しているのかが分かるメソッドを利用しましょう。
メソッド | 意味と用途 |
---|---|
log( level,message ) | 指定したレベルでログにmessageを出力する |
debug( message ) | デバッグ情報としてmessageを出力する。 デバッグ時に処理をトレースしたい場合に使用する。 |
info( message ) | 想定された処理が行われたことを表す message を出力する。 通常運用(本番稼働時)において、処理が実行されたことを把握したい 場合に使用する。 |
warning( message ) | 想定外の事が発生したことを表す message を出力する。 処理は継続して続けられるが、想定外の事象が発生したことを把握したい 場合に使用する。 |
error( message ) | エラーが発生したことを表す message を出力する。 処理に影響を与えるエラーが発生したことを把握したい場合に使用する。 |
exception( message ) | error (message) と同様の意味だが、 Pythonが出力した例外処理の詳細情報も合わせてログに出力する。 エラー発生時の原因追及のため、詳細情報を得たい場合に利用する。 |
critical( message ) | 何らかの問題によって処理が中断したことを表すmessageを出力する 処理が異常終了したことを把握したい場合に使用する。 |
setLevel とメソッドの関係は以下の通りです。
ログの出力先指定
ログの出力先として一般的なものは標準出力(コンソール出力)とファイル出力です。
標準出力は logging.StreamHandlerメソッドを使ってハンドルを作成するのですが、引数には sys.stdoutとsys.err が指定できます。
一方、ファイル出力には logging.FileHandler メソッドを使います。
出力先 | メソッド | 引数 |
---|---|---|
標準出力 | StreamHandler | sys.stdout sys.stderr |
ファイル | FileHandler | ファイルのパス |
出力先は上記以外にも多数存在します。
詳しくはこちらの公式サイトに記載されていますが、ここでは概要だけを一覧にまとめました。
ハンドラの種類 | 出力先 |
---|---|
FileHandler | 一般的なファイル出力。 |
RotatingFileHandler | 最大ログファイル数とログファイルの循環をサポートしたファイル出力。 |
TimedRotatingFileHandler | ファイル出力時、特定時間のインターバル毎の循環をサポートしたファイル出力。 |
SocketHandler | TCP/IP通信でログを出力。 |
DatagramHandler | UDP通信でログを出力。 |
SMTPHandler | 指定したメールアドレスにログを出力。 |
SysLogHandler | Unix OSのシステムログにログを出力。 |
NTEventLogHandler | Windowsのイベントログにログを出力。 |
MemoryHandler | メモリ中のバッファにログを出力。 |
HTTPHandler | HTTP通信でログを出力。 |
WatchedFileHandler | 出力先のファイルを監視し、変更されたことを検知するとファイルを閉じ、新しくログファイルを作成します。 UNIX系OSでの利用が前提。 |
NullHandler | ログ出力をしない。 |
ログ出力レベルの指定は、ハンドラ毎に指定することも可能です。
多くの場合はロガーにレベルを設定するだけで事が足りると思いますが、もし複数のハンドラを用意して、それぞれにレベルを設定したい場合は、ハンドラ毎(もしくはレベルを変えたいハンドラ)に setLevel でレベルを設定します。
ログのフォーマット指定
「loggerの構造」でも記載しましたが、ログ出力メソッドの引数で指定した文字列を、単純にログ出力するだけであればフォーマットの指定は不要です。
しかし、多くの場合はログ出力の時刻を行頭に付けますし、時にはプロセス名やプロセスID、モジュール名などの情報を付加したい場合もあります。
そんな時は setFormatter メソッドを使うことで分かりやすいログを出力出来ます。
1 2 |
#フォーマッタの生成 fmt = logging.Formatter('%(asctime)s %(message)s') |
以下にフォーマットと概要について一覧でまとめていますが、より詳細の情報が必要な場合はこちらの公式サイトの真ん中より下部分にスクロールして頂くと閲覧可能です。
フォーマット | 概要 |
---|---|
%(asctime)s | ログが生成された時刻。 デフォルトは "2003-07-08 16:49:45,896" 形式 。 コンマ以降の数字は時刻のミリ秒を表す。 |
%(created)f | ログが出力された時刻 (time.time() によって返される形式)。 |
%(filename)s | pathname のファイル名部分。 |
%(funcName)s | ロギングの呼び出しを含む関数名。 |
%(levelname)s | ログの出力レベルを表す文字列 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')。 |
%(levelno)s | ログの出力レベルを表す数値。 |
%(lineno)d | ログ出力が行われたソースの行番号 (利用できる場合のみ)。 |
%(message)s | ログ出力メソッドの引数に指定された文字列。 フォーマッタを指定した場合、これを記述しないとログ出力で指定した文字列が 出力されない。 |
%(module)s | モジュール名 (filename の名前部分)。 |
%(msecs)d | ログ出力された時刻のミリ秒部分。 |
%(name)s | getLogger メソッドで指定したロガーの名前。 |
%(pathname)s | ログ出力が行われたファイルの完全なパス名 (但し、利用できる場合のみ)。 |
%(process)d | プロセス ID (但し、利用可能な場合のみ)。 |
%(processName)s | プロセス名(但し、利用可能な場合のみ)。 |
%(relativeCreated)d | logging モジュールが読み込まれた時刻に対する、 ログ出力された時刻を、ミリ秒で表したもの。 |
%(thread)d | スレッド ID (但し、利用可能な場合のみ)。 |
%(threadName)s | スレッド名 (但し、利用可能な場合のみ)。 |
環境設定ファイルを使ったログ設定
ログに関する詳細をプログラムではなく、環境設定ファイルに記述して、それを読み込むことで必要な設定を行うことが可能です。
この機能を使うには、logging から config を import しておきます。
1 |
from logging import config |
環境設定ファイルにハンドラーやフォーマッターの情報を記述しておくことで、ソースコードが簡略化できます。
その反面、必ずロガーのルート(root) の設定も必要であるため、一歩間違うと標準ライブラリ、拡張ライブラリ含めてログ出力に影響を及ぼす可能性があります。
この方法を使う場合は、その点にご留意ください。
環境設定ファイルはテキストに決められたルールで設定値を記述する方法と、json , yml , XML などの階層構造を持つファイルから辞書を作成し、それを読み取る方法の2通りがあります。
ここでは yaml とテキストの2通りについて例を挙げておきます。
より詳細な情報は、こちらの公式サイトに記載されていますので、ここでは簡単な例を紹介しておきます。
yaml を使う方法
あらかじめ yaml を import しておく必要があります。
ymlファイルを読み込む際は yaml ライブラリを使いますが、取得した辞書形式のデータは、 config.dictConfig に渡すことでログの設定が行えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from logging import getLogger,config import yaml #定義ファイルの読み込み with open('p:/logging.yml', 'r') as yml: dic= yaml.safe_load(yml) #定義ファイルを使ったloggingの設定 config.dictConfig(dic) #ロガーの取得 logger = logging.getLogger('mylogger') #ログの出力 logger.debug('デバッグ') |
下記が定義ファイル(logging.yml)の例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
version: 1 formatters: myformat: format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handlers: console: class: logging.StreamHandler level: DEBUG formatter: myformat stream: ext://sys.stdout loggers: mylogger: level: DEBUG handlers: [console] propagate: no root: level: DEBUG handlers: [console] |
テキストファイルを使う方法
YAML形式より少し見難くなりますが、テキストファイルに記述する方が、より楽に設定が可能です。
1 2 3 4 5 6 7 8 9 10 |
from logging import getLogger,config #定義ファイルを使ったloggingの設定 config.fileConfig('p:/logging.conf') #ロガーの取得 logger = logging.getLogger('mylogger') #ログの出力 logger.debug('デバッグ') |
下記が定義ファイル(logging.conf)の例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[loggers] keys=root,mylogger [handlers] keys=myStreamHandler,myFileHandler [formatters] keys=myformat [logger_root] level=WARNING handlers=myFileHandler,myStreamHandler [logger_mylogger] level=WARNING handlers=myFileHandler,myStreamHandler qualname=mylogger [handler_myStreamHandler] class=StreamHandler formatter=myformat args=(sys.stdout,) [handler_myFileHandler] class=FileHandler formatter=myformat args=('p:/test.log',) [formatter_myformat] format=[%(asctime)s]%(filename)s(%(lineno)d): %(message)s datefmt=%Y-%m-%d %H:%M:%S |
定義方法が少し分かり難いので、補足しておきます。
下記の様な関係性になっていて、ロガー、ハンドラー、フォーマッターの詳細を[logger_名前]、[handler_名前]、[formatter_名前] というルールで名前を付けて定義し、[loggers]、[handlers]、[formatters] の3か所に、それぞれ定義した名前をカンマで羅列するという記述になります。
まとめ
今回はPython の logging を使ったログ出力について解説致しました。
logging クラスに備わっているメソッドを使うことは行儀が悪いので、必ず getLogger メソッドでインスタンス(オブジェクト) を取得し、それを使ってログを出力しましょう。
ログを出力する場合は、
- ロガーのインスタンスを取得
- ログ出力レベル(setLevel)の指定
- 出力先のハンドラ(StreamHandlerやFileHandler)の指定
- 出力フォーマット(setFormatter)の指定
という手順を踏んでから、debugやinfoなどのログ出力メソッドを使います。
この記事がlogging を利用する時の皆さんの一助になれば幸いです。
コメント