python 线程行为异常的记录器

python logger with threading misbehaving

我正在使用 python 2.7 将线程调用中包含的一些消息登录到文件中。但是,我没有收到消息 1 次,而是收到了 3 次日志。好吧,我将范围设置为 5,但是,消息出现了 15 次。

下面是代码:

import sys
import logging
import threading  
import logging.handlers
import time 
import os 

ETT="TVAR 1"
objectname="TVAR 2"
URLFORLOG="TVAR 3"

def setup_custom_logger(name):
    fileLogName='visualreclogfile.log'
    #formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')

    handlerCH = logging.StreamHandler()
    handlerCH.setFormatter(formatter)

    handlerFILE = logging.handlers.RotatingFileHandler(fileLogName, maxBytes=(1048576*5), backupCount=7)
    handlerFILE.setFormatter(formatter)


    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)

    logger.addHandler(handlerCH)
    logger.addHandler(handlerFILE)

    return logger

def LoggingFileForELK(MessageToBeLogged):
    logger = setup_custom_logger('root')
    logger.info(MessageToBeLogged)


def mainFunction():
    Messages=("*** CONTENT LOGGING *** OBJECT UUID : %s WITH NAME KEY : %s HAS URL : %s ") %(ETT,objectname,URLFORLOG)
    MessageToBeLogged=str(Messages)
    LoggingFileForELK(MessageToBeLogged)





threads = []
for i in range(5):
    t = threading.Thread(target=mainFunction)
    threads.append(t)
    t.start()
    time.sleep(0.5)
for  t in threads:
    t.join()

下面是我的结果:

2017-04-22 12:36:59,010 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:36:59,512 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:36:59,512 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,018 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,018 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,018 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,520 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,520 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,520 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:00,520 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:01,022 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:01,022 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:01,022 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:01,022 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3
2017-04-22 12:37:01,022 - INFO - *** CONTENT LOGGING *** OBJECT UUID : TVAR 1 WITH NAME KEY : TVAR 2 HAS URL : TVAR 3

我查看了这个 Whosebug thread 但这里没有答案。

我看不到错误的零件代码...

这个复制的东西实际上与 threading 无关,而是与生成线程并在每个线程上运行 mainFunctionfor loop 有关。在每次迭代中,当您认为您正在创建一个新的记录器对象时,您实际上只是在引用相同的记录器对象,因为您提供了相同的记录器名称(在本例中为 'root')。但是您还在每次迭代中向此记录器对象添加更多处理程序。因此在迭代 i = 0 时,您有 1 个记录器对象、1 个文件处理程序和 1 个流处理程序。但是当你点击迭代 i = 1 时,你现在仍然有 1 个记录器对象,但是有 2 个文件处理程序(指向同一个文件)和 2 个流处理程序。这意味着在迭代 i = 1 结束时,您已经将 3 行添加到您的文件中,并将 3 行打印到您的标准流中。按照这个递增逻辑,到第 5 次迭代结束时,您将得到 1 个记录器对象、5 个文件处理程序和 5 个流处理程序。从本质上讲,这就是文件和标准流中重复行背后的原因。

一个修复方法是重新定义 setup_custom_logger 函数,以便仅在记录器对象尚不存在时生成新的处理程序。基本上,您必须拥有某种形式的容器(在本例中为字典)来跟踪您创建的记录器及其处理程序。如果使用已经存在的记录器名称调用 setup_custom_logger,则该函数将只是 return 现有记录器 ;但否则它会生成一个新的记录器及其处理程序。

我已经调整了你的脚本以添加修复:

import sys
import logging
import threading
import logging.handlers
import time
import os

ETT="TVAR 1"
objectname="TVAR 2"
URLFORLOG="TVAR 3"

# container to keep track of loggers
loggers = {}

def setup_custom_logger(name):
    global loggers
    # return existing logger if it exists
    if name in loggers:
        return loggers.get(name)
    else:   # else, create a new logger with handlers
        fileLogName='visualreclogfile.log'
        #formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
        formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')

        handlerCH = logging.StreamHandler()
        handlerCH.setFormatter(formatter)

        handlerFILE = logging.handlers.RotatingFileHandler(fileLogName, maxBytes=(1048576*5), backupCount=7)
        handlerFILE.setFormatter(formatter)


        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)

        logger.addHandler(handlerCH)
        logger.addHandler(handlerFILE)
        loggers[name] = logger
        return logger

def LoggingFileForELK(MessageToBeLogged):
    logger = setup_custom_logger('root')
    logger.info(MessageToBeLogged)


def mainFunction():
    Messages=("*** CONTENT LOGGING *** OBJECT UUID : %s WITH NAME KEY : %s HAS URL : %s ") %(ETT,objectname,URLFORLOG)
    MessageToBeLogged=str(Messages)
    LoggingFileForELK(MessageToBeLogged)




threads = []
for i in range(5):
    t = threading.Thread(target=mainFunction)
    t.start()
    threads.append(t)
    time.sleep(0.1)
for  t in threads:
    t.join()

编辑:

这是记录器及其句柄的正常行为。即使没有线程,只要您引用相同的记录器并向其添加处理程序,这将以相同的方式运行。这里的问题是 for loop,而不是 threading 过程。如果您从脚本中删除了 threading 部分,您仍然会得到重复的行。例如,以下将 return 与原始线程版本相同的行数:

import sys
import logging
import threading
import logging.handlers
import time
import os

ETT="TVAR 1"
objectname="TVAR 2"
URLFORLOG="TVAR 3"

def setup_custom_logger(name):
    fileLogName='visualreclogfile.log'
    #formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')

    handlerCH = logging.StreamHandler()
    handlerCH.setFormatter(formatter)

    handlerFILE = logging.handlers.RotatingFileHandler(fileLogName, maxBytes=(1048576*5), backupCount=7)
    handlerFILE.setFormatter(formatter)


    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)

    logger.addHandler(handlerCH)
    logger.addHandler(handlerFILE)

    return logger

def LoggingFileForELK(MessageToBeLogged):
    logger = setup_custom_logger('root')
    logger.info(MessageToBeLogged)


def mainFunction():
    Messages=("*** CONTENT LOGGING *** OBJECT UUID : %s WITH NAME KEY : %s HAS URL : %s ") %(ETT,objectname,URLFORLOG)
    MessageToBeLogged=str(Messages)
    LoggingFileForELK(MessageToBeLogged)




for i in range(5):
    mainFunction()

这表明 不是 导致日志记录处理行为异常的线程,而是添加过程每次迭代时记录器对象的新处理程序。底线是:logging 非常了解线程并且可以与任何线程应用程序一起正常工作,只要你没有不必要地复制处理程序。我不精通任何比 logging 模块在这方面做得更好的东西,但那里可能有更好的东西。我只是不知道。

希望对您有所帮助。