Pythonロギングモジュール使用時のログ出力の重複


105

私はpythonロガーを使用しています。以下は私のコードです:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

私が抱えている問題は、logger.info呼び出しごとにログファイルに複数のエントリが表示されることです。どうすればこれを解決できますか?


私のために働く。Python 3.2およびWindows XP。
Zuljin、2011

2
複数のロガーインスタンスを作成しませんか?
ガンディ2011

はい。別のファイルでは、Javaプロジェクトで行ったように、新しいインスタンスを取得しています。問題が発生しているかどうかを教えてください。
user865438 2011

回答:


94

logging.getLogger()すでにシングルトンです。(ドキュメント

問題は、を呼び出すたびmyLogger()に、インスタンスに別のハンドラーが追加されるため、ログが重複することです。

おそらくこのようなものですか?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
代わりにloggers.update(dict((name、logger)))が必要だと思います。
アクロフォビア2012

なんでloggers.update(dict(name=logger))loggers[name] = logger簡単ではないですか?
Ryan J McCall

@RyanJMcCall当時は、私が使用したコーディング規約でした。しかし、現在のコードを確認すると、コードが壊れていることがわかります。loggers.update(dict(name=logger))と呼ばれる単一のキーで辞書を作成し、nameその同じキーを継続的に更新します。このコードはかなり壊れているので、これまでに誰もこれに言及しなかったのには驚いています:)必要な修正を行います。
Werner Smit

@acrophobiaはこの時代に先んじて逃げていたようです。ありがとう。
Werner Smit

グローバルloggers辞書は冗長ではありませんlogging.getLoggerか?余分なハンドラーの追加を本当に避けたいので、ハンドラーを直接チェックする以下の回答を好むようです
mway

60

Python 3.2以降では、ハンドラーが既に存在するかどうかを確認でき、存在する場合は、新しいハンドラーを追加する前にそれらをクリアします。これはデバッグ時に非常に便利で、コードにはロガーの初期化が含まれます

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

良い答え、Thx :))
Gavriel Cohen

2
ローカル/カスタムハンドラーがまだ追加されていない場合でも、ハンドラーがルートロガーに追加されているpytestでは、hasHandlers()がtrueを返すことに注意してください。この場合、len(logger.handlers)(Guillaumeの回答によると)は0を返すため、より良いオプションである可能性があります。
付与

これは私が探していた本当の解決策です。
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

私のためにトリックを作りました

Python 2.7を使用


1
これは、モジュールがリロードされた場合でも機能します(他の回答の場合とは
異なり

3
ヒントをありがとう、BTWはリストが空であるかどうかを確認します。「len」演算子を使用する必要はありません。my_listで直接使用できます。..
rkachach

26

私はすでにシングルトンloggerとして使用してチェックしましたが、それでも重複がありました:これはフォーマットされた出力で、その後にフォーマットされていませんif not len(logger.handlers)

私の場合の解決策logger.propagate = False

この回答ドキュメントへの謝辞。


1
ロギングが2倍になったのはRootLoggerとStreamHandlerであることがわかっていましたが、これを行うまで(StreamHandlerでフォーマッターを維持しながら)問題を解決できませんでした。
Xander YzWich

10

Logger.myLogger()複数回電話しています。ロガーインスタンスをどこかに返し、それを再利用ます。

ハンドラーが追加される前にログを記録すると、デフォルトStreamHandler(sys.stderr)が作成されることにも注意してください。


実際、私はjava。で使用しているロガーインスタンスにアクセスしようとしていますが、プロジェクト全体でインスタンスを1回だけ作成する必要があるかどうかはわかりません。
user865438 2011

1
@ user865483:一度だけ。すべての標準ライブラリロガーはシングルトンです。
マットジョイナー'24

5

これは@ rm957377の回答への追加ですが、なぜこれが起こっているのかを説明しています。AWSでラムダ関数を実行すると、複数の呼び出しの間存続するラッピングインスタンス内から関数が呼び出されます。つまりaddHandler()、関数のコード内で呼び出した場合、関数が実行されるたびに、重複するハンドラーがロギングシングルトンに追加され続けます。 ロギングシングルトンは、ラムダ関数を複数回呼び出しても保持されます。

これを解決するには、次の方法でハンドラーをクリアしてから設定します。

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

どういうわけか私の場合、ロガーハンドラーは、.info()私が理解していない呼び出し時のイベントで追加されます。
エフゲニー

4

ロガーはシングルトンとして機能するはずです。複数回作成しないでください。これがどのように見えるかの例です:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

次に、別のファイルで別のインスタンスを取得する場合。ファイル1でs = Logger()m = s.myLogger()であり、ファイル2でs = Logger()であると仮定します。m2= s.myLogger()
user865438 '24

それでも、同じログのコピーを何度か取得しています。内部スレッドLogが複数回印刷するかどうか、ここで疑問があります。これで私を助けてください。
user865438 2011

1
@ user865438、実装をシングルトンにすることを心配する必要はありません(それはすでにです)。サブモジュールでのログインについては、公式のLogging Cookbook リンクに従ってください。基本的に、ロガーに名前を付けるときは、名前付けの階層に従う必要があります。それにより残りの処理が行われます。
ナラヤン2015

4

ロガーの実装はすでにシングルトンです。

logging.getLogger( 'someLogger')を複数回呼び出すと、同じロガーオブジェクトへの参照が返されます。これは、同じモジュール内だけでなく、同じPythonインタープリタープロセス内にある限り、モジュール全体にも当てはまります。同じオブジェクトへの参照についても同様です。さらに、アプリケーションコードは、1つのモジュールで親ロガーを定義および構成し、別のモジュールで子ロガーを作成できます(ただし、構成はできません)。子へのすべてのロガー呼び出しは、親に渡されます。ここにメインモジュールがあります

ソース- 複数のモジュールでのロギングの使用

したがって、これを利用する方法は-

メインモジュールに'main_logger'というロガーを作成して構成したとしましょう(ロガーを構成するだけで、何も返されません)。

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

今度はサブモジュールで、名前の階層'main_logger.sub_module_logger'に従って子ロガーを作成する場合、サブモジュールでそれを構成する必要はありません。名前付け階層に従ってロガーを作成するだけで十分です。

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

また、重複したハンドラーも追加されません。

もう少し詳細な回答については、この質問を参照してください。


1
getLoggerが機能するように思えた後のハンドラーの再定義: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

importlib.reload受け入れられた回答で説明されているのと同じ理由で)を介してモジュールをリロードすると、ダブル(またはリロードの数に基づいてトリプルまたは..-)ロガー出力も発生する可能性があります。私の出力がdupli(triple)catedである理由を理解するのにしばらく時間がかかったので、将来の参考のためにこの回答を追加します。


1

簡単な回避策の1つは

logger.handlers[:] = [handler]

このようにして、基になるリスト「ハンドラー」に新しいハンドラーを追加することを避けます。


1

これが発生するほとんどの場合の結論は、モジュールごとに一度だけlogger.getLogger()を呼び出すだけです。私のように複数のクラスがある場合は、次のように呼び出すことができます。

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

両方とも、独自の完全なパッケージ名とメソッドが記録されます。


0

特定のロガーのすべてのハンドラーのリストを取得できるため、次のようなことができます

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

上記の例では、指定されたファイルのハンドラーがロガーにすでにフックされているかどうかを確認していますが、すべてのハンドラーのリストにアクセスできるため、別のハンドラーを追加するかどうかを判断することができます。


0

今日、この問題がありました。私の関数は@staticmethodだったので、上記の提案はrandom()で解決されました。

次のように見えます:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

この回答を長期的に使用する場合の価値を高めるために説明を追加してください。
アミナヌライニ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.