Author: | Vinay Sajip <vinay_sajip at red-dove dot com> |
---|
ロギングは、あるソフトウェアが実行されているときに起こったイベントを追跡するための手段です。ソフトウェアの開発者は、特定のイベントが発生したことを示すロギングの呼び出しをコードに加えます。イベントは、メッセージで記述され、これに変数データ (すなわち、イベントが起こる度に異なるかもしれないデータ) を加えることもできます。イベントには、開発者がそのイベントに定めた重要性も含まれます。重要性は、 レベル (level) や 重大度 (severity) とも呼ばれます。
logging は、単純なロギングの用法に便利な関数群を提供しています。この中には、 debug(), info(), warning(), error() および critical() があります。logging を使うべき時を決めるには、よくあるタスクに使う最適なツールを述べた、以下のテーブルを参照してください。
行いたいタスク | そのタスクに最適なツール |
---|---|
コマンドラインスクリプトやプログラムで普通に使う、コンソール出力の表示 | print() |
プログラムの通常の操作中に発生したイベントの報告 (例えば、状態の監視や障害の分析) | logging.info() (または、診断のための特に詳細な出力には logging.debug()) |
特定のランタイムイベントに関わる警告の発行 | その発行が避けられるもので、クライアントアプリケーションを修正してその警告を排除するべきなら warnings.warn() アプリケーションにできることはないが、それでもイベントを記録するべきなら logging.warning() |
特定のランタイムイベントに関わるエラーの報告 | 例外の送出 |
例外の送出をしないエラーの抑制 (例えば、長期のサーバプロセス中のエラーハンドラ) | 特定のエラーやアプリケーションドメインに応じて logging.error(), logging.exception() または logging.critical() |
ロギング関数は、そのイベントのレベルや重大度から名前を付けられ、それが追跡に使われます。標準のレベルとその適用範囲は、以下に (重大度が増す順に) 記述されています
レベル | 使われるとき |
---|---|
DEBUG | おもに問題を診断するときにのみ関心があるような、詳細な情報 |
INFO | 想定された通りのことが起こったことの確認 |
WARNING | 想定外のことが起こった、または問題が近く起こりそうである (例えば、’disk space low’) ことの表示 |
ERROR | より重大な問題により、ソフトウェアがある機能を実行できないこと |
CRITICAL | プログラム自体が実行を続けられないことを表す、重大なエラー |
デフォルトのレベルは WARNING で、logging パッケージが他に設定されなければ、このレベル以上のイベントのみ追跡されます。
追跡されるイベントは、異なる方法で処理されます。追跡されたイベントを処理する最も単純な方法は、それをコンソールに表示することです。その他のよくある方法は、それをディスクファイルに書き出すことです。
ごく簡単な例は:
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything
これらの行をスクリプトにタイプして実行すると、次のようにコンソールに出力されます:
WARNING:root:Watch out!
デフォルトのレベルは WARNING なので、 INFO メッセージは現れません。表示されたメッセージには、レベルの表示と、ロギングの呼び出しで提供された、イベントの説明すなわち ‘Watch out!’ が含まれます。 ‘root’ の部分は今は気にしないでください。あとで説明します。実際の出力は、必要に応じてかなり柔軟に書式化できます。書式化操作もあとで説明します。
非常によくある状況として、ロギングイベントのファイルへの記録が挙げられますから、次はそれを見てみましょう:
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
そして、ファイルを開いて何が起こったか見てみると、このようなログメッセージが見つかるでしょう:
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
この例はまた、追跡のしきい値となるロギングレベルを設定する方法も示しています。この例では、しきい値を DEBUG に設定しているので、全てのメッセージが表示されています。
ロギングレベルをコマンドラインオプションから次のように設定したいなら:
--log=INFO
--log に渡されたパラメタの値をある変数 loglevel に保存すれば:
getattr(logging, loglevel.upper())
を使い、 level 引数を通して basicConfig() に渡すべき値を得られます。ユーザの入力値をすべてエラーチェックしたいこともあり、以下のようになるかもしれません:
# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)
basicConfig() は、 debug() や info() を最初に呼び出す 前 に呼び出さなければなりません。これは、一度限りの単純な設定機能を意図しているので、実際に作用するのは最初の呼び出しのみで、続く呼び出しの効果は no-op です。
上記のスクリプトを複数回実行すると、2 回目以降の実行によるメッセージは example.log に加えられます。以前の実行によるメッセージを記憶せず、実行ごとに新たに始めたいなら、上記の例での呼び出しを次のように変え、 filemode 引数を指定することができます:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
出力は先ほどと同じになりますが、ログファイルは追記されるのではなくなり、以前の実行によるメッセージは失われます。
プログラムが複数のモジュールでできているなら、そのロギングをどのように構成するかの例はこちらです:
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO)
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# mylib.py
import logging
def do_something():
logging.info('Doing something')
myapp.py を実行すると、 myapp.log には、おそらく期待したとおりに、次のように書き込まれるでしょう:
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished
この mylib.py でのパターンは、複数のモジュールに一般化できます。なお、この簡単な使用パターンでは、ログファイルを見ることで、イベントの説明は見られますが、アプリケーションの どこから メッセージが来たのかを知ることはできません。メッセージの位置を追跡したいなら、このチュートリアルレベルを超えたドキュメントが必要になります – 上級ロギングチュートリアル を参照してください。
変数データのログを取るには、イベント記述メッセージにフォーマット文字列を使い、引数に変数データを加えてください。例えば:
import logging
logging.warning('%s before you %s', 'Look', 'leap!')
により、次のように表示されます:
WARNING:root:Look before you leap!
ご覧の通り、イベント記述メッセージに変数データを統合するために、古い、% スタイルの文字列フォーマットを使っています。これは後方互換性のためです。logging パッケージは、 str.format() や string.Template のような新しいフォーマットオプションよりも先に生まれました。新しいフォーマットオプションはサポートされて います が、その探求はこのチュートリアルでは対象としません。
メッセージを表示するのに使われるフォーマットを変更するには、使いたいフォーマットを指定する必要があります:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
すると次のように表示されます:
DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too
ご覧の通り、先の例に現れた ‘root’ が消失しています。フォーマット文字列に含めることができるものの一覧は、 LogRecord 属性 のドキュメントから参照できますが、単純な用途では、必要なものは levelname (重大度)、 message (変数データを含むイベント記述)、それともしかしたら、イベントがいつ起こったかという表示だけです。これは次の節で解説します。
イベントの日付と時刻を表示するには、フォーマット文字列に ‘%(asctime)s’ を置いてください:
import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')
のようにすると、このように表示されます:
2010-12-12 11:41:42,612 is when this event was logged.
(上で示した) 日付と時刻表示のデフォルトフォーマットは ISO8601 です。日付と時刻のフォーマットを詳細に制御するには、この例のように、 basicConfig に datefmt 変数を与えてください:
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
すると、このように表示されるでしょう:
12/12/2010 11:46:36 AM is when this event was logged.
datefmt 引数のフォーマットは、 time.strftime() でサポートされているものと同じです。
基本チュートリアルはこれで終わりです。あなたがロギングを使っていくためには、これで十分でしょう。logging パッケージが提供するものはもっとありますが、それを使いこなすためには、もうちょっと時間をかけて、以下のセクションを読む必要があります。その用意ができたら、好きな飲み物を持って、次に進みましょう。
ロギングを簡潔に行いたいなら、上記の例を使って、ロギングをあなたのスクリプトに組み込んでください。そして問題があったり、理解出来ないことがあったら、 comp.lang.python Usenet group (http://groups.google.com/group/comp.lang.python から利用できます) に質問を投稿してくだされば、そう遠くないうちに助けが得られるでしょう。
まだいますか?もう少し上級の、踏み込んだチュートリアルを綴った、幾つかの節を読み続けることができます。その後で、 ロギングクックブック もご覧ください。
logging ライブラリはモジュール方式のアプローチを取り、いくつかのカテゴリの部品を提供します。ロガー、ハンドラ、フィルタ、フォーマッタです。
ロギングは、 Logger クラスのインスタンス (以下 ロガー) にメソッドを呼び出すことで実行されます。各インスタンスには名前があり、名前空間階層構造に、ドット (ピリオド) をセパレータとして、概念的に並べられています。例えば、 ‘scan’ という名前のロガーは、ロガー ‘scan.text’, ‘scan.html’ および ‘scan.pdf’の親です。ロガー名は、何でも望むものにでき、ロギングされたメッセージが発生した場所を指し示します。
ロガーに名前をつけるときの良い習慣は、ロギングを使う各モジュールに、以下のように名付けられた、モジュールレベルロガーを使うことです:
logger = logging.getLogger(__name__)
これにより、ロガー名はパッケージ/モジュール階層をなぞり、ロガー名だけで、どこでイベントのログが取られたか、直感的に明らかになります。
ロガーの階層構造の根源は、ルートロガーと呼ばれます。それが、関数 debug(), info(), warning(), error() および critical() によって使われるロガーとなります。これらの関数は単に、ルートロガーの同名のメソッドを呼び出します。これらの関数とメソッドは、同じ署名をもっています。ルートロガーの名前は、ログ出力では ‘root’ と表示されます。
もちろん、メッセージを異なる送信先に記録することも出来ます。このパッケージでは、ファイルへ、 HTTP GET/POST 先へ、 SMTP 経由で電子メールへ、汎用のソケットへ、または Windows NT イベントログのような OS 毎のログ記録機構への、ログメッセージの書きこみがサポートされています。送信先は、 handler クラスによって取り扱われます。組み込みのハンドラクラスでは満たせないような、特殊な要件があるなら、独自のログ送信先を生成できます。
デフォルトでは、どのロギングメッセージに対しても、送信先は設定されていません。チュートリアルの例のように、 basicConfig() を使って、送信先 (コンソールやファイルなど) を指定できます。関数 debug(), info(), warning(), error() および critical() を呼び出すと、それらは送信先が設定されていないかを調べます。そして設定されていなければ、ルートロガーに委譲して実際にメッセージを出力する前に、コンソール (sys.stderr) を送信先に、デフォルトのフォーマットを表示されるメッセージに設定します。
basicConfig() によって設定される、メッセージのデフォルトのフォーマットはこうです:
severity:logger name:message
basicConfig() の format キーワード引数にフォーマット文字列を渡すことで、これを変更できます。フォーマット文字列を構成するためのすべてのオプションは、 フォーマッタオブジェクト を参照してください。
Logger オブジェクトの仕事は大きく三つに分かれます。一つ目は、アプリケーションが実行中にメッセージを記録できるように、いくつかのメソッドをアプリケーションから呼べるようにしています。二つ目に、ロガーオブジェクトはどのメッセージに対して作用するかを、深刻度 (デフォルトのフィルタ機構) またはフィルタオブジェクトに基づいて決定します。三つ目に、ロガーオブジェクトは関心を持っているすべてのログハンドラに関連するログメッセージを回送します。
とりわけ広く使われるロガーオブジェクトのメソッドは、二つのカテゴリーに分類できます: 設定とメッセージ送信です。
これらが設定メソッドの中でよく使われます:
これらのメソッドを、生成したすべてのロガーに毎回呼び出さなければならないわけではありません。最後の 2 段落を参照してください。
ロガーオブジェクトが設定されれば、以下のメソッドがログメッセージを生成します。
getLogger() は、指定されればその特定の名前の、そうでなければ root のロガーインスタンスへの参照を返します。ロガーの名前はピリオド区切りの階層構造を表します。同じ名前で getLogger() を複数回呼び出した場合、同一のロガーオブジェクトへの参照が返されます。階層リストを下ったロガーはリスト上位のロガーの子です。たとえば、名前が foo であるロガーがあったとして、 foo.bar, foo.bar.baz, foo.bam といった名前のロガーはすべて foo の子孫になります。
ロガーには、 有効レベル (effective level) の概念があります。ロガーにレベルが明示的に設定されていなければ、代わりに親のレベルがその有効レベルとして使われます。親のレベルが設定されなければ、 その 親のレベルが確かめられ、以下同様に、明示的に設定されたレベルが見つかるまで祖先が探されます。ルートロガーは、必ず明示的なレベルが設定されています (デフォルトでは WARNING です)。イベントを処理するかを決定するとき、ロガーの有効レベルを使って、イベントがロガーのハンドラに渡されるかが決められます。
子ロガーはメッセージを親ロガーのハンドラに伝えます。このため、アプリケーションが使っているすべてのロガーのためのハンドラを定義して設定する必要はありません。トップレベルのロガーのためのハンドラだけ設定しておいて必要に応じて子ロガーを作成すれば十分です。 (しかし、ロガーの propagate 属性を False に設定することで、伝播を抑制できます。)
Handler オブジェクトは適切なログメッセージを (ログメッセージの深刻度に基づいて) ハンドラの指定された出力先に振り分けることに責任を持ちます。ロガーオブジェクトには addHandler() メソッドで 0 個以上のハンドラを追加することができます。例として、あるアプリケーションがすべてのログメッセージをログファイルに、 error 以上のすべてのログメッセージを標準出力に、 critical のメッセージはすべてメールアドレスに、それぞれ送りたいとします。この場合、 3 つの個別のハンドラがそれぞれの深刻度と宛先に応じて必要になります。
このライブラリには多くのハンドラが用意されています (便利なハンドラ を参照してください) が、このチュートリアルでは StreamHandler と FileHandler だけを例に取り上げます。
アプリケーション開発者にとってハンドラを扱う上で気にするべきメソッドは極々限られています。組み込みのハンドラオブジェクトを使う (つまり自作ハンドラを作らない) 開発者に関係あるハンドラのメソッドは、次の設定用のメソッドだけでしょう:
アプリケーションのコード中では Handler のインスタンスを直接インスタンス化して使ってはなりません。代わりに、 Handler クラスはすべてのハンドラが持つべきインターフェイスを定義し、子クラスが使える (もしくはオーバライドできる) いくつかのデフォルトの振る舞いを規定します。
フォーマッタオブジェクトは最終的なログメッセージの順序、構造および内容を設定します。基底クラスである logging.Handler とは違って、アプリケーションのコードはフォーマッタクラスをインスタンス化しても構いません。特別な振る舞いをさせたいアプリケーションではフォーマッタのサブクラスを使う可能性もあります。コンストラクタは二つのオプション引数を取ります – メッセージのフォーマット文字列と日付のフォーマット文字列です。
メッセージのフォーマット文字列がなければ、デフォルトではメッセージをそのまま使います。日付のフォーマット文字列がなければ、デフォルトは:
%Y-%m-%d %H:%M:%S
で、最後にミリ秒が付きます。
メッセージのフォーマット文字列は %(<dictionary key>)s 形式の文字列代入を用います。使えるキーについては LogRecord 属性 に書いてあります。
次のメッセージフォーマット文字列は、人が読みやすい形式の時刻、メッセージの深刻度、およびメッセージの内容を、順番に出力します:
'%(asctime)s - %(levelname)s - %(message)s'
フォーマッタは、ユーザが設定できる関数を使って、生成時刻をタプルに記録します。デフォルトでは、 time.localtime() が使われます。特定のフォーマッタインスタンスに対してこれを変更するには、インスタンスの converter 属性を time.localtime() や time.gmtime() と同じ署名をもつ関数に設定してください。すべてのフォーマッタインスタンスに対してこれを変更するには、例えば全てのロギング時刻を GMT で表示するには、フォーマッタクラスの converter 属性を (GMT 表示の time.gmtime に) 設定してください。
プログラマは、ロギングを 3 種類の方法で設定できます:
最後の2つの選択肢については、 環境設定のための関数 で解説しています。以下の例では、Python コードを使って、とても簡単なロガー、コンソールハンドラ、そして簡単なフォーマッタを設定しています:
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
このモジュールを実行すると、コマンドラインによって以下の出力がなされます:
$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message
以下の Python モジュールは、ロガー、ハンドラ、フォーマッタをほとんど上述の例と同じように生成していますが、オブジェクト名だけが異なります:
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
これが logging.conf ファイルです:
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
出力は、設定ファイルに基づく例とだいたい同じです:
$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message
この通り、設定ファイルの方法は、主に設定とコードが分かれ、非コーダがロギングプロパティを変えやすくなるという点で、 Python コードの方法より少し優れています。
なお、設定ファイルで参照されるクラス名は、logging モジュールに対して相対であるか、通常のインポート機構を使って解決される絶対である値でなければなりません。従って、(logging モジュールに相対な) WatchedFileHandler または (Python インポートパスとして mypackage が使えるとき、パッケージ mypackage のモジュール mymodule で定義されたクラスに) mypackage.mymodule.MyHandler のどちらかが使えます。
Python 2.7 では、、ロギングを設定するのに新しく、辞書に設定情報を持たせる手段が導入されました。これは、上で概説した設定ファイルに基づく方法による機能の上位版を提供し、新しいアプリケーションやデプロイにはこのメソッドが推奨されます。Python の辞書を使って設定情報を保持し、辞書は他の用途にも使えるので、設定の選択肢が広がります。例えば、JSON フォーマットの設定ファイルや、YAML 処理機能が使えれば YAML フォーマットのファイルを使って、設定辞書を構成できます。また、もちろん、Python コードで辞書を構成し、ソケットを通して pickle 化された形式を受け取るなど、アプリケーションで意味があるいかなるやり方でも使えます。
ここに、YAML フォーマットにおける新しい辞書に基づく方法での、上述の例と同じ設定の例を挙げます:
version: 1
formatters:
simple:
format: format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
辞書を使ったロギングについて詳細は、 環境設定のための関数 を参照してください。
ロギング環境設定を与えられないと、ロギングイベントを出力しなければならないのに、イベントを出力するハンドラが見つからないことがあります。この状況での logging パッケージの振る舞いは、Python のバージョンに依ります。
Python 2.x では、振る舞いは以下の通りです:
ロギングを使うライブラリを開発するときは、ライブラリがどのようにロギングを使うのか、例えば使われているロガーの名前などを、ドキュメントにしておくべきです。ロギングの設定については、いくつか考えておくべきこともあります。使っているアプリケーションがロギングを使っていなくて、ライブラリコードがロギングを呼び出すと、 (前の節で解説したように) 重大度 WARNING 以上のイベントが、 sys.stderr に表示されます。これが最高のデフォルトの振る舞いと見なされます。
何らかの理由で、ロギング設定がなされていないときにメッセージを表示 させたくない なら、あなたのライブラリのトップレベルロガーに何もしないハンドラを取り付けられます。このハンドラは何も処理しないというだけで、全てのライブラリイベントに対してハンドラが見つかるので、メッセージが表示されることを防げます。ライブラリのユーザがアプリケーションでの使用のためにロギングを設定したら、それはおそらくハンドラを追加する設定でしょうが、そしてレベルが適切に設定されたら、ライブラリコード内でなされたロギングの呼び出しは、通常通りそのハンドラに出力を送るようになります。
何もしないハンドラ NullHandler (Python 2.7 以降) は、 logging パッケージに含まれます。このハンドラのインスタンスを、 (ロギング設定がなされていないときにライブラリのログイベントを sys.stderr に出力させたく ないなら) ライブラリの名前空間で使われるトップレベルロガーに取り付けられます。ライブラリ foo によるすべてのロギングが、 ‘foo.x’, ‘foo.x.y’ その他に該当する名前のロガーによってなされるなら:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
とすれば望んだ効果が得られるでしょう。組織が複数のライブラリを作り出すなら、指定されるロガー名は単に ‘foo’ ではなく、 ‘orgname.foo’ になります。
注意事項: ライブラリのロガーには、 NullHandler 以外の ハンドラを追加しない ことを強く推奨します。これは、ハンドラの設定が、あなたのライブラリを使うアプリケーション開発者に伝播するからです。アプリケーション開発者は、対象となる聴衆と、そのアプリケーションにどのハンドラが最も適しているかを知っているからです。ハンドラを ‘ボンネットの中で’ 加えてしまうと、ユニットテストをして必要に応じたログを送達する能力に干渉しかねません。
ログレベルの数値は以下の表のように与えられています。これらは基本的に自分でレベルを定義したい人のためのもので、定義するレベルを既存のレベルの間に位置づけるためには具体的な値が必要になります。もし数値が他のレベルと同じだったら、既存の値は上書きされその名前は失われます。
レベル | 数値 |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
レベルはロガーに関連付けることもでき、開発者が設定することも、保存されたログ記録設定を読み込む際に設定することもできます。ロガーに対してログ記録メソッドが呼び出されると、ロガーは自らのレベルとメソッド呼び出しに関連付けられたレベルを比較します。ロガーのレベルがメソッド呼び出しのレベルよりも高い場合、実際のログメッセージは生成されません。これはログ出力の冗長性を制御するための基本的なメカニズムです。
ログ記録されるメッセージは LogRecord クラスのインスタンスとしてエンコードされます。ロガーがあるイベントを実際にログ出力すると決定した場合、ログメッセージから LogRecord インスタンスが生成されます。
ログ記録されるメッセージは、ハンドラ (handlers) を通してディスパッチ機構にかけられます。ハンドラは Handler クラスのサブクラスのインスタンスで、ログ記録された (LogRecord 形式の) メッセージが、そのメッセージの伝達対象となる相手 (エンドユーザ、サポートデスクのスタッフ、システム管理者、開発者) に行き着くようにする役割を持ちます。ハンドラには特定の出力先を意図された LogRecord インスタンスが渡されます。各ロガーは 0 個以上のハンドラを (Logger の addHandler() メソッド) で関連付けることができます。ロガーに直接関連付けられたハンドラに加えて、ロガーの上位にあるロガーすべてに関連付けられたハンドラ がメッセージを処理する際に呼び出されます。 (ただしロガーの propagate フラグが false 値にセットされている場合を除きます。その場合は、祖先ハンドラへの伝搬はそこで止まります。)
ロガーと同様に、ハンドラは関連付けられたレベルを持つことができます。ハンドラのレベルはロガーのレベルと同じ方法で、フィルタとして働きます。ハンドラがあるイベントを実際に処理すると決定した場合、 emit() メソッドが使われ、メッセージを出力先に送信します。ほとんどのユーザ定義の emit() のサブクラスで、この emit() をオーバライドする必要があるでしょう。
独自のレベルを定義することは可能ですが、必須ではなく、実経験上は既存のレベルが選ばれます。しかし、カスタムレベルが必要だと確信するなら、レベルの定義には多大な注意を払うべきで、 ライブラリの開発の際、カスタムレベルを定義することは とても悪いアイデア になり得ます。これは、複数のライブラリの作者がみな独自のカスタムレベルを定義すると、与えられた数値が異なるライブラリで異なる意味になりえるため、開発者がこれを制御または解釈するのが難しくなるからです。
基底の Handler クラスに加え、多くの便利なサブクラスが提供されています:
StreamHandler インスタンスは、メッセージをストリーム (ファイル風オブジェクト) に送ります。
FileHandler インスタンスは、メッセージをディスクファイルに送ります。
BaseRotatingHandler は、ある地点でログファイルを循環させるハンドラの基底クラスです。これを直接インスタンス化することは意図されていません。代わりに、 RotatingFileHandler や TimedRotatingFileHandler を使用してください。
RotatingFileHandler インスタンスは、メッセージをディスクファイルに送り、最大ログファイル数とログファイル循環をサポートします。
TimedRotatingFileHandler インスタンスは、メッセージをディスクファイルに送り、ログファイルを特定時間のインターバルで循環します。
SocketHandler インスタンスは、メッセージを TCP/IP ソケットに送ります。
DatagramHandler インスタンスは、メッセージを UDP ソケットに送ります。
SMTPHandler インスタンスは、メッセージを指示された email アドレスに送ります。
SysLogHandler インスタンスは、メッセージを、必要ならばリモートマシンの、Unix syslog daemon に送ります。
NTEventLogHandler インスタンスは、メッセージを Windows NT/2000/XP イベントログに送ります。
MemoryHandler インスタンスは、メッセージを、特定の基準が満たされる度に流される、メモリ中のバッファに送ります。
HTTPHandler インスタンスは、メッセージを、 GET または POST セマンティクスを使って、HTTP サーバに送ります。
WatchedFileHandler インスタンスは、ロギングする先のファイルを監視します。ファイルが変更されると、そのファイルは閉じられ、ファイル名を使って再び開かれます。このハンドラは Unix 系のシステムにのみ便利です。Windows は、使われている基の機構をサポートしていません。
NullHandler インスタンスは、エラーメッセージに対して何もしません。これは、ライブラリ開発者がロギングを使いたいが、ライブラリのユーザがロギングを設定してなくても ‘No handlers could be found for logger XXX’ メッセージを表示させたくない場合に使われます。詳しい情報は ライブラリのためのロギングの設定 を参照してください。
バージョン 2.7 で追加: NullHandler クラス。
コア logging パッケージで、 NullHandler, StreamHandler および FileHandler クラスが定義されています。その他のハンドラは、サブモジュールの logging.handlers で定義されています。(環境設定機能のためのサブモジュール、 logging.config もあります。)
ログメッセージは、 Formatter クラスのインスタンスを通してフォーマット化してから表示されます。このインスタンスは、 % 演算子と辞書で使うのに適切なフォーマット文字列で初期化されます。
複数のメッセージを一括してフォーマット化するには、 BufferingFormatter が使えます。(一連の文字列のそれぞれに適用される) フォーマット文字列に加え、ヘッダとトレーラフォーマット文字列も提供されています。
ロガーレベルおよび/またはハンドラレベルに基づくフィルタリングで十分でなければ、 Filter のインスタンスを Logger と Handler インスタンスの両方に (addFilter() を通して) 加えることができます。メッセージの処理を続ける前に、ロガーもハンドラも、全てのフィルタに許可を求めます。フィルタのいずれかが偽値を返したら、メッセージの処理は続けられません。
基本の addFilter() 機能では、特定のロガー名でのフィルタリングをできます。この機能が使われると、指名されたロガーに送られたメッセージとその子だけがフィルタを通り、その他は落とされます。
logging パッケージは、ログを生成している間に起こる例外を飲み込むように設計されています。これは、ログ記録イベントを扱っている間に発生するエラー (ログ記録の設定ミス、ネットワークまたは他の同様のエラー) によってログ記録を使用するアプリケーションが早期に終了しないようにするためです。
SystemExit と KeyboardInterrupt 例外は決して飲み込まれません。 Handler サブクラスの emit() メソッドの間に起こる他の例外は、 handleError() メソッドに渡されます。
Handler の handleError() のデフォルト実装は、モジュールレベル変数 raiseExceptions が設定されているかどうかチェックします。設定されているなら、トレースバックが sys.stderr に出力されます。設定されていないなら、例外は飲み込まれます。
Note: raiseExceptions のデフォルト値は True です。これは、開発の間に起こるどんな例外についても通常は通知してほしいからです。実運用環境では raiseExceptions を False に設定することをお勧めします。
前の節とそこで挙げた例では、イベントを記録するときに渡されたメッセージが文字列であると仮定していました。しかし、これは唯一の可能性ではありません。メッセージとして任意のオブジェクトを渡すことができます。そして、ロギングシステムがそのオブジェクトを文字列表現に変換する必要があるとき、オブジェクトの __str__() メソッドが呼び出されます。実際、そうしたければ、文字列表現を計算することを完全に避けることができます - 例えば、 SocketHandler は、イベントを pickle してネットワーク上で送信することでログ出力します。
message 引数の整形は、必要になるまで延期されます。しかしながら、ログ記録メソッドに渡す引数を計算するだけでもコストがかかる場合があります。ロガーが単にイベントを捨てるなら、その計算を避けたいと考えるかもしれません。どうするかを決定するために isEnabledFor() メソッドを呼ぶことができます。このメソッドは引数にレベルを取って、そのレベルの呼び出しに対して Logger がイベントを生成するなら true を返します。このようにコードを書くことができます:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
このようにすると、ロガーの閾値が DEBUG より上に設定されている場合、 expensive_func1() と expensive_func2() の呼び出しは行われません。
これ以外にも、どんなログ情報が集められるかについてより正確なコントロールを必要とする、特定のアプリケーションでできる最適化があります。これは、ログ記録の間の不要な処理を避けるためにできることのリストです:
不要な情報 | それを避ける方法 |
---|---|
呼び出しがどこから行われたかに関する情報 | logging._srcfile を None にする |
スレッド情報 | logging.logThreads を 0 にする |
プロセス情報 | logging.logProcesses を 0 にする |
また、コア logging モジュールが基本的なハンドラだけを含んでいることに注意してください。 logging.handlers と logging.config をインポートしなければ、余分なメモリを消費することはありません。
参考