33

我有一个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
}

它有两个功能:

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 )

和:

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)

我调用LoggingInitfrom server.py,它初始化文件和控制台记录器。WriteLog然后我从各地打电话,所以多个线程正在访问fileLoggerconsoleLogger.

我的日志文件需要进一步保护吗?该文档指出线程锁由处理程序处理。

4

2 回答 2

63

好消息是你不需要为线程安全做任何额外的事情,你要么不需要任何额外的东西,要么不需要一些几乎微不足道的东西来干净关闭。稍后我会详细介绍。

坏消息是你的代码甚至在你到达那一点之前就有一个严重的问题:fileLogger并且consoleLogger是同一个对象。从文档中getLogger()

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

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

可以将多个处理程序添加到同一个记录器——并且,由于您实际为每个记录器执行的唯一初始化是addHandler,因此您的代码将按预期工作,但只是偶然。而且只是一种。如果您通过,您将在两个日志中获得每条消息的两个副本,即使您通过print_screen=True,您也会在控制台中获得副本print_screen=False

实际上根本没有理由使用全局变量。关键getLogger()是您可以在每次需要时调用它并获取全局根记录器,因此您无需将其存储在任何地方。


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

不太严重的是,不在内部的标签序列不是<p>有效的 HTML 文档。但是很多观众会自动处理这些,或者您可以在显示它们之前对日志进行简单的后处理。但是,如果您真的希望这是正确的,则需要子类化并在给定空文件时添加页眉,如果存在则删除页脚,然后添加页脚。<body><html>FileHandler__init__close


回到你的实际问题:

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

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


同样,如果处理程序正确实现flushand close,则日志记录机制将确保它在正常关闭期间正确完成。

在这里,文档确实解释了StreamHandler.flush()FileHandler.flush()FileHandler.close(). 它们大多是您所期望的,除了它StreamHandler.close()是无操作的,这意味着控制台的最终日志消息可能会丢失。从文档:

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

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

class ClosingStreamHandler(logging.StreamHandler):
    def close(self):
        self.flush()
        super().close()

然后使用ClosingStreamHandler()代替StreamHandler().

FileHandler没有这样的问题。


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

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

这在“多个处理程序和格式化程序”部分以及日志记录手册的其余部分中都有很好的描述。

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

但是,如果你想要更多的控制,你可以使用过滤器。例如,给您FileHandler一个接受所有内容的过滤器,以及ConsoleHandler一个需要以 开头的内容console的过滤器,然后使用 filter 'console' if print_screen else ''。这减少WriteLog到几乎单线。

您仍然需要额外的两行来删除换行符——但您甚至可以过滤器中执行此操作,或者如果您愿意,可以通过适配器执行此操作。(再次,请参阅食谱。)然后WriteLog真的单线。

于 2013-06-05T00:36:25.353 回答
7

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

所以你在 Python(库)代码中没有问题。

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

所以你没事。

于 2013-06-05T00:36:26.810 回答