动态更改日志级别,而无需重新启动应用程序


70

是否可以在不重新启动应用程序的情况下使用python中的fileConfig更改日志级别。如果无法通过fileConfig实现,是否还有其他方法可以获得相同的结果?

更新:这是针对服务器上运行的应用程序,我希望系统管理员能够更改应用程序在运行时选择的配置文件,并动态更改日志级别。当时我正在使用gevent,因此我将代码添加为使用inotify来选择对配置文件进行更改的答案之一。


2
每个人都使用线程和计时器来重置日志记录级别。这就是发明信号的目的。您可以使用“ SIGHUP”(* nix上的标准)向进程发出信号,以重新加载配置。在python中,您可以安装一个信号处理程序来捕获该事件处理程序(事件驱动器),从而重新加载或重置配置。
Ben DeMott 2014年

2
我同意使用信号。建议为此目的使用SIGUSR1或SIGUSR2代替SIGHUP。当进程的控制终端断开连接时,发送后者。
Mike Ellis

1
SIGUSR1SIGUSR2仅适用于类Unix系统。您可以使用logging.config.listen()侦听新配置(docs.python.org/3/library/...
yoonghm

Answers:


116

fileConfig是一种基于文件为您配置日志级别的机制;您可以随时在程序中动态更改它。

调用.setLevel()要更改其日志级别的日志记录对象。通常,您将在根目录上执行此操作:

logging.getLogger().setLevel(logging.DEBUG)

1
如果只想更改特定处理程序的setLevel,也请参阅@sfinkens的答案(以及我的评论)。
Starman's

25

除了可接受的答案:根据您初始化记录器的方式,您可能还必须更新记录器的处理程序:

import logging

level = logging.DEBUG
logger = logging.getLogger()
logger.setLevel(level)
for handler in logger.handlers:
    handler.setLevel(level)

1
有用的:如果使用dictConfig指定记录器,处理程序等,并且只想在一个特定的处理程序上设置setLevel,则可以在@sfinkens所示的循环中使用.get_name()函数,并进行确保仅更改所需级别的级别。
Starman '18

处理程序为什么需要一个logLevel?此外,为什么可以独立于拥有它们的记录器来设置它们的级别?这非常令人困惑,而且设计不好。
CodeKid

1
@CodeKid如果您想以DEBUG级别登录到文件,但以不同级别登录到控制台该怎么办?您是否会附加两个不同级别的处理程序?
supermitch

8

扩展sfinken的答案和Starman的后续评论,您还可以检查以特定输出为目标的处理程序的类型-例如:

import logging
logger = logging.getLogger()
for handler in logger.handlers:
    if isinstance(handler, type(logging.StreamHandler())):
        handler.setLevel(logging.DEBUG)
        logger.debug('Debug logging enabled')

如果我在没有-o标志的情况下运行,正是我需要自动启用调试日志记录
takanuva15

7

当然,可以随时随地fileConfig()更改日志记录配置,尽管对于简单的更改,Martijn Pieters的答案中建议的编程方法可能是合适的。日志记录甚至提供了一个套接字服务器,以使用listen()/ stopListening()API监听配置更改,如此处所述。要获取日志以侦听特定端口,请使用

t = logging.config.listen(PORT_NUMBER)
t.start()

并停止收听,请致电

logging.config.stopListening()

要将数据发送到服务器,您可以使用例如

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', PORT_NUMBER))
with open(CONFIG_FILE) as f:
    data_to_send = f.read()
s.send(struct.pack('>L', len(data_to_send)))
s.send(data_to_send)
s.close()

更新:由于向后兼容性的限制,该fileConfig()调用的内部实现意味着您无法disable_existing_loggers=False在调用中指定,这使得此功能在某些情况下不那么有用。您可以使用相同的API通过dictConfig模式发送JSON文件,从而可以更好地控制重新配置。这需要Python 2.7 / 3.2或更高版本(dictConfig()已添加)。或者,您可以使用stdlib代码实现自己的侦听器,该侦听器的工作方式相同,但可以根据您的特定需求进行定制。


1
哇,我今天早些时候看到了这个答案,但没有注意到最新消息。我在那个问题上苦苦挣扎。已提交一个错误以添加该选项。 bugs.python.org/issue26533 现在,我以一种卑鄙的方式解决了这个问题:
Gdogg,2016年

python def my_fileConfig(fname, defaults=None, disable_existing_loggers=False): """ This does _NOTHING_ but call fileConfig with disable_existing_loggers set to False instead of true. It is intended to monkey patcn it out, so that the config listener can be used without disabling all non-root loggers. """ orig_fileConfig(fname, defaults, disable_existing_loggers) orig_fileConfig = logging.config.fileConfig # HACK: Patch out fileConfig for my version. logging.config.fileConfig = my_fileConfig logging.config.fileConfig(config_path)
Gdogg '16

3

这可能是您要寻找的:

import logging
logging.getLogger().setLevel(logging.INFO)

请注意,getLogger()不带任何参数的调用将返回根记录器。


2

最后,我决定使用inotify和gevent来检查文件写入操作,一旦知道文件已更改,就可以根据配置为每个记录器设置级别。

import gevent
import gevent_inotifyx as inotify
from gevent.queue import Queue

class FileChangeEventProducer(gevent.Greenlet):
    def __init__(self, fd, queue):
        gevent.Greenlet.__init__(self)
        self.fd = fd
        self.queue = queue

    def _run(self):
        while True:
            events = inotify.get_events(self.fd)
            for event in events:
                self.queue.put(event)
                gevent.sleep(0)


class FileChangeEventConsumer(gevent.Greenlet):
    def __init__(self, queue, callBack):
        gevent.Greenlet.__init__(self)
        self.queue = queue
        self.callback = callBack

    def _run(self):
        while True:
            _ = self.queue.get()
            self.callback()
            gevent.sleep(0)


class GeventManagedFileChangeNotifier:
    def __init__(self, fileLocation, callBack):
        self.fileLocation = fileLocation
        self.callBack = callBack
        self.queue = Queue()
        self.fd = inotify.init()
        self.wd = inotify.add_watch(self.fd, self.fileLocation, inotify.IN_CLOSE_WRITE)


    def start(self):
        producer = FileChangeEventProducer(self.fd, self.queue)
        producer.start()
        consumer = FileChangeEventConsumer(self.queue, self.callBack)
        consumer.start()
        return (producer, consumer)

上面的代码如下使用:

    def _setUpLoggingConfigFileChangeNotifier(self):
        loggingFileNameWithFullPath = self._getFullPathForLoggingConfig()
        self.gFsNotifier = GeventManagedFileChangeNotifier(loggingFileNameWithFullPath, self._onLogConfigChanged)
        self.fsEventProducer, self.fsEventConsumer = self.gFsNotifier.start()


    def _onLogConfigChanged(self):
        self.rootLogger.info('Log file config has changed - examining the changes')
        newLoggingConfig = Config(self.resourcesDirectory, [self.loggingConfigFileName]).config.get('LOG')
        self.logHandler.onLoggingConfigChanged(newLoggingConfig)

一旦有了新的日志文件配置,就可以从config中为每个记录器连接正确的日志记录级别。我只是想分享答案,如果有人尝试将其与gevent一起使用,它可能会有所帮助。


0

根据您的应用程序,您首先需要找到一种在执行过程中根据自己的配置文件重新加载该文件或重置日志级别的方法。

最简单的方法是使用计时器。要么使用线程来做到这一点,要么使您的异步框架来做到这一点(如果使用的话,他们通常会实现它)。

使用threading.Timer:

import threading
import time


def reset_level():
    # you can reload your own config file or use logging.config.fileConfig here
    print 'Something else'
    pass


t = threading.Timer(10, reset_level)
t.start()

while True:
    # your app code
    print 'Test'
    time.sleep(2)

输出:

Test
Test
Test
Test
Test
Something else
Test
Test

更新:请检查Martijn Pieters提出的解决方案。


我认为这不能完全回答问题。
杰斯·布朗宁

我已经使用了上面的解决方案,我面临的问题(在带有Python 2.4.3的rhel5和python 2.4.3上并使用ConcurrentLogHandler)是每次重新加载配置文件时,都会添加一个文件句柄,运行一段时间后会抛出一个“打开的文件太多”异常。因此,我同意特拉维斯·贝尔(Travis Bear)的观点,认为马丁·彼得斯(Martijn Pieters)的答案应该被接受。
Fabian76
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.