programing

응용 프로그램을 다시 시작하지 않고 동적으로 로그 수준 변경

itsource 2021. 1. 18. 07:58
반응형

응용 프로그램을 다시 시작하지 않고 동적으로 로그 수준 변경


응용 프로그램을 다시 시작하지 않고 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)

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 코드를 사용하여 동일한 방식으로 작동하지만 특정 요구 사항에 맞는 자체 리스너를 구현할 수 있습니다.


마침내 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)

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.


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.


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:

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

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:

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.

ReferenceURL : https://stackoverflow.com/questions/19617355/dynamically-changing-log-level-without-restarting-the-application

반응형