ロギングクックブック
- 著者
- ビナイサジップ
このページには、過去に役立つことがわかっているロギングに関連するレシピがいくつか含まれています。
複数のモジュールへのログインの使用
logging.getLogger('someLogger')
を複数回呼び出すと、同じロガーオブジェクトへの参照が返されます。 これは、同じモジュール内だけでなく、同じPythonインタープリタープロセス内にある限り、モジュール間でも当てはまります。 同じオブジェクトへの参照にも当てはまります。 さらに、アプリケーションコードは、1つのモジュールで親ロガーを定義および構成し、別のモジュールで子ロガーを作成(構成はしない)することができ、子へのすべてのロガー呼び出しは親に渡されます。 メインモジュールは次のとおりです。
補助モジュールは次のとおりです。
出力は次のようになります。
2005-03-23 23:47:11,663 - spam_application - INFO -
creating an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,665 - spam_application.auxiliary.Auxiliary - INFO -
creating an instance of Auxiliary
2005-03-23 23:47:11,665 - spam_application - INFO -
created an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,668 - spam_application - INFO -
calling auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,668 - spam_application.auxiliary.Auxiliary - INFO -
doing something
2005-03-23 23:47:11,669 - spam_application.auxiliary.Auxiliary - INFO -
done doing something
2005-03-23 23:47:11,670 - spam_application - INFO -
finished auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,671 - spam_application - INFO -
calling auxiliary_module.some_function()
2005-03-23 23:47:11,672 - spam_application.auxiliary - INFO -
received a call to 'some_function'
2005-03-23 23:47:11,673 - spam_application - INFO -
done with auxiliary_module.some_function()
複数のスレッドからのロギング
複数のスレッドからのロギングには、特別な労力は必要ありません。 次の例は、メイン(初期)スレッドと別のスレッドからのロギングを示しています。
実行すると、スクリプトは次のように出力されます。
0 Thread-1 Hi from myfunc
3 MainThread Hello from main
505 Thread-1 Hi from myfunc
755 MainThread Hello from main
1007 Thread-1 Hi from myfunc
1507 MainThread Hello from main
1508 Thread-1 Hi from myfunc
2010 Thread-1 Hi from myfunc
2258 MainThread Hello from main
2512 Thread-1 Hi from myfunc
3009 MainThread Hello from main
3013 Thread-1 Hi from myfunc
3515 Thread-1 Hi from myfunc
3761 MainThread Hello from main
4017 Thread-1 Hi from myfunc
4513 MainThread Hello from main
4518 Thread-1 Hi from myfunc
これは、予想どおりにログ出力が散在していることを示しています。 もちろん、このアプローチは、ここに示されているよりも多くのスレッドで機能します。
複数のハンドラーとフォーマッター
ロガーはプレーンなPythonオブジェクトです。 addHandler()メソッドには、追加できるハンドラーの数に対する最小または最大のクォータがありません。 アプリケーションがすべての重大度のすべてのメッセージをテキストファイルに記録すると同時に、エラー以上をコンソールに記録すると便利な場合があります。 これを設定するには、適切なハンドラーを構成するだけです。 アプリケーションコードのロギング呼び出しは変更されません。 これは、前の単純なモジュールベースの構成例へのわずかな変更です。
'application'コードは複数のハンドラーを気にしないことに注意してください。 変更されたのは、 fh という名前の新しいハンドラーの追加と構成だけでした。
重大度の高いまたは低いフィルターを使用して新しいハンドラーを作成する機能は、アプリケーションを作成およびテストするときに非常に役立ちます。 デバッグに多くのprint
ステートメントを使用する代わりに、logger.debug
を使用します。後で削除またはコメントアウトする必要があるprintステートメントとは異なり、logger.debugステートメントはソースコードにそのまま残すことができます。再び必要になるまで休眠状態を保ちます。 その時点で発生する必要がある唯一の変更は、デバッグするロガーやハンドラーの重大度レベルを変更することです。
複数の宛先へのロギング
さまざまなメッセージ形式でさまざまな状況でコンソールとファイルにログを記録するとします。 レベルがDEBUG以上のメッセージをファイルに記録し、レベルがINFO以上のメッセージをコンソールに記録するとします。 また、ファイルにはタイムスタンプが含まれている必要がありますが、コンソールメッセージには含まれていないと仮定しましょう。 これを実現する方法は次のとおりです。
これを実行すると、コンソールに表示されます
root : INFO Jackdaws love my big sphinx of quartz.
myapp.area1 : INFO How quickly daft jumping zebras vex.
myapp.area2 : WARNING Jail zesty vixen who grabbed pay from quack.
myapp.area2 : ERROR The five boxing wizards jump quickly.
ファイルには次のようなものが表示されます
10-22 22:19 root INFO Jackdaws love my big sphinx of quartz.
10-22 22:19 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim.
10-22 22:19 myapp.area1 INFO How quickly daft jumping zebras vex.
10-22 22:19 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack.
10-22 22:19 myapp.area2 ERROR The five boxing wizards jump quickly.
ご覧のとおり、DEBUGメッセージはファイルにのみ表示されます。 他のメッセージは両方の宛先に送信されます。
この例では、コンソールハンドラーとファイルハンドラーを使用していますが、選択したハンドラーの数と組み合わせを任意に使用できます。
構成サーバーの例
ロギング構成サーバーを使用するモジュールの例を次に示します。
そして、これがファイル名を受け取り、そのファイルをサーバーに送信するスクリプトです。新しいロギング構成として、適切にバイナリエンコードされた長さが前に付けられます。
ブロックするハンドラーの処理
場合によっては、ロギング元のスレッドをブロックせずにロギングハンドラーに作業を行わせる必要があります。 これはWebアプリケーションでは一般的ですが、もちろん他のシナリオでも発生します。
動作が遅いことを示す一般的な原因は、 SMTPHandler です。開発者の制御が及ばないさまざまな理由(たとえば、メールやネットワークインフラストラクチャのパフォーマンスの低下)により、電子メールの送信に時間がかかる場合があります。 ただし、ほとんどすべてのネットワークベースのハンドラーがブロックできます。 SocketHandler 操作でさえ、速度が遅すぎる内部でDNSクエリを実行する可能性があります(このクエリは、Pythonレイヤーの下のソケットライブラリコードの奥深くにある可能性があります。そしてあなたのコントロールの外)。
1つの解決策は、2つの部分からなるアプローチを使用することです。 最初の部分では、パフォーマンスが重要なスレッドからアクセスされるロガーに QueueHandler のみをアタッチします。 キューに書き込むだけです。キューは、十分な大きさの容量にサイズ設定することも、サイズに上限を設けずに初期化することもできます。 キューへの書き込みは通常すぐに受け入れられますが、コードの予防措置として queue.Full 例外をキャッチする必要があります。 コードにパフォーマンスが重要なスレッドがあるライブラリ開発者の場合は、コードを使用する他の開発者のために、必ずこれを文書化してください(QueueHandlers
のみをロガーに添付することをお勧めします)。 。
ソリューションの2番目の部分は、 QueueListener です。これは、 QueueHandler に対応するものとして設計されています。 QueueListener は非常に単純です。キューといくつかのハンドラーが渡され、QueueHandlers
(または[X201Xの他のソース)から送信されたLogRecordsのキューをリッスンする内部スレッドを起動します。 ] 、そのことについて)。 LogRecords
はキューから削除され、処理のためにハンドラーに渡されます。
個別の QueueListener クラスを持つことの利点は、同じインスタンスを使用して複数のQueueHandlers
にサービスを提供できることです。 これは、たとえば、既存のハンドラークラスのスレッドバージョンを使用するよりもリソースに適しています。既存のハンドラークラスは、ハンドラーごとに1つのスレッドを消費し、特別なメリットはありません。
これら2つのクラスの使用例は次のとおりです(インポートは省略)。
これを実行すると、次のようになります。
MainThread: Look out!
バージョン3.5で変更: Python 3.5より前は、 QueueListener は常に、キューから受信したすべてのメッセージを、初期化されたすべてのハンドラーに渡していました。 (これは、レベルフィルタリングがすべて、キューがいっぱいになる反対側で行われると想定されていたためです。)3.5以降、この動作は、キーワード引数respect_handler_level=True
をリスナーのコンストラクターに渡すことで変更できます。 これが行われると、リスナーは各メッセージのレベルをハンドラーのレベルと比較し、適切な場合にのみメッセージをハンドラーに渡します。
ネットワークを介したロギングイベントの送受信
ネットワークを介してログイベントを送信し、受信側でそれらを処理するとします。 これを行う簡単な方法は、 SocketHandler インスタンスを送信側のルートロガーにアタッチすることです。
受信側では、 socketserver モジュールを使用して受信機をセットアップできます。 基本的な作業例を次に示します。
最初にサーバーを実行し、次にクライアントを実行します。 クライアント側では、コンソールには何も出力されません。 サーバー側では、次のように表示されます。
About to start TCP server...
59 root INFO Jackdaws love my big sphinx of quartz.
59 myapp.area1 DEBUG Quick zephyrs blow, vexing daft Jim.
69 myapp.area1 INFO How quickly daft jumping zebras vex.
69 myapp.area2 WARNING Jail zesty vixen who grabbed pay from quack.
69 myapp.area2 ERROR The five boxing wizards jump quickly.
一部のシナリオでは、pickleにセキュリティ上の問題があることに注意してください。 これらが影響する場合は、makePickle()
メソッドをオーバーライドしてそこに代替を実装し、上記のスクリプトを代替のシリアル化を使用するように適合させることで、代替のシリアル化スキームを使用できます。
ログ出力にコンテキスト情報を追加する
ロギング呼び出しに渡されるパラメーターに加えて、ロギング出力にコンテキスト情報を含めたい場合があります。 たとえば、ネットワークアプリケーションでは、クライアント固有の情報をログに記録することが望ましい場合があります(たとえば、 リモートクライアントのユーザー名、またはIPアドレス)。 extra パラメーターを使用してこれを実現することもできますが、この方法で情報を渡すことが常に便利であるとは限りません。 接続ごとにLogger
インスタンスを作成したくなるかもしれませんが、これらのインスタンスはガベージコレクションされていないため、これはお勧めできません。 これは実際には問題ではありませんが、Logger
インスタンスの数がアプリケーションのロギングで使用する粒度のレベルに依存している場合、 [の数が管理しにくい場合があります。 X212X]インスタンスは事実上無制限になります。
LoggerAdaptersを使用してコンテキスト情報を伝達する
ロギングイベント情報とともに出力されるコンテキスト情報を渡すことができる簡単な方法は、LoggerAdapter
クラスを使用することです。 このクラスはLogger
のように設計されているため、debug()
、info()
、warning()
、error()
、[X114X ] 、critical()
、log()
。 これらのメソッドは、Logger
の対応するメソッドと同じシグネチャを持っているため、2つのタイプのインスタンスを同じ意味で使用できます。
LoggerAdapter
のインスタンスを作成するときは、Logger
インスタンスと、コンテキスト情報を含むdictのようなオブジェクトを渡します。 LoggerAdapter
のインスタンスでロギングメソッドの1つを呼び出すと、コンストラクターに渡されたLogger
の基になるインスタンスに呼び出しが委任され、委任された呼び出しでコンテキスト情報が渡されるように調整されます。 。 LoggerAdapter
のコードの抜粋は次のとおりです。
LoggerAdapter
のprocess()
メソッドでは、コンテキスト情報がロギング出力に追加されます。 ロギング呼び出しのメッセージとキーワードの引数が渡され、基になるロガーへの呼び出しで使用するために、これらの(潜在的に)変更されたバージョンが返されます。 このメソッドのデフォルトの実装では、メッセージはそのままですが、コンストラクターに渡されるdictのようなオブジェクトを値とするキーワード引数に「extra」キーが挿入されます。 もちろん、アダプターの呼び出しで「extra」キーワード引数を渡した場合は、サイレントに上書きされます。
'extra'を使用する利点は、dictのようなオブジェクトの値がLogRecord
インスタンスの__dict__にマージされ、キーを認識しているFormatter
インスタンスでカスタマイズされた文字列を使用できることです。 dictのようなオブジェクトの。 別の方法が必要な場合、例えば メッセージ文字列にコンテキスト情報を追加または追加する場合は、LoggerAdapter
をサブクラス化し、process()
をオーバーライドするだけで必要な処理を実行できます。 簡単な例を次に示します。
あなたはこのように使うことができます:
次に、アダプタにログを記録するイベントには、ログメッセージの前にsome_conn_id
の値が付加されます。
dict以外のオブジェクトを使用してコンテキスト情報を渡す
実際のdictをLoggerAdapter
に渡す必要はありません。__getitem__
と__iter__
を実装するクラスのインスタンスを渡して、次のdictのように見せることができます。ロギング。 これは、値を動的に生成する場合に役立ちます(dictの値は一定です)。
フィルタを使用してコンテキスト情報を伝達する
ユーザー定義のFilter
を使用して、コンテキスト情報をログ出力に追加することもできます。 Filter
インスタンスは、渡されたLogRecords
を変更できます。これには、適切なフォーマット文字列を使用して出力できる属性を追加したり、必要に応じてカスタムFormatter
を追加したりできます。
たとえば、Webアプリケーションでは、処理中のリクエスト(または少なくともその興味深い部分)をthreadlocal( threading.local )変数に格納し、 [からアクセスできます。 X202X]たとえば、 [のように属性名「ip」と「user」を使用して、リクエストからの情報(たとえば、リモートIPアドレスとリモートユーザーのユーザー名)をLogRecord
に追加します。 X382X]上記の例。 その場合、同じフォーマット文字列を使用して、上記と同様の出力を取得できます。 スクリプトの例を次に示します。
これを実行すると、次のようなものが生成されます。
2010-09-06 22:38:15,292 a.b.c DEBUG IP: 123.231.231.123 User: fred A debug message
2010-09-06 22:38:15,300 a.b.c INFO IP: 192.168.0.1 User: sheila An info message with some parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f ERROR IP: 127.0.0.1 User: jim A message at ERROR level with 2 parameters
2010-09-06 22:38:15,300 d.e.f DEBUG IP: 127.0.0.1 User: sheila A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,300 d.e.f ERROR IP: 123.231.231.123 User: fred A message at ERROR level with 2 parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1 User: sheila A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f DEBUG IP: 192.168.0.1 User: jim A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,301 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters
2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters
複数のプロセスから単一のファイルにログを記録する
ロギングはスレッドセーフであり、単一プロセスの複数のスレッドから単一ファイルへのロギングはサポートされていますが、複数プロセスから単一ファイルへのロギングはではありません ]サポートされています。これは、Pythonの複数のプロセス間で単一のファイルへのアクセスをシリアル化する標準的な方法がないためです。 複数のプロセスから単一のファイルにログを記録する必要がある場合、これを行う1つの方法は、すべてのプロセスをSocketHandler
にログに記録し、ソケットから読み取るソケットサーバーを実装する別のプロセスを用意することです。ファイルにログを記録します。 (必要に応じて、既存のプロセスの1つに1つのスレッドを割り当てて、この機能を実行できます。)このセクションでは、このアプローチについて詳しく説明し、開始点として使用できる動作中のソケットレシーバーが含まれています。あなたがあなた自身のアプリケーションに適応するために。
multiprocessing モジュールの Lock クラスを使用して、プロセスからファイルへのアクセスをシリアル化する独自のハンドラーを作成することもできます。 既存のFileHandler
およびサブクラスは、将来的には使用する可能性がありますが、現在マルチプロセッシングを使用していません。 現在、マルチプロセッシングモジュールは、すべてのプラットフォームで機能するロック機能を提供しているわけではないことに注意してください( https://bugs.python.org/issue3770 を参照)。
または、Queue
と QueueHandler を使用して、すべてのログイベントをマルチプロセスアプリケーションのプロセスの1つに送信することもできます。 次のサンプルスクリプトは、これを行う方法を示しています。 この例では、別のリスナープロセスが他のプロセスから送信されたイベントをリッスンし、独自のログ構成に従ってそれらをログに記録します。 この例はそれを行う1つの方法のみを示していますが(たとえば、個別のリスナープロセスではなく、リスナースレッドを使用したい場合があります。実装は類似しています)、リスナーと他のプロセスに対して完全に異なるロギング構成を許可します。アプリケーションで使用でき、独自の特定の要件を満たすコードの基礎として使用できます。
上記のスクリプトの変形は、メインプロセスのロギングを別のスレッドに保持します。
このバリアントは、次の方法を示しています。 特定のロガーに構成を適用します-例: foo
ロガーには、foo
サブシステム内のすべてのイベントをファイルmplog-foo.log
に格納する特別なハンドラーがあります。 これは、メッセージを適切な宛先に送信するために、メインプロセスのロギング機構によって使用されます(ロギングイベントはワーカープロセスで生成されますが)。
コンカレント.futures.ProcessPoolExecutorの使用
concurrent.futures.ProcessPoolExecutor を使用してワーカープロセスを開始する場合は、キューを少し異なる方法で作成する必要があります。 それ以外の
あなたが使用する必要があります
次に、これからワーカーの作成を置き換えることができます。
これに(最初に current.futures をインポートすることを忘れないでください):
ファイルローテーションの使用
ログファイルを特定のサイズに拡大してから、新しいファイルを開いてそれにログを記録したい場合があります。 これらのファイルを一定数保持することをお勧めします。その数のファイルが作成されたら、ファイルの数とサイズの両方が制限されるようにファイルをローテーションします。 この使用パターンの場合、ロギングパッケージはRotatingFileHandler
を提供します。
結果は6つの個別のファイルになり、それぞれにアプリケーションのログ履歴の一部が含まれます。
logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5
最新のファイルは常にlogging_rotatingfile_example.out
であり、サイズ制限に達するたびに、サフィックス.1
で名前が変更されます。 既存の各バックアップファイルの名前が変更されてサフィックスがインクリメントされ(.1
は.2
などになります)、.6
ファイルは消去されます。
明らかに、この例では、極端な例としてログの長さを非常に小さく設定しています。 maxBytes を適切な値に設定することをお勧めします。
代替フォーマットスタイルの使用
ロギングがPython標準ライブラリに追加されたとき、可変コンテンツでメッセージをフォーマットする唯一の方法は、%-f ormattingメソッドを使用することでした。 それ以来、Pythonは2つの新しいフォーマットアプローチを獲得しました: string.Template (Python 2.4で追加)と str.format()(Python 2.6で追加)。
ロギング(3.2以降)は、これら2つの追加のフォーマットスタイルのサポートを改善します。 Formatter
クラスが拡張され、style
という名前の追加のオプションのキーワードパラメーターを受け取るようになりました。 これはデフォルトで'%'
になりますが、他の可能な値は'{'
と'$'
で、他の2つのフォーマットスタイルに対応します。 下位互換性はデフォルトで維持されますが(予想どおり)、スタイルパラメータを明示的に指定することで、 str.format()または string.Templateで機能するフォーマット文字列を指定できます。 。 可能性を示すためのコンソールセッションの例を次に示します。
ログへの最終出力用のログメッセージのフォーマットは、個々のログメッセージの作成方法とは完全に独立していることに注意してください。 ここに示すように、それでも%-f ormattingを使用できます。
ロギングコール(logger.debug()
、logger.info()
など)は、実際のロギングメッセージ自体の位置パラメータのみを取り、キーワードパラメータは、実際のロギングコールの処理方法のオプションを決定するためにのみ使用されます(例: トレースバック情報をログに記録する必要があることを示すexc_info
キーワードパラメーター、またはログに追加する追加のコンテキスト情報を示すextra
キーワードパラメーター)。 したがって、 str.format()または string.Template 構文を使用してロギング呼び出しを直接行うことはできません。これは、ロギングパッケージが内部的に%-f ormattingを使用してフォーマット文字列と変数引数をマージするためです。 。 既存のコードにあるすべてのロギング呼び出しは%-f ormat文字列を使用するため、下位互換性を維持しながらこれを変更することはありません。
ただし、{}-および$-フォーマットを使用して個々のログメッセージを作成する方法があります。 メッセージの場合、任意のオブジェクトをメッセージ形式の文字列として使用でき、ロギングパッケージはそのオブジェクトに対してstr()
を呼び出して、実際の形式の文字列を取得することを思い出してください。 次の2つのクラスを検討してください。
これらのいずれかをフォーマット文字列の代わりに使用して、{}-または$-フォーマットを使用して、「%(message)s」または「」の代わりにフォーマットされたログ出力に表示される実際の「メッセージ」部分を作成できます。 {メッセージ}」または「$メッセージ」。 何かをログに記録するときはいつでもクラス名を使用するのは少し扱いにくいですが、__(二重アンダースコア-_と混同しないでください。単一のアンダースコアは[の同義語/エイリアスとして使用されます)などのエイリアスを使用すると非常に快適です。 X236X] gettext.gettext()またはその兄弟)。
上記のクラスはPythonには含まれていませんが、コピーして独自のコードに貼り付けるのは簡単です。 これらは次のように使用できます(wherever
というモジュールで宣言されていると仮定します)。
上記の例ではprint()
を使用してフォーマットがどのように機能するかを示していますが、もちろんlogger.debug()
などを使用して、このアプローチを使用して実際にログを記録します。
注意すべき点の1つは、このアプローチではパフォーマンスが大幅に低下することはないということです。実際のフォーマットは、ロギング呼び出しを行ったときではなく、ロギングされたメッセージがハンドラーによって実際にログに出力されようとしているとき(およびその場合)に発生します。 したがって、つまずく可能性のあるわずかに珍しいことは、かっこがフォーマット文字列だけでなく、フォーマット文字列と引数を囲んでいることです。 これは、__表記が、XXXMessageクラスの1つへのコンストラクター呼び出しの単なるシンタックスシュガーであるためです。
必要に応じて、次の例のように、LoggerAdapter
を使用して、上記と同様の効果を実現できます。
上記のスクリプトは、Python 3.2以降で実行すると、メッセージHello, world!
をログに記録する必要があります。
LogRecordのカスタマイズ
すべてのロギングイベントは、 LogRecord インスタンスで表されます。 イベントがログに記録され、ロガーのレベルでフィルターで除外されない場合、 LogRecord が作成され、イベントに関する情報が入力されてから、そのロガー(およびその祖先、までは階層のそれ以上の伝播が無効になっているロガー)。 Python 3.2より前は、この作成が行われた場所は2つだけでした。
- Logger.makeRecord()。これは、イベントをログに記録する通常のプロセスで呼び出されます。 これにより、 LogRecord が直接呼び出され、インスタンスが作成されました。
- makeLogRecord()。これは、LogRecordに追加される属性を含むディクショナリで呼び出されます。 これは通常、適切な辞書がネットワーク経由で受信されたときに呼び出されます(例: SocketHandler を介したピクルス形式、または HTTPHandler を介したJSON形式)。
これは通常、 LogRecord で何か特別なことをする必要がある場合、次のいずれかを行う必要があることを意味します。
- Logger.makeRecord()をオーバーライドする独自の Logger サブクラスを作成し、 setLoggerClass()を使用して設定してから、気になるロガーをインスタンス化します。
- Filter をロガーまたはハンドラーに追加します。これにより、 filter()メソッドが呼び出されたときに必要な特別な操作が行われます。
最初のアプローチは、(たとえば)いくつかの異なるライブラリが異なることをしたいというシナリオでは少し扱いにくいでしょう。 それぞれが独自の Logger サブクラスを設定しようとし、これを最後に実行したものが勝ちます。
2番目のアプローチは、多くの場合に適切に機能しますが、たとえば、 LogRecord の特殊なサブクラスを使用します。 ライブラリ開発者はロガーに適切なフィルターを設定できますが、新しいロガーを導入するたびにこれを行うことを忘れないでください(新しいパッケージまたはモジュールを追加して実行するだけで実行できます)。
モジュールレベルで)。 それはおそらく考えるには多すぎることの1つです。 開発者は、トップレベルのロガーに接続された NullHandler にフィルターを追加することもできますが、アプリケーション開発者がハンドラーを下位レベルのライブラリロガーにアタッチした場合、これは呼び出されません。したがって、そのハンドラーからの出力はライブラリ開発者の意図を反映していません。
Python 3.2以降では、 LogRecord の作成は、指定可能なファクトリを介して行われます。 ファクトリは、 setLogRecordFactory()で設定し、 getLogRecordFactory()で問い合わせることができる呼び出し可能ファイルです。 LogRecord がファクトリのデフォルト設定であるため、ファクトリは LogRecord コンストラクタと同じ署名で呼び出されます。
このアプローチにより、カスタムファクトリはLogRecord作成のすべての側面を制御できます。 たとえば、次のようなパターンを使用して、サブクラスを返すか、作成したレコードにいくつかの属性を追加することができます。
このパターンにより、さまざまなライブラリがファクトリをチェーン化できます。それらが互いの属性を上書きしたり、標準として提供されている属性を意図せずに上書きしたりしない限り、驚くことはありません。 ただし、チェーン内の各リンクはすべてのロギング操作に実行時のオーバーヘッドを追加することに注意してください。この手法は、フィルターを使用しても目的の結果が得られない場合にのみ使用してください。
QueueHandlerのサブクラス化-ZeroMQの例
QueueHandler
サブクラスを使用して、ZeroMQの「公開」ソケットなどの他の種類のキューにメッセージを送信できます。 以下の例では、ソケットは個別に作成され、ハンドラーに(「キュー」として)渡されます。
もちろん、これを整理する方法は他にもあります。たとえば、ハンドラーがソケットを作成するために必要なデータを渡すなどです。
QueueListenerのサブクラス化-ZeroMQの例
QueueListener
をサブクラス化して、ZeroMQの「サブスクライブ」ソケットなどの他の種類のキューからメッセージを取得することもできます。 次に例を示します。
も参照してください
- モジュールロギング
- ロギングモジュールのAPIリファレンス。
- モジュール logging.config
- ロギングモジュールの構成API。
- モジュール logging.handlers
- ロギングモジュールに含まれている便利なハンドラー。
辞書ベースの構成の例
以下は、ロギング構成ディクショナリの例です。これは、Djangoプロジェクトのドキュメントから抜粋したものです。 このディクショナリは dictConfig()に渡され、構成が有効になります。
この構成の詳細については、Djangoドキュメントの関連セクションを参照してください。
ローテーターとネーマーを使用してログローテーション処理をカスタマイズする
ネーマーとローテーターを定義する方法の例を次のスニペットに示します。これは、ログファイルのzlibベースの圧縮を示しています。
これらは、実際のgzipファイルにあるような「コンテナ」がない裸の圧縮データであるため、「真の」.gzファイルではありません。 このスニペットは、説明のみを目的としています。
より複雑なマルチプロセッシングの例
次の作業例は、構成ファイルを使用したマルチプロセッシングでログを使用する方法を示しています。 構成はかなり単純ですが、実際のマルチプロセッシングシナリオでより複雑な構成を実装する方法を説明するのに役立ちます。
この例では、メインプロセスがリスナープロセスと一部のワーカープロセスを生成します。 メインプロセス、リスナー、およびワーカーのそれぞれには、3つの別個の構成があります(ワーカーはすべて同じ構成を共有します)。 メインプロセスでのロギング、ワーカーがQueueHandlerにログを記録する方法、リスナーがQueueListenerとより複雑なロギング構成を実装する方法、およびキューを介して受信したイベントを構成で指定されたハンドラーにディスパッチするように調整する方法を確認できます。 これらの構成は単なる例示であることに注意してください。ただし、この例を独自のシナリオに適合させることができるはずです。
スクリプトは次のとおりです。docstringとコメントは、スクリプトがどのように機能するかを説明していることを願っています。
SysLogHandlerに送信されるメッセージへのBOMの挿入
RFC 5424 では、Unicodeメッセージを次の構造を持つバイトのセットとしてsyslogデーモンに送信する必要があります:オプションの純粋なASCIIコンポーネントとそれに続くUTF-8バイトOrder Mark(BOM)の後に、UTF-8を使用してエンコードされたUnicodeが続きます。 (仕様の 関連セクションを参照してください。)
Python 3.1では、メッセージにBOMを挿入するコードが SysLogHandler に追加されましたが、残念ながら、BOMがメッセージの先頭に表示されるため、純粋なASCIIコンポーネントが許可されないという誤った実装が行われました。その前に表示されます。
この動作が壊れているため、Python3.2.4以降から誤ったBOM挿入コードが削除されています。 ただし、これは置き換えられていません。 RFC 5424 準拠のメッセージを生成する場合は、BOM、その前のオプションの純粋なASCIIシーケンス、およびその後の任意のUnicodeが含まれます。 UTF-8を使用してエンコードされている場合は、次の手順を実行する必要があります。
Formatter インスタンスを SysLogHandler インスタンスに、次のようなフォーマット文字列でアタッチします。
UnicodeコードポイントU + FEFFは、UTF-8を使用してエンコードされると、UTF-8 BOM(バイト文字列
b'\xef\xbb\xbf'
)としてエンコードされます。ASCIIセクションを任意のプレースホルダーに置き換えますが、置換後にそこに表示されるデータが常にASCIIであることを確認してください(そうすると、UTF-8エンコード後も変更されません)。
Unicodeセクションを任意のプレースホルダーに置き換えます。 置換後に表示されるデータにASCII範囲外の文字が含まれている場合は、問題ありません。UTF-8を使用してエンコードされます。
フォーマットされたメッセージは、SysLogHandler
によるUTF-8エンコーディングを使用してエンコードされます。 上記のルールに従うと、 RFC 5424 準拠のメッセージを生成できるはずです。 そうしないと、ロギングは文句を言わないかもしれませんが、メッセージはRFC 5424に準拠せず、syslogデーモンが文句を言うかもしれません。
構造化ロギングの実装
ほとんどのロギングメッセージは人間が読むことを目的としているため、機械で簡単に解析することはできませんが、プログラムで解析できる構造化された形式でメッセージを出力したい場合があります(ログメッセージを解析するために複雑な正規表現が必要です)。 これは、ロギングパッケージを使用して簡単に実現できます。 これを実現する方法はいくつかありますが、以下はJSONを使用してマシンで解析可能な方法でイベントをシリアル化する簡単なアプローチです。
上記のスクリプトを実行すると、次のように出力されます。
message 1 >>> {"fnum": 123.456, "num": 123, "bar": "baz", "foo": "bar"}
使用するPythonのバージョンによって、アイテムの順序が異なる場合があることに注意してください。
より特殊な処理が必要な場合は、次の完全な例のように、カスタムJSONエンコーダーを使用できます。
上記のスクリプトを実行すると、次のように出力されます。
message 1 >>> {"snowman": "\u2603", "set_value": [1, 2, 3]}
使用するPythonのバージョンによって、アイテムの順序が異なる場合があることに注意してください。
dictConfig()を使用したハンドラーのカスタマイズ
ロギングハンドラーを特定の方法でカスタマイズしたい場合があります。 dictConfig()を使用すると、サブクラス化せずにこれを実行できる場合があります。 例として、ログファイルの所有権を設定したい場合があると考えてください。 POSIXでは、これは shutil.chown()を使用して簡単に実行できますが、stdlibのファイルハンドラーは組み込みのサポートを提供していません。 次のような単純な関数を使用して、ハンドラーの作成をカスタマイズできます。
次に、 dictConfig()に渡されるログ構成で、次の関数を呼び出してログハンドラーを作成するように指定できます。
この例では、説明のために、pulse
ユーザーとグループを使用して所有権を設定しています。 それをまとめて動作するスクリプトchowntest.py
:
これを実行するには、おそらくroot
として実行する必要があります。
$ sudo python3.3 chowntest.py
$ cat chowntest.log
2013-11-05 09:34:51,128 DEBUG mylogger A debug message
$ ls -l chowntest.log
-rw-r--r-- 1 pulse pulse 55 2013-11-05 09:34 chowntest.log
この例ではPython3.3を使用していることに注意してください。これは、 shutil.chown()が表示される場所だからです。 このアプローチは、 dictConfig()をサポートするすべてのPythonバージョン、つまりPython 2.7、3.2以降で機能するはずです。 3.3より前のバージョンでは、たとえばを使用して実際の所有権の変更を実装する必要があります。 os.chown()。
実際には、ハンドラー作成関数は、プロジェクトのどこかにあるユーティリティモジュールにある場合があります。 構成の行の代わりに:
たとえば、次のように使用できます。
ここで、project.util
は、関数が存在するパッケージの実際の名前に置き換えることができます。 上記の動作スクリプトでは、'ext://__main__.owned_file_handler'
を使用すると動作するはずです。 ここで、実際の呼び出し可能オブジェクトは、ext://
仕様の dictConfig()によって解決されます。
この例は、他の種類のファイル変更を実装する方法も示していることを願っています。 特定のPOSIX許可ビットを設定する-同じ方法で、 os.chmod()を使用します。
もちろん、このアプローチは、 FileHandler 以外のタイプのハンドラー(たとえば、回転するファイルハンドラーの1つ、またはまったく別のタイプのハンドラー)に拡張することもできます。
アプリケーション全体で特定のフォーマットスタイルを使用する
Python 3.2では、 Formatter はstyle
キーワードパラメーターを取得しました。これは、下位互換性のためにデフォルトで%
に設定されていますが、{
または$
は、 str.format()および string.Template でサポートされているフォーマットアプローチをサポートします。 これは、ログへの最終出力用のログメッセージのフォーマットを管理し、個々のログメッセージの作成方法と完全に直交していることに注意してください。
ロギング呼び出し( debug()、 info()など)は、実際のロギングメッセージ自体の位置パラメーターのみを取り、キーワードパラメーターはロギングの処理方法のオプションを決定するためにのみ使用されます。呼び出し(例: トレースバック情報をログに記録する必要があることを示すexc_info
キーワードパラメーター、またはログに追加する追加のコンテキスト情報を示すextra
キーワードパラメーター)。 したがって、 str.format()または string.Template 構文を使用してロギング呼び出しを直接行うことはできません。これは、ロギングパッケージが内部的に%-f ormattingを使用してフォーマット文字列と変数引数をマージするためです。 。 既存のコードにあるすべてのロギング呼び出しは%-f ormat文字列を使用するため、下位互換性を維持しながらこれを変更することはありません。
There have been suggestions to associate format styles with specific loggers, but that approach also runs into backward compatibility problems because any existing code could be using a given logger name and using %-formatting.
サードパーティのライブラリとコードの間でロギングが相互運用可能に機能するためには、フォーマットに関する決定を個々のロギング呼び出しのレベルで行う必要があります。 これにより、代替のフォーマットスタイルに対応できるいくつかの方法が開かれます。
LogRecordファクトリの使用
Python 3.2では、上記の Formatter の変更に加えて、ロギングパッケージで、ユーザーが setLogRecordFactory()を使用して独自の LogRecord サブクラスを設定できるようになりました。 ] 関数。 これを使用して、 LogRecord の独自のサブクラスを設定できます。これは、 getMessage()メソッドをオーバーライドすることで正しいことを行います。 このメソッドの基本クラスの実装は、msg % args
フォーマットが発生する場所であり、代替フォーマットに置き換えることができます。 ただし、他のコードとの相互運用性を確保するために、すべてのフォーマットスタイルをサポートし、デフォルトとして%-f ormattingを許可するように注意する必要があります。 基本実装と同様に、str(self.msg)
の呼び出しにも注意する必要があります。
詳細については、 setLogRecordFactory()および LogRecord のリファレンスドキュメントを参照してください。
カスタムメッセージオブジェクトの使用
{}-および$-フォーマットを使用して個々のログメッセージを作成する、もう1つのおそらくより簡単な方法があります。 (任意のオブジェクトをメッセージとして使用するから)ロギング時に任意のオブジェクトをメッセージ形式の文字列として使用でき、ロギングパッケージがその上で str()を呼び出すことを思い出してください。実際のフォーマット文字列を取得するオブジェクト。 次の2つのクラスを検討してください。
これらのいずれかをフォーマット文字列の代わりに使用して、{}-または$-フォーマットを使用して、「%(message)s」または「」の代わりにフォーマットされたログ出力に表示される実際の「メッセージ」部分を作成できます。 {メッセージ}」または「$メッセージ」。 何かをログに記録するときにクラス名を使用するのが少し扱いにくい場合は、メッセージにM
や_
などのエイリアスを使用すると(またはローカリゼーションに_
を使用している場合は、おそらく__
)。
このアプローチの例を以下に示します。 まず、 str.format()でフォーマットします。
次に、 string.Template を使用したフォーマット:
注意すべき点の1つは、このアプローチではパフォーマンスが大幅に低下することはないということです。実際のフォーマットは、ロギング呼び出しを行ったときではなく、ロギングされたメッセージがハンドラーによって実際にログに出力されようとしているとき(およびその場合)に発生します。 したがって、つまずく可能性のあるわずかに珍しいことは、かっこがフォーマット文字列だけでなく、フォーマット文字列と引数を囲んでいることです。 これは、__表記が、上記のXXXMessage
クラスの1つへのコンストラクター呼び出しの単なるシンタックスシュガーであるためです。
dictConfig()を使用したフィルターの構成
は dictConfig()を使用してフィルターを構成できますが、その方法は一見して明らかではないかもしれません(したがってこのレシピ)。 Filter は標準ライブラリに含まれる唯一のフィルタークラスであり、多くの要件に対応する可能性は低いため(基本クラスとしてのみ存在します)、通常は独自の Filterを定義する必要があります。 サブクラスとオーバーライドされた filter()メソッド。 これを行うには、フィルターの構成ディクショナリで()
キーを指定し、フィルターの作成に使用される呼び出し可能オブジェクトを指定します(クラスが最も明白ですが、[ X227X] Filter インスタンス)。 完全な例を次に示します。
この例は、インスタンスを構成するcallableに構成データをキーワードパラメーターの形式で渡す方法を示しています。 実行すると、上記のスクリプトが出力されます。
changed: hello
これは、フィルターが構成どおりに機能していることを示しています。
注意すべきいくつかの追加ポイント:
- 構成で呼び出し可能オブジェクトを直接参照できない場合(例: 別のモジュールにあり、構成ディクショナリがある場所に直接インポートできない場合は、外部オブジェクトへのアクセスの説明に従って
ext://...
の形式を使用できます。 たとえば、上記の例では、MyFilter
の代わりにテキスト'ext://__main__.MyFilter'
を使用できます。 - フィルタの場合と同様に、この手法を使用してカスタムハンドラとフォーマッタを構成することもできます。 ロギングが構成でのユーザー定義オブジェクトの使用をサポートする方法の詳細については、ユーザー定義オブジェクトを参照してください。また、上記の他のクックブックレシピ dictConfig()を使用したハンドラーのカスタマイズを参照してください。
カスタマイズされた例外フォーマット
カスタマイズされた例外フォーマットを実行したい場合があります-引数のために、例外情報が存在する場合でも、ログに記録されたイベントごとに正確に1行が必要だとします。 次の例に示すように、カスタムフォーマッタクラスを使用してこれを行うことができます。
実行すると、正確に2行のファイルが生成されます。
28/01/2015 07:21:23|INFO|Sample message|
28/01/2015 07:21:23|ERROR|ZeroDivisionError: integer division or modulo by zero|'Traceback (most recent call last):\n File "logtest7.py", line 30, in main\n x = 1 / 0\nZeroDivisionError: integer division or modulo by zero'|
上記の処理は単純ですが、例外情報を好みに合わせてフォーマットする方法を示しています。 traceback モジュールは、より専門的なニーズに役立つ場合があります。
ロギングメッセージを話す
ロギングメッセージを可視形式ではなく可聴形式でレンダリングすることが望ましい場合があります。 これは、Pythonバインディングがなくても、システムでテキスト読み上げ(TTS)機能を使用できる場合は簡単に実行できます。 ほとんどのTTSシステムには、実行できるコマンドラインプログラムがあり、これはサブプロセスを使用してハンドラーから呼び出すことができます。 ここでは、TTSコマンドラインプログラムがユーザーとの対話や完了までに長い時間がかかることを想定しておらず、ログに記録されるメッセージの頻度がユーザーをメッセージでいっぱいにするほど高くはなく、メッセージは同時にではなく一度に1つずつ話されます。以下の実装例では、次のメッセージが処理される前に1つのメッセージが話されるのを待ちます。これにより、他のハンドラーが待機し続ける可能性があります。 これは、espeak
TTSパッケージが利用可能であることを前提としたアプローチを示す短い例です。
実行すると、このスクリプトは女性の声で「こんにちは」、次に「さようなら」と言うはずです。
もちろん、上記のアプローチは、他のTTSシステム、さらにはコマンドラインから実行される外部プログラムを介してメッセージを処理できる他のシステムにも適用できます。
ロギングメッセージをバッファリングし、条件付きで出力する
一時的な領域にメッセージを記録し、特定の条件が発生した場合にのみメッセージを出力したい場合があります。 たとえば、関数でデバッグイベントのログ記録を開始し、関数がエラーなしで完了した場合、収集したデバッグ情報でログを乱雑にしたくないが、エラーがある場合は、すべてのデバッグが必要な場合があります。出力する情報とエラー。
これは、ロギングをこのように動作させたい関数のデコレータを使用してこれを行う方法を示す例です。 logging.handlers.MemoryHandler を利用します。これにより、何らかの条件が発生するまでログに記録されたイベントをバッファリングできます。条件が発生すると、バッファリングされたイベントはflushed
-別のハンドラー([X212X ] ハンドラー)処理用。 デフォルトでは、MemoryHandler
は、バッファーがいっぱいになるか、レベルが指定されたしきい値以上のイベントが表示されるとフラッシュされます。 カスタムフラッシュ動作が必要な場合は、MemoryHandler
のより特殊なサブクラスでこのレシピを使用できます。
サンプルスクリプトには、foo
という単純な関数があります。この関数は、すべてのログレベルを循環し、sys.stderr
に書き込み、ログを記録しようとしているレベルを示し、実際にそのレベルでメッセージをログに記録します。レベル。 パラメータをfoo
に渡すことができます。これは、trueの場合、ERRORおよびCRITICALレベルでログに記録されます。それ以外の場合は、DEBUG、INFO、およびWARNINGレベルでのみログに記録されます。
スクリプトは、必要な条件付きロギングを実行するデコレータでfoo
をデコレートするように調整するだけです。 デコレータはロガーをパラメータとして受け取り、デコレートされた関数の呼び出し中にメモリハンドラをアタッチします。 デコレータは、ターゲットハンドラ、フラッシュが発生するレベル、およびバッファの容量(バッファされたレコードの数)を使用して、さらにパラメータ化できます。 これらはデフォルトで StreamHandler になり、それぞれsys.stderr
、logging.ERROR
、および100
に書き込みます。
スクリプトは次のとおりです。
このスクリプトを実行すると、次の出力が表示されます。
Calling undecorated foo with False
about to log at DEBUG ...
about to log at INFO ...
about to log at WARNING ...
Calling undecorated foo with True
about to log at DEBUG ...
about to log at INFO ...
about to log at WARNING ...
about to log at ERROR ...
about to log at CRITICAL ...
Calling decorated foo with False
about to log at DEBUG ...
about to log at INFO ...
about to log at WARNING ...
Calling decorated foo with True
about to log at DEBUG ...
about to log at INFO ...
about to log at WARNING ...
about to log at ERROR ...
Actually logged at DEBUG
Actually logged at INFO
Actually logged at WARNING
Actually logged at ERROR
about to log at CRITICAL ...
Actually logged at CRITICAL
ご覧のとおり、実際のログ出力は、重大度がERROR以上のイベントがログに記録された場合にのみ発生しますが、その場合、重大度の低い以前のイベントもログに記録されます。
もちろん、従来の装飾手段を使用することもできます。
構成によるUTC(GMT)を使用した時間のフォーマット
UTCを使用して時刻をフォーマットしたい場合があります。これは、以下に示す UTCFormatter などのクラスを使用して実行できます。
その後、 Formatter の代わりにUTCFormatter
をコードで使用できます。 構成を介してこれを行う場合は、 dictConfig() APIを使用して、次の完全な例で示すアプローチを使用できます。
このスクリプトを実行すると、次のように出力されます。
2015-10-17 12:53:29,501 The local time is Sat Oct 17 13:53:29 2015
2015-10-17 13:53:29,501 The local time is Sat Oct 17 13:53:29 2015
ハンドラーごとに1つずつ、現地時間とUTCの両方として時刻がどのようにフォーマットされるかを示します。
選択的ロギングのためのコンテキストマネージャーの使用
ロギング構成を一時的に変更し、何かを行った後に元に戻すと便利な場合があります。 このため、コンテキストマネージャは、ロギングコンテキストを保存および復元する最も明白な方法です。 このようなコンテキストマネージャーの簡単な例を次に示します。これにより、オプションでログレベルを変更し、純粋にコンテキストマネージャーのスコープ内にログハンドラーを追加できます。
レベル値を指定すると、ロガーのレベルは、コンテキストマネージャーがカバーするwithブロックのスコープ内でその値に設定されます。 ハンドラーを指定すると、ブロックに入るときにロガーに追加され、ブロックから出るときに削除されます。 ブロック終了時にハンドラーを閉じるようにマネージャーに依頼することもできます。ハンドラーが不要になった場合は、これを行うことができます。
それがどのように機能するかを説明するために、上記に次のコードブロックを追加できます。
最初にロガーのレベルをINFO
に設定したため、メッセージ#1は表示され、メッセージ#2は表示されません。 次に、次のwith
ブロックで一時的にレベルをDEBUG
に変更すると、メッセージ#3が表示されます。 ブロックが終了すると、ロガーのレベルがINFO
に復元されるため、メッセージ#4は表示されません。 次のwith
ブロックでは、レベルをDEBUG
に再度設定しますが、sys.stdout
に書き込むハンドラーも追加します。 したがって、メッセージ#5はコンソールに2回表示されます(1回はstderr
経由、もう1回はstdout
経由)。 with
ステートメントの完了後、ステータスは以前と同じであるため、メッセージ#6は表示されますが(メッセージ#1のように)、メッセージ#7は表示されません(メッセージ#2のように)。
結果のスクリプトを実行すると、結果は次のようになります。
$ python logctx.py
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.
もう一度実行しても、stderr
を/dev/null
にパイプすると、stdout
に書き込まれる唯一のメッセージである次のメッセージが表示されます。
$ python logctx.py 2>/dev/null
5. This should appear twice - once on stderr and once on stdout.
繰り返しますが、stdout
を/dev/null
にパイプすると、次のようになります。
$ python logctx.py >/dev/null
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.
この場合、stdout
に出力されたメッセージ#5は期待通りに表示されません。
もちろん、ここで説明するアプローチは、たとえばロギングフィルターを一時的にアタッチするなど、一般化することができます。 上記のコードはPython2とPython3で機能することに注意してください。
CLIアプリケーションスターターテンプレート
これがあなたができる方法を示す例です:
- コマンドライン引数に基づいてログレベルを使用する
- 別々のファイル内の複数のサブコマンドにディスパッチし、すべてが一貫した方法で同じレベルでログを記録します
- シンプルで最小限の構成を利用する
一部のサービスを停止、開始、または再起動するコマンドラインアプリケーションがあるとします。 これは、説明の目的で、アプリケーションのメインスクリプトであるファイルapp.py
として編成でき、個々のコマンドはstart.py
、stop.py
、および [に実装されています。 X191X]。 さらに、コマンドライン引数を介してアプリケーションの詳細度を制御したいとします。デフォルトはlogging.INFO
です。 app.py
を作成する1つの方法は次のとおりです。
また、start
、stop
、およびrestart
コマンドは、次のように別々のモジュールに実装できます。
したがって、停止するために:
同様に再起動する場合:
このアプリケーションをデフォルトのログレベルで実行すると、次のような出力が得られます。
$ python app.py start foo
INFO start Started the 'foo' service.
$ python app.py stop foo bar
INFO stop Stopped the 'foo' and 'bar' services.
$ python app.py restart foo bar baz
INFO restart Restarted the 'foo', 'bar' and 'baz' services.
最初の単語はログレベルであり、2番目の単語はイベントがログに記録された場所のモジュールまたはパッケージ名です。
ログレベルを変更すると、ログに送信される情報を変更できます。 たとえば、より多くの情報が必要な場合:
$ python app.py --log-level DEBUG start foo
DEBUG start About to start foo
INFO start Started the 'foo' service.
$ python app.py --log-level DEBUG stop foo bar
DEBUG stop About to stop 'foo' and 'bar'
INFO stop Stopped the 'foo' and 'bar' services.
$ python app.py --log-level DEBUG restart foo bar baz
DEBUG restart About to restart 'foo', 'bar' and 'baz'
INFO restart Restarted the 'foo', 'bar' and 'baz' services.
そして、私たちがもっと少なくしたいのなら:
$ python app.py --log-level WARNING start foo
$ python app.py --log-level WARNING stop foo bar
$ python app.py --log-level WARNING restart foo bar baz
この場合、WARNING
レベル以上のログは記録されないため、コマンドはコンソールに何も出力しません。
ロギング用のQtGUI
時々出てくる質問は、GUIアプリケーションにログインする方法についてです。 Qt フレームワークは、 PySide2 または PyQt5 ライブラリを使用したPythonバインディングを備えた人気のあるクロスプラットフォームUIフレームワークです。
次の例は、QtGUIにログインする方法を示しています。 これにより、呼び出し可能オブジェクトを受け取る単純なQtHandler
クラスが導入されます。これは、GUI更新を行うメインスレッドのスロットである必要があります。 ワーカースレッドも作成され、UI自体(手動ログのボタンを使用)とバックグラウンドで作業を行うワーカースレッド(ここでは、ランダムなレベルでメッセージをランダムにログに記録するだけ)の両方からGUIにログを記録する方法を示します。間の短い遅延)。
ワーカースレッドは、 threading モジュールではなくQtのQThread
クラスを使用して実装されます。これは、他のQt
コンポーネント。
このコードは、PySide2
またはPyQt5
の最近のリリースで機能するはずです。 このアプローチを以前のバージョンのQtに適応させることができるはずです。 詳細については、コードスニペットのコメントを参照してください。
避けるべきパターン
前のセクションでは、実行または対処する必要がある可能性のあることを行う方法について説明しましたが、役に立たないであるため、ほとんどの場合回避する必要があるいくつかの使用パターンについて言及する価値があります。 以下のセクションは順不同です。
同じログファイルを複数回開く
Windowsでは、「ファイルは別のプロセスで使用されています」というエラーが発生するため、通常、同じファイルを複数回開くことはできません。 ただし、POSIXプラットフォームでは、同じファイルを複数回開いてもエラーは発生しません。 これは、たとえば次のようにして誤って行われる可能性があります。
- 同じファイルを参照するファイルハンドラーを複数回追加する(例: コピー/貼り付け/変更を忘れるエラーによる)。
- 名前が異なるために外観が異なるが、一方が他方へのシンボリックリンクであるため同じである2つのファイルを開く。
- プロセスをフォークし、その後、親と子の両方が同じファイルへの参照を持ちます。 これは、たとえば、マルチプロセッシングモジュールの使用による可能性があります。
ファイルを複数回開くと、ほとんどの場合が動作するように見える場合がありますが、実際にはいくつかの問題が発生する可能性があります。
- 複数のスレッドまたはプロセスが同じファイルに書き込もうとするため、ログ出力が文字化けする可能性があります。 ロギングは、複数のスレッドによる同じハンドラーインスタンスの同時使用を防ぎますが、同じファイルを指す2つの異なるハンドラーインスタンスを使用して2つの異なるスレッドによって同時書き込みが試行された場合、そのような保護はありません。
- ファイルを削除しようとしました(例: ファイルのローテーション中)は、それを指す別の参照があるため、サイレントに失敗します。 これにより、混乱が生じ、デバッグ時間が無駄になる可能性があります。ログエントリが予期しない場所に配置されたり、完全に失われたりします。
複数のプロセスから単一のファイルにログを記録するで概説されている手法を使用して、このような問題を回避します。
ロガーをクラスの属性として使用するか、パラメーターとして渡す
これを行う必要があるという珍しいケースがあるかもしれませんが、ロガーはシングルトンであるため、一般的には意味がありません。 コードは常にlogging.getLogger(name)
を使用して名前で特定のロガーインスタンスにアクセスできるため、インスタンスを渡し、インスタンス属性として保持することは無意味です。 JavaやC#などの他の言語では、ロガーは静的なクラス属性であることが多いことに注意してください。 ただし、このパターンは、モジュール(クラスではなく)がソフトウェア分解の単位であるPythonでは意味がありません。
NullHandler以外のハンドラーをライブラリのロガーに追加する
ハンドラー、フォーマッター、フィルターを追加してロギングを構成するのは、ライブラリ開発者ではなく、アプリケーション開発者の責任です。 ライブラリを保守している場合は、 NullHandler インスタンス以外のロガーにハンドラーを追加しないようにしてください。
たくさんのロガーを作成する
ロガーは、スクリプトの実行中に解放されることのないシングルトンであるため、多数のロガーを作成すると、メモリを使い果たして解放できなくなります。 たとえばごとにロガーを作成するのではなく ファイル処理またはネットワーク接続が確立されたら、既存のメカニズムを使用してコンテキスト情報をログに渡し、作成されたロガーをアプリケーション内の領域を記述するものに制限します(通常はモジュールですが、それよりも少し細かい場合もあります) 。