Python Loggingで2回表示されるログメッセージ


100

私はPythonロギングを使用しています。何らかの理由で、すべてのメッセージが2回表示されます。

ロギングを構成するモジュールがあります。

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

後で、このメソッドを呼び出してロギングを構成します。

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

そして言うと、buy_hamモジュール内で、次のように呼び出します。

self.logger.info('Successfully able to write to %s' % path)

そして、何らかの理由で、すべてのメッセージが2回表示されます。ストリームハンドラの1つをコメントアウトしましたが、まだ同じです。ちょっと奇妙なもの、なぜこれが起こっているのかわからない...笑 私は明らかな何かを見逃したと仮定します。

乾杯、ビクター


1
configure_logging()(例えばコンストラクタからも)2度呼び出されないことは確かですか?Boy()のインスタンスは1つだけ作成されますか?
Jacek Konieczny

1
self.logger.handlers = [ch]代わりにを使用すると、この問題は解決しますが、たとえば、if not self.logger最初に使用することによって、このコードを2回実行しないようにするのが最善です。
Ninjakannon、

回答:


133

configure_logging2回呼び出しています(おそらくの__init__メソッド内Boy):getLogger同じオブジェクトを返しますがaddHandler、同様のハンドラーがロガーにすでに追加されているかどうかはチェックしません。

そのメソッドの呼び出しをトレースして、これらの1つを削除してみてください。またはのメソッドでlogging_initialized初期化されたフラグを設定しFalse、is ifの場合は何もしないように変更し、ロガーを初期化した後にフラグを設定します。__init__Boyconfigure_logginglogging_initializedTrueTrue

プログラムが複数のBoyインスタンスを作成する場合configure_logging、ハンドラーを追加するグローバル関数と属性をBoy.configure_logging初期化するメソッドのみを使用して、実行方法を変更する必要がありself.loggerます。

これを解決する別の方法は、ロガーのハンドラー属性をチェックすることです。

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
はい、あなたは正しかった-ばかげた私。私はそれをinitだけでなく、明示的に他の場所でも呼び出しました。笑。ありがとう=)。
victorhooi

ありがとう。あなたの解決策は今日私を救いました。
ForeverLearner 2016

1
私の場合、それらは6回表示されていました。私は6つのoopクラスで同じタイプのロガーを宣言したため、それを疑っていました
answerSeeker

5
ここで私の経験を共有したいと思います。私が開発したFlaskアプリケーションの場合、ログメッセージが2回以上表示されていました。アプリケーションとモジュールがロードされたときにlogger使用された変数は、クラスの1つからインスタンス化されたものではなく、loggerPython3キャッシュに存在する変数であったため、ログファイルで増加していたと思います、そしてハンドラーは私が構成したAppSchedulerによって60秒ごとに追加されました。したがって、これif not logger.handlersはこのタイプの現象を回避するためのかなり賢い方法です。解決策をありがとう、同志:)!
ivanleoncz 2017年

2
Flaskアプリでこの問題が発生しています。このソリューションはメインフラスコアプリで生成されたログメッセージの問題を修正しましたが、私のアプリはライブラリモジュールの関数をcalsし、そのライブラリからのメッセージはまだ何度も記録されています。これを修正する方法がわかりません。
Cas

24

この問題が発生していて、ハンドラーを2回追加していない場合は、ここで abarnertの回答を参照してください

ドキュメントから:

注:ロガーとその1つ以上の祖先にハンドラーをアタッチすると、同じレコードが複数回出力される場合があります。一般に、ハンドラーを複数のロガーにアタッチする必要はありません。ロガー階層の最上位にある適切なロガーにハンドラーをアタッチするだけであれば、すべての子孫ロガーによってログに記録されたすべてのイベントが表示されます。設定はTrueのままです。一般的なシナリオは、ハンドラーをルートロガーにのみ接続し、残りの部分は伝播に任せることです。

したがって、「test」でカスタムハンドラーが必要で、そのメッセージがルートハンドラーにも送信されないようにする場合、答えは簡単です。その伝播フラグをオフにします。

logger.propagate = False


1
それが最良の答えです。それはポスターの目的(コーディングの論理エラー)に適合しませんでしたが、ほとんどの場合、これは事実です。
Artem

ブラボー。これが重複の実際の原因です(最も一般的な場合)。
Duhart氏、19年

これも私のコードの問題でした。どうもありがとう。
過酷な

史上最高の答え。ありがとうございました!
Foivos Ts

8

ハンドラは、外部から呼び出すたびに追加されます。ジョブが完了したら、ハンドラーを削除してみてください。

self.logger.removeHandler(ch)

1
logger.handlers.pop() はpython 2.7で使用しましたが、トリックを行います
radtek '25年


4

私の場合logger.propagate = False、二重印刷を防ぐように設定します。

以下のコードで削除するとlogger.propagate = False、二重印刷が表示されます。

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

これが私の問題です。ありがとう
q0987

すばらしい答えです。追加したのlogger.propagate = Falseは、Flaskのapp.loggerインスタンスにログを記録するときに、ウェイトレスによってホストされているFlaskアプリケーションでの二重ログ記録を防ぐソリューションでした。
bluebinary

1

コールlogging.debug()の呼び出しlogging.basicConfig()インストールなしルートハンドラが存在しない場合。これは、テストケースが起動する順序を制御できないテストフレームワークで発生していました。私の初期化コードは2番目のものをインストールしていました。デフォルトでは、私が望まないlogging.BASIC_FORMATを使用しています。


これが私に起こっていることだと思います。コンソールロガーの自動作成をどのように防止しますか?
ロバート

@Robertそれは、最初のロギング呼び出しの前に、必要なロガーで初期化されていることを確認することです。テストフレームワークはこれを曖昧にすることができますが、それを行う方法があるはずです。また、マルチプロセッシングの場合は、各プロセスで同じことを行う必要があります。
JimB

1

(誤って)ロガーに何かを出力してから設定すると、遅すぎるようです。たとえば、私のコードでは

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

私は(フォーマットオプションを無視して)のようなものを得るでしょう

look out
hello
hello

そして、すべてが2回stdoutに書き込まれました。これは、最初の呼び出しlogging.warningによって新しいハンドラーが自動的に作成され、次に明示的に別のハンドラーを追加したためと考えられます。偶発的な最初のlogging.warning呼び出しを削除すると、問題は解消しました。


0

コンソールログが2倍になったのに、ファイルログが2倍にならないという奇妙な状況になりました。何トンもの掘り起こしの後、私はそれを理解しました。

サードパーティのパッケージがロガーを登録できることに注意してください。これは注意が必要なことです(場合によっては防止できないことがあります)。多くの場合、サードパーティのコードは、既存のルートロガーハンドラーがあるかどうかを確認します。ない場合は、新しいコンソールハンドラーを登録します。

これに対する私の解決策は、コンソールロガーをルートレベルで登録することでした。

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.