33

log.py少なくとも 2 つの他のモジュール (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)

ファイルとコンソールのロガーを初期化するLoggingInitfromを呼び出します。server.py次に、あちこちから呼び出すWriteLogため、複数のスレッドがアクセスfileLoggerしてconsoleLogger.

ログ ファイルをさらに保護する必要はありますか? ドキュメントには、スレッド ロックはハンドラーによって処理されると記載されています。

4

2 に答える 2

63

幸いなことに、スレッド セーフのために特別なことをする必要はありません。また、クリーン シャットダウンのために特別なことは何も必要ないか、ほとんど簡単なことです。詳細については後述します。

悪いニュースは、その時点に到達する前にコードに重大な問題があることです:fileLoggerconsoleLoggerは同じオブジェクトです。のドキュメントgetLogger()から:

指定された名前のロガーを返すか、名前が指定されていない場合は、階層のルート ロガーであるロガーを返します。

したがって、ルートロガーを取得して として保存しfileLogger、次にルートロガーを取得して として保存していconsoleLoggerます。したがって、 ではLoggingInit、 を初期化fileLoggerしてから、同じオブジェクトを別の名前で別の値で再初期化します。

同じロガーに複数のハンドラーを追加できます。また、それぞれに対して実際に行う唯一の初期化は であるためaddHandler、コードは意図したとおりに機能しますが、偶然にすぎません。そして、ちょっとだけ。に合格すると、両方のログで各メッセージの 2 つのコピーを取得し、合格print_screen=Trueしてもコンソールにコピーを取得しますprint_screen=False

実際には、グローバル変数を使用する理由はまったくありません。要点はgetLogger()、必要なときにいつでも呼び出すことができ、グローバル ルート ロガーを取得できるため、どこにも保存する必要がないということです。


もっと小さな問題は、HTML に挿入するテキストをエスケープしていないことです。ある時点で、文字列をログに記録しようとすると"a < b"、問題が発生します。

それほど深刻ではありませんが、 の内側にない一連の<p>タグは、有効な HTML ドキュメントではありません。しかし、多くのビューアーは自動的にそれを処理するか、ログを表示する前に後処理を簡単に行うことができます。ただし、これを本当に正しくしたい場合は、サブクラス化して、空のファイルが指定されている場合はヘッダーを追加し、存在する場合はフッターを削除してから、フッターを追加する必要があります。<body><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 つのハンドラーでルート ロガーを使用することです。

console_logging_level_switchまた、2 つのロガーが必要な場合でも、別個のおよびfile_logging_level_switchマップは必要ありません。を呼び出すことLogger.debug(msg)は、 を呼び出すこととまったく同じですLogger.log(DEBUG, msg)debugカスタム レベル名などを標準名 などにマップする何らかの方法が必要DEBUGですが、ロガーごとに 1 回検索するのではなく、1 回検索するだけでかまいません (さらに、名前が標準名にすぎない場合)。異なるキャストを使用すると、ごまかすことができます)。

これはすべて、`複数のハンドラーとフォーマッターのセクションと、ロギング クックブックの残りの部分でかなり詳しく説明されています。

これを行う標準的な方法の唯一の問題は、メッセージごとにコンソール ロギングを簡単にオフにできないことです。それは、普通のことではないからです。通常は、レベルごとにログを記録し、ファイル ログのログ レベルを高く設定します。

ただし、さらに制御したい場合は、フィルターを使用できます。たとえば、FileHandlerすべてを受け入れるフィルターと、ConsoleHandlerで始まる何かを必要とするフィルターを指定してから、consoleフィルターを使用します'console' if print_screen else ''。それはWriteLogほぼワンライナーになります。

改行を削除するために余分な 2 行が必要ですが、必要に応じて、フィルター内またはアダプターを介してそれを行うこともできます。(もう一度、クックブックを参照してください。) そして、WriteLog実際にはワンライナーです。

于 2013-06-05T00:36:25.353 に答える
7

Python ロギングはスレッド セーフです。

したがって、Python (ライブラリ) コードに問題はありません。

複数のスレッド ( ) から呼び出すルーチンはWriteLog、共有状態に書き込みません。したがって、コードに問題はありません。

だからあなたは大丈夫です。

于 2013-06-05T00:36:26.810 に答える