Python 无需重新启动应用程序即可动态更改日志级别

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19617355/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 14:11:48  来源:igfitidea点击:

Dynamically changing log level without restarting the application

pythonlogginggevent

提问by opensourcegeek

Is it possible to change the log level using fileConfig in python without restarting the application. If it cannot be achieved through fileConfig is there some other way to get the same result?

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

Update: This was for an application running on a server, I wanted sys admins to be able to change a config file that would be picked during run time by application and change the log level dynamically. I was working with gevent at that time hence I've added my code as one of the answers which uses inotify to pick changes to config file.

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

采纳答案by Martijn Pieters

fileConfigis a mechanism to configure the log level for you based on a file; you can dynamically change it at any time in your program.

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

Call .setLevel()on the logging object for which you want to change the log level. Usually you'd do that on the root:

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

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

回答by R. Max

This might be what you are looking for:

这可能是您正在寻找的:

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

Note that getLogger()called without any arguments returns the root logger.

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

回答by Vinay Sajip

It is certainly possible to use fileConfig()to change logging configuration on the fly, though for simple changes a programmatic approach as suggested in Martijn Pieters' answer might be appropriate. Logging even provides a socket server to listen for config changes using the listen()/ stopListening()APIs, as documented here. To get logging to listen on a particular port, you use

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

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

and to stop listening, call

并停止听,打电话

logging.config.stopListening()

To send data to the server, you can use e.g.

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

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()

Update:Due to backwards-compatibility constraints, the internal implementation of the fileConfig()call means that you can't specify disable_existing_loggers=Falsein the call, which makes this feature less useful in certain scenarios. You can use the same API to send a JSON file using the dictConfig schema, which will allow better control over the reconfiguration. This requires Python 2.7/3.2 or above (where dictConfig()was added). Or, you can use the stdlib code to implement your own listener which works in the same way but which is tailored to your specific needs.

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

回答by Mihai

Depending on your app, you first need to find a way for reloading that file or resetting the log level based on your own config file during execution.

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

Easiest way would be to use a timer. Either use threading to do that, or make your async framework to do that (if you use any; they usually implement it).

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

Using threading.Timer:

使用 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)

Output:

输出:

Test
Test
Test
Test
Test
Something else
Test
Test

Update: Please check the solution proposed by Martijn Pieters.

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

回答by opensourcegeek

I finally settled with using inotify and gevent to check for the file write operation, and once I know the file has been changed then I go and set the level for each logger I have based on the config.

我最终决定使用 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)

The above code gets used like below,

上面的代码使用如下,

    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)

Once I have the new log file config I can wire in the right logging level for each logger from config. I just wanted to share the answer and it might help someone if they are trying to use it with gevent.

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

回答by sfinkens

In addition to the accepted answer: Depending on how you initialized the logger, you might also have to update the logger's handlers:

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

import logging

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

回答by DrOffler

Expanding on sfinken's answer, and Starman's subsequent comment, you can also check the type of the handler to target a specific outputter - for instance:

扩展sfinken 的 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')