少なくとも2つの他のモジュール(log.py
およびserver.py
)で使用されるdevice.py
モジュールがあります。
次のグローバルがあります。
fileLogger = logging.getLogger()
fileLogger.setLevel(logging.DEBUG)
consoleLogger = logging.getLogger()
consoleLogger.setLevel(logging.DEBUG)
file_logging_level_switch = {
'debug': fileLogger.debug,
'info': fileLogger.info,
'warning': fileLogger.warning,
'error': fileLogger.error,
'critical': fileLogger.critical
}
console_logging_level_switch = {
'debug': consoleLogger.debug,
'info': consoleLogger.info,
'warning': consoleLogger.warning,
'error': consoleLogger.error,
'critical': consoleLogger.critical
}
次の2つの機能があります。
def LoggingInit( logPath, logFile, html=True ):
global fileLogger
global consoleLogger
logFormatStr = "[%(asctime)s %(threadName)s, %(levelname)s] %(message)s"
consoleFormatStr = "[%(threadName)s, %(levelname)s] %(message)s"
if html:
logFormatStr = "<p>" + logFormatStr + "</p>"
# File Handler for log file
logFormatter = logging.Formatter(logFormatStr)
fileHandler = logging.FileHandler(
"{0}{1}.html".format( logPath, logFile ))
fileHandler.setFormatter( logFormatter )
fileLogger.addHandler( fileHandler )
# Stream Handler for stdout, stderr
consoleFormatter = logging.Formatter(consoleFormatStr)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter( consoleFormatter )
consoleLogger.addHandler( consoleHandler )
そして:
def WriteLog( string, print_screen=True, remove_newlines=True,
level='debug' ):
if remove_newlines:
string = string.replace('\r', '').replace('\n', ' ')
if print_screen:
console_logging_level_switch[level](string)
file_logging_level_switch[level](string)
ファイルロガーとコンソールロガーを初期化するserver.py
からLoggingInit
を呼び出します。次に、WriteLog
をあらゆる場所から呼び出します。そのため、複数のスレッドがfileLogger
とconsoleLogger
にアクセスしています。
ログファイルをさらに保護する必要がありますか?ドキュメントには、スレッドロックはハンドラーによって処理されると記載されています。
良いニュースは、スレッドセーフのために特別なことをする必要はなく、クリーンシャットダウンのために特別なことはほとんど必要ないか、ささいなことです。詳細については後で説明します。
悪いニュースは、その点に到達する前であってもコードに深刻な問題があることです。fileLogger
とconsoleLogger
は同じオブジェクトです。 getLogger()
のドキュメントから:
指定された名前のロガーを返すか、名前が指定されていない場合は、階層のルートロガーであるロガーを返します。
したがって、ルートロガーを取得してfileLogger
として保存し、次にルートロガーを取得してconsoleLogger
として保存します。そのため、LoggingInit
でfileLogger
を初期化してから、同じオブジェクトを異なる名前で異なる値で再初期化します。
can同じロガーに複数のハンドラーを追加します。それぞれに対して実際に行う初期化はaddHandler
だけなので、コードは意図したとおりに動作しますが、偶然です。そして、ちょっとだけ。 _print_screen=True
_を渡すと、両方のログで各メッセージのコピーが2つ取得され、_print_screen=False
_を渡してもコンソールでコピーが取得されます。
実際、グローバル変数を使用する理由はまったくありません。 getLogger()
の要点は、必要なときにいつでも呼び出してグローバルルートロガーを取得できるため、どこにでも保存する必要がないということです。
さらに小さな問題は、HTMLに挿入したテキストをエスケープしないことです。ある時点で、文字列_"a < b"
_をログに記録しようとして、問題が発生します。
それほど深刻ではありませんが、_<p>
_内の_<body>
_内にない_<html>
_タグのシーケンスは、有効なHTMLドキュメントではありません。しかし、多くの視聴者がそれを自動的に処理するか、ログを表示する前に後処理することができます。しかし、本当にこれを正しくしたい場合は、FileHandler
をサブクラス化し、空のファイルが指定されている場合は___init__
_にヘッダーを追加し、フッターが存在する場合はフッターを削除してからclose
フッターを追加します。
実際の質問に戻る:
追加のロックは必要ありません。ハンドラーがcreateLock
、acquire
、およびrelease
を正しく実装している場合(およびスレッドを備えたプラットフォームで呼び出される場合)、ロギングマシンは必要に応じて自動的にロックを取得します各メッセージがアトミックに記録されるようにします。
私の知る限り、ドキュメンテーションは直接ではなく、StreamHandler
とFileHandler
がこれらのメソッドを実装していると言っているわけではなく、( 質問 で言及したテキストは、「ロギングモジュールは、クライアントによる特別な作業を必要とせずにスレッドセーフにすることを目的としている」など)と述べています。また、実装のソース(例: CPython 3.3 )を見ると、両方とも_logging.Handler
_から正しく実装されたメソッドを継承していることがわかります。
同様に、ハンドラーがflush
およびclose
を正しく実装している場合、ロギングマシンは通常のシャットダウン中に正しくファイナライズされることを確認します。
ここで、ドキュメントはStreamHandler.flush()
、FileHandler.flush()
、およびFileHandler.close()
について説明しています。 StreamHandler.close()
がノーオペレーションであることを除いて、ほとんど期待どおりです。つまり、コンソールへの最終的なログメッセージが失われる可能性があることを意味します。ドキュメントから:
close()
メソッドはHandler
から継承されるため、出力は行われないため、時々明示的なflush()
呼び出しが必要になる場合があります。
これがあなたにとって重要であり、それを修正したい場合は、次のようなことをする必要があります。
_class ClosingStreamHandler(logging.StreamHandler):
def close(self):
self.flush()
super().close()
_
そして、ClosingStreamHandler()
の代わりにStreamHandler()
を使用します。
FileHandler
にはこのような問題はありません。
ログを2か所に送信する通常の方法は、それぞれ独自のフォーマッターを持つ2つのハンドラーでルートロガーを使用することです。
また、2つのロガーが必要な場合でも、個別の_console_logging_level_switch
_および_file_logging_level_switch
_マップは必要ありません。 Logger.debug(msg)
の呼び出しは、Logger.log(DEBUG, msg)
の呼び出しとまったく同じです。カスタムレベル名debug
などを標準名DEBUG
などにマッピングする方法が必要ですが、1回につき1回検索するのではなく、1回だけ検索できますロガー(さらに、あなたの名前がキャストの異なる標準名である場合は、ごまかすことができます)。
これはすべて、「 Multiple handlers and formatters 」セクション、およびロギングクックブックの残りの部分で説明されています。
これを行う標準的な方法の唯一の問題は、メッセージごとにコンソールロギングを簡単にオフにできないことです。それは普通のことではないからです。通常、レベルごとにログを記録し、ファイルログでログレベルを高く設定します。
しかし、さらに制御したい場合は、フィルターを使用できます。たとえば、FileHandler
にすべてを受け入れるフィルターを与え、ConsoleHandler
にconsole
で始まるものを必要とするフィルターを与えてから、フィルター_'console' if print_screen else ''
_を使用します。これにより、WriteLog
がほぼ1行になります。
改行を削除するにはさらに2行が必要ですが、必要に応じて、フィルターで、またはアダプターを介してthatを実行することもできます。 (もう一度、クックブックを参照してください。)そして、WriteLog
本当にisワンライナーです。
Pythonロギングはスレッドセーフです。
したがって、Python(ライブラリ)コードでは問題ありません。
複数のスレッド(WriteLog
)から呼び出すルーチンは、共有状態に書き込みません。したがって、コードに問題はありません。
だからあなたは大丈夫です。