是否可以在 python 中使用 fileConfig 更改日志级别而无需重新启动应用程序。如果无法通过 fileConfig 实现,是否有其他方法可以获得相同的结果?
更新:这是针对在服务器上运行的应用程序,我希望系统管理员能够更改应用程序在运行时选择的配置文件并动态更改日志级别。当时我正在使用 gevent,因此我将我的代码添加为使用 inotify 选择对配置文件的更改的答案之一。
fileConfig
是一种基于文件为您配置日志级别的机制;您可以在程序中随时动态更改它。
调用.setLevel()
要更改其日志级别的日志记录对象。通常你会在根目录上这样做:
logging.getLogger().setLevel(logging.DEBUG)
除了接受的答案:根据您初始化记录器的方式,您可能还必须更新记录器的处理程序:
import logging
level = logging.DEBUG
logger = logging.getLogger()
logger.setLevel(level)
for handler in logger.handlers:
handler.setLevel(level)
扩展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')
当然可以使用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 代码实现您自己的侦听器,该侦听器以相同的方式工作,但根据您的特定需求量身定制。
这可能是您正在寻找的:
import logging
logging.getLogger().setLevel(logging.INFO)
请注意,getLogger()
不带任何参数的调用会返回根记录器。
我最终决定使用 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)
一旦我有了新的日志文件配置,我就可以从配置中为每个记录器连接正确的日志记录级别。我只是想分享答案,如果他们试图将它与 gevent 一起使用,它可能会对某人有所帮助。
根据您的应用程序,您首先需要找到一种方法来重新加载该文件或在执行期间根据您自己的配置文件重置日志级别。
最简单的方法是使用计时器。要么使用线程来做到这一点,要么让你的异步框架来做到这一点(如果你使用的话;他们通常会实现它)。
使用 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 提出的解决方案。