Python从多个线程记录

nck*_*ner 24 python logging multithreading

我有一个log.py模块,用于至少两个其他模块(server.pydevice.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
}
Run Code Online (Sandbox Code Playgroud)

它有两个功能:

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 )
Run Code Online (Sandbox Code Playgroud)

和:

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)
Run Code Online (Sandbox Code Playgroud)

我打电话LoggingInitserver.py,初始化文件和控制台记录器.然后我WriteLog从所有地方打电话,所以多个线程正在访问fileLoggerconsoleLogger.

我的日志文件需要进一步保护吗?文档声明线程锁由处理程序处理.

aba*_*ert 46

好消息是,您不需要为线程安全做任何额外的事情,并且您不需要额外的任何东西或者几乎无关紧要的干净关闭.我稍后会详细介绍.

坏消息是,在您达到这一点之前,您的代码存在严重问题:fileLogger并且consoleLogger是相同的对象.从以下文档getLogger():

返回具有指定名称的记录器,或者,如果未指定名称,则返回记录器,该记录器是层次结构的根记录器.

因此,您将获得根记录器并将其存储为fileLogger,然后您将获得根记录器并将其存储为consoleLogger.因此,在LoggingInit初始化时fileLogger,然后使用不同的值在不同的名称下重新初始化同一个对象.

可以将多个处理程序添加到同一个记录器中,并且由于您实际上只对每个处理程序进行了初始化addHandler,因此您的代码将按预期工作,但只是偶然.而且只有一点.如果通过print_screen=True,您将在两个日志中获得每个邮件的两个副本,即使您通过,也将在控制台中获得副本print_screen=False.

实际上根本没有全局变量的理由; 重点getLogger()是你可以在每次需要时调用它并获得全局根记录器,因此你不需要将它存储在任何地方.


一个更小的问题是您没有转义插入HTML的文本.在某些时候,你将尝试记录字符串"a < b"并最终遇到麻烦.

那么严肃,序列<p>标签,是不是一个内<body>里的<html>是不是一个有效的HTML文档.但是大量的观众会自动处理,或者您可以在显示之前轻松地对日志进行后期处理.但是如果你真的希望这是正确的,你需要子类FileHandler并在你__init__给出一个空文件时添加一个标题并删除页脚(如果存在),然后close添加一个页脚.


回到你的实际问题:

您不需要任何额外的锁定.如果处理程序正确实现createLock,acquirerelease(并且它在具有线程的平台上调用),日志记录机制将自动确保在需要时获取锁定以确保以原子方式记录每条消息.

据我所知,文档没有直接说明StreamHandlerFileHandler实现这些方法,它强烈暗示它(你在问题中提到的文本说"日志记录模块旨在是线程安全的,无需任何特殊工作由客户完成"等等.您可以查看实现的源代码(例如,CPython 3.3),并看到它们都继承了正确实现的方法logging.Handler.


同样,如果处理正确实施flushclose,伐木机械将确保它的正常关机过程中正确完成.

在这里,文件并解释什么StreamHandler.flush(),FileHandler.flush()以及FileHandler.close().它们大多是您期望的,除非StreamHandler.close()是无操作,这意味着可能会丢失到控制台的最终日志消息.来自文档:

请注意,该close()方法是继承的Handler,因此没有输出,因此flush()有时可能需要显式调用.

如果这对您很重要,而您想修复它,则需要执行以下操作:

class ClosingStreamHandler(logging.StreamHandler):
    def close(self):
        self.flush()
        super().close()
Run Code Online (Sandbox Code Playgroud)

然后用ClosingStreamHandler()而不是StreamHandler().

FileHandler 没有这样的问题.


将日志发送到两个地方的常规方法是使用带有两个处理程序的根记录程序,每个处理程序都有自己的格式化程序.

此外,即使你确实需要两个记录器,你也不需要单独的console_logging_level_switchfile_logging_level_switch地图; 调用Logger.debug(msg)与调用完全相同Logger.log(DEBUG, msg).您仍然需要某种方法将自定义级别名称debug等映射到标准名称DEBUG等,但您可以只执行一次查找,而不是每个记录器执行一次(另外,如果您的名称只是标准名称)与不同的演员,你可以作弊).

这在" 多个处理程序和格式化程序"部分以及其他日志记录菜谱中都有很好的描述.

执行此操作的标准方法的唯一问题是您无法在逐个消息的基础上轻松关闭控制台日志记录.那是因为这不是正常的事情.通常,您只需按级别进行日志记录,并在文件日志中将日志级别设置得更高.

但是,如果您想要更多控制,可以使用过滤器.例如,给你FileHandler一个接受所有东西的过滤器,以及ConsoleHandler一个需要以某些东西开头console的过滤器,然后使用过滤器'console' if print_screen else ''.这WriteLog几乎减少到一个班轮.

您仍然需要额外的两行来删除换行符 - 但您甚至可以在过滤器中执行此操作,或者通过适配器执行此操作(如果需要).(再看看食谱.)然后WriteLog真的一个单行.


and*_*oke 5

Python日志记录是线程安全的:

因此,您在Python(库)代码中没有问题。

您从多个线程(WriteLog)调用的例程不会写入任何共享状态。因此,您的代码没有问题。

这样就可以了