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

时间:2013-10-27 11:07:42

标签: python logging gevent

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

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

7 个答案:

答案 0 :(得分:87)

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

在要更改日志级别的日志记录对象上调用.setLevel()。通常你会在root上做到这一点:

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

答案 1 :(得分:10)

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

import logging

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

答案 2 :(得分:6)

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

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代码来实现您自己的侦听器,该侦听器以相同的方式工作,但是根据您的特定需求进行定制。

答案 3 :(得分:4)

扩展sfinken's answer和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')

答案 4 :(得分:2)

这可能就是你要找的东西:

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

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

答案 5 :(得分: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一起使用,它可能会有所帮助。

答案 6 :(得分: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提出的解决方案。