问题:使用Python日志记录模块时重复的日志输出

我正在使用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呼叫的日志文件中都有多个条目。我该如何解决?

I am using python logger. The following is my code:

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

The problem I have is that I get multiple entries in the log file for each logger.info call. How can I solve this?


回答 0

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

The logging.getLogger() is already a singleton. (Documentation)

The problem is that every time you call myLogger(), it’s adding another handler to the instance, which causes the duplicate logs.

Perhaps something like this?

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

回答 1

从Python 3.2开始,您可以检查是否已经存在处理程序,如果存在,请在添加新处理程序之前清除它们。这在调试时非常方便,并且代码包括记录器的初始化

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

logger.addHandler(handler)

Since Python 3.2 you can just check if handlers are already present and if so, clear them before adding new handlers. This is pretty convenient when debugging and the code includes your logger initialization

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

logger.addHandler(handler)

回答 2

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

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

made the trick for me

using python 2.7


回答 3

我已经将它logger用作Singleton并进行了检查if not len(logger.handlers),但仍然有重复项:它是格式化的输出,然后是未格式化的输出。

我的解决方案logger.propagate = False

归功于这个答案文档

I already used the logger as a Singleton and checked if not len(logger.handlers), but still got duplicates: It was the formatted output, followed by the unformatted.

Solution in my case: logger.propagate = False

Credits to this answer and the docs.


回答 4

您拨打Logger.myLogger()了不止一次。存储返回某处记录器实例和重用

还建议如果在添加任何处理程序之前登录,StreamHandler(sys.stderr)将创建默认值。

You are calling Logger.myLogger() more than once. Store the logger instance it returns somewhere and reuse that.

Also be advised that if you log before any handler is added, a default StreamHandler(sys.stderr) will be created.


回答 5

这是@ rm957377答案的补充,但解释了为什么发生这种情况。当您在AWS中运行lambda函数时,它们从包装实例中调用您的函数,该包装实例对于多个调用仍然有效。这意味着,如果您addHandler()在函数的代码内调用,它将在每次函数运行时继续向日志记录单例添加重复的处理程序。 日志单例通过lambda函数的多次调用而持续存在。

为了解决这个问题,您可以在设置处理器之前先清除它们,方法是:

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

This is an addition to @rm957377’s answer but with an explanation of why this is happening. When you run a lambda function in AWS, they call your function from within a wrapping instance that stays alive for multiple calls. Meaning, if you call addHandler() within the code of your function, it will continue to add duplicate handlers to the logging singleton every time the function runs. The logging singleton persists through multiple calls of you lambda function.

To solve this you can clear your handlers before you set them via:

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

回答 6

您的记录器应作为单例工作。您不应该多次创建它。这是看起来的示例:

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")

Your logger should work as singleton. You shouldn’t create it more than once. Here is example how it might look:

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")

回答 7

logger的实现已经是单例。

多次调用logging.getLogger(’someLogger’)会返回对同一记录器对象的引用。只要在同一Python解释器进程中,不仅在同一模块内,而且在各个模块之间都是如此。对于相同对象的引用是正确的。此外,应用程序代码可以在一个模块中定义和配置父记录器,并在单独的模块中创建(但不能配置)子记录器,并且所有对子记录器的调用都将传递给父记录器。这是一个主要模块

来源- 在多个模块中使用日志记录

因此,您应该利用它的方式是-

假设我们已经在主模块中创建并配置了一个名为“ 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
...

而且它也不会添加重复的处理程序。

对一个小更详细的回答问题。

The implementation of logger is already a singleton.

Multiple calls to logging.getLogger(‘someLogger’) return a reference to the same logger object. This is true not only within the same module, but also across modules as long as it is in the same Python interpreter process. It is true for references to the same object; additionally, application code can define and configure a parent logger in one module and create (but not configure) a child logger in a separate module, and all logger calls to the child will pass up to the parent. Here is a main module

Source- Using logging in multiple modules

So the way you should utilize this is –

Let’s suppose we have created and configured a logger called ‘main_logger’ in the main module (which simply configures the logger, doesn’t return anything).

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

Now in a sub-module, if we create a child logger following the naming hierarchy ‘main_logger.sub_module_logger’, we don’t need to configure it in the sub-module. Just creation of the logger following the naming hierarchy is sufficient.

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

And it won’t add duplicate handler as well.

See this question for a little more verbose answer.


回答 8

当您通过方式重新加载模块时,也会发生两倍(或三倍或..-基于重新加载的次数)记录器输出importlib.reload(出于与接受的答案中所述相同的原因)。我添加此答案只是为了将来参考,因为花了我一段时间才弄清楚为什么我的输出是dupli(triple)cated。

Double (or triple or ..- based on number of reloads) logger output may also happen when you reload your module via importlib.reload (for the same reason as explained in accepted answer). I am adding this answer just for a future reference as it took me a while to figure out why my output is dupli(triple)cated.


回答 9

一种简单的解决方法是

logger.handlers[:] = [handler]

这样,您可以避免将新的处理程序附加到基础列表“处理程序”。

One simple workaround is like

logger.handlers[:] = [handler]

This way you avoid appending new handler to the underlying list “handlers”.


回答 10

在大多数情况下,这种情况的底线是,每个模块只需要调用一次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')

然后两者都将在记录的地方拥有自己的完整程序包名称和方法。

Bottom line for most cases when this happens, one only needs to call logger.getLogger() only once per module. If you have multiple classes like I did, I could call it like so:

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')

Both then will have their own full package name and method where logged.


回答 11

您可以获取特定记录器的所有处理程序的列表,因此您可以执行以下操作

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)

在上面的示例中,我们检查指定文件的处理程序是否已经连接到记录器,但是可以访问所有处理程序的列表,使您能够决定应添加哪个处理程序或不添加其他处理程序。

You are able to get list of all handlers for the particular logger, so you can do something like this

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)

In the example above we check if the handler for a file specified is already hooked to the logger, but having access to the list of all handlers gives you an ability to decide on which criteria you should add another handler or not.


回答 12

今天有这个问题。由于我的函数是@staticmethod,因此上述建议已通过random()解决。

看起来像:

import random

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

Had this problem today. Since my functions were @staticmethod the above suggestions were resolved with random().

Looking something like:

import random

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

回答 13

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
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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。