web-dev-qa-db-fra.com

Modification dynamique du niveau de journalisation en python sans redémarrer l'application

Est-il possible de changer le niveau de log en utilisant fileConfig en python sans redémarrer l'application. Si cela ne peut pas être réalisé via fileConfig, existe-t-il un autre moyen d'obtenir le même résultat? 

Mise à jour: il s'agissait d'une application s'exécutant sur un serveur. Je souhaitais que les administrateurs système puissent modifier un fichier de configuration qui serait sélectionné au moment de l'exécution par l'application et modifier le niveau de journalisation de manière dynamique. À l'époque, je travaillais avec gevent, c'est pourquoi j'ai ajouté mon code parmi les réponses qui utilisent inotify pour choisir les modifications apportées au fichier de configuration.

45
opensourcegeek

fileConfig est un mécanisme permettant de configurer le niveau de journal pour vous en fonction d'un fichier; vous pouvez le changer dynamiquement à tout moment dans votre programme.

Appelez .setLevel() sur l'objet de journalisation pour lequel vous souhaitez modifier le niveau de journalisation. Habituellement, vous le feriez à la racine:

logging.getLogger().setLevel(logging.DEBUG)
78
Martijn Pieters

En plus de la réponse acceptée: Selon la manière dont vous avez initialisé le consignateur, vous devrez peut-être également mettre à jour les gestionnaires de ce dernier:

import logging

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

Il est certainement possible d'utiliser fileConfig() pour modifier la configuration de la journalisation à la volée, bien que, pour de simples modifications, une approche programmatique telle que suggérée dans la réponse de Martijn Pieters puisse convenir. La journalisation fournit même un serveur de socket pour écouter les modifications de configuration à l'aide des API listen()stopListening(), comme documenté ici . Pour que la journalisation soit à l'écoute sur un port particulier, vous utilisez

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

et pour arrêter d'écouter, appelez

logging.config.stopListening()

Pour envoyer des données au serveur, vous pouvez utiliser par exemple.

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: En raison de contraintes de compatibilité ascendante, l'implémentation interne de l'appel fileConfig() signifie que vous ne pouvez pas spécifier disable_existing_loggers=False dans l'appel, ce qui rend cette fonctionnalité moins utile dans certains scénarios. Vous pouvez utiliser la même API pour envoyer un fichier JSON à l'aide du schéma dictConfig, ce qui permettra un meilleur contrôle de la reconfiguration. Cela nécessite Python 2.7/3.2 ou supérieur (où dictConfig() a été ajouté). Ou bien, vous pouvez utiliser le code stdlib pour implémenter votre propre écouteur, qui fonctionne de la même manière mais qui est adapté à vos besoins spécifiques.

6
Vinay Sajip

C'est peut-être ce que vous recherchez:

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

Notez que getLogger() appelé sans aucun argument renvoie le consignateur racine.

2
Rolando Max

J'ai finalement décidé d'utiliser inotify et gevent pour vérifier l'opération d'écriture de fichier, et une fois que je sais que le fichier a été modifié, je règle le niveau de chaque enregistreur basé sur la configuration.

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)

Le code ci-dessus s'utilise comme ci-dessous,

    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)

Une fois que j'ai le nouveau fichier journal config, je peux câbler le bon niveau de journalisation pour chaque enregistreur de config. Je voulais juste partager la réponse et cela pourrait aider quelqu'un s'il essaye de l'utiliser avec gevent.

2
opensourcegeek

Selon votre application, vous devez d'abord trouver un moyen de recharger ce fichier ou de réinitialiser le niveau de journalisation en fonction de votre propre fichier de configuration lors de l'exécution.

Le moyen le plus simple serait d'utiliser une minuterie. Soit vous utilisez des threads pour le faire, soit vous vous en servez dans votre framework async (si vous en utilisez un, ils le mettent généralement en œuvre).

Utilisation de 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)

Sortie:

Test
Test
Test
Test
Test
Something else
Test
Test

Mise à jour: Veuillez vérifier la solution proposée par Martijn Pieters.

0
Mihai