messages de journal apparaissant deux fois avec la journalisation Python


100

J'utilise la journalisation Python et, pour une raison quelconque, tous mes messages apparaissent deux fois.

J'ai un module pour configurer la journalisation:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Plus tard, j'appelle cette méthode pour configurer la journalisation:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

Et puis dans disons, le module buy_ham, j'appellerais:

self.logger.info('Successfully able to write to %s' % path)

Et pour une raison quelconque, tous les messages apparaissent deux fois. J'ai commenté l'un des gestionnaires de flux, toujours la même chose. Un peu bizarre, je ne sais pas pourquoi cela se produit ... lol. En supposant que j'ai raté quelque chose d'évident.

Bravo, Victor


1
Etes-vous sûr qu'il configure_logging()n'est pas appelé deux fois (par exemple depuis le constructeur aussi)? Une seule instance de Boy () est-elle créée?
Jacek Konieczny

1
Utiliser à la self.logger.handlers = [ch]place résoudrait ce problème, mais il serait préférable de vous assurer de ne pas exécuter ce code deux fois, par exemple en utilisant if not self.loggerau début.
Ninjakannon

Réponses:


135

Vous appelez configure_loggingdeux fois (peut-être dans la __init__méthode de Boy): getLoggerrenvoie le même objet, mais addHandlerne vérifie pas si un gestionnaire similaire a déjà été ajouté à l'enregistreur.

Essayez de suivre les appels à cette méthode et d'éliminer l'un d'entre eux. Ou configurez un indicateur logging_initializedinitialisé Falsedans la __init__méthode de Boyet changez configure_loggingpour ne rien faire si logging_initializedc'est le cas True, et pour le définir Trueaprès avoir initialisé le logger.

Si votre programme crée plusieurs Boyinstances, vous devrez changer la façon dont vous faites les choses avec une configure_loggingfonction globale ajoutant les gestionnaires, et la Boy.configure_loggingméthode initialisant uniquement l' self.loggerattribut.

Une autre façon de résoudre ce problème consiste à vérifier l'attribut handlers de votre enregistreur:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
Oui, tu avais raison - idiot moi. Je l'ai appelé dans init , ainsi que explicitement ailleurs. Lol. Merci =).
victorhooi

Merci. Votre solution m'a sauvé aujourd'hui.
ForeverLearner

1
Dans mon cas, ils apparaissaient 6 fois. J'avais suspecté cela parce que j'ai déclaré le même type d'enregistreur dans 6 classes oop
answerSeeker

5
Je voudrais partager ici mon expérience: pour une application Flask que j'ai développée, les messages du journal apparaissaient PLUS DE DEUX FOIS. Je dirais qu'ils incrémentaient sur le fichier journal, en raison du fait que, lorsque l'application et les modules ont été chargés, la loggervariable utilisée n'était pas celle instanciée à partir d'une de mes classes, mais la loggervariable présente sur le cache Python3 , et le gestionnaire a été ajouté toutes les 60 secondes par un AppScheduler que j'ai configuré. C'est donc if not logger.handlersun moyen assez intelligent d'éviter ce type de phénomène. Merci pour la solution, camarade :)!
ivanleoncz

2
Je vois ce problème dans mon application Flask. Cette solution a résolu le problème des messages de journal générés dans l'application flask principale, mais mon application fonctionne dans un module de bibliothèque, et ces messages de cette bibliothèque sont toujours enregistrés plusieurs fois. Je ne sais pas comment résoudre ce problème.
Cas

27

Si vous rencontrez ce problème et que vous n'ajoutez pas deux fois le gestionnaire, consultez la réponse d'abarnert ici

À partir de la documentation :

Remarque: Si vous attachez un gestionnaire à un enregistreur et à un ou plusieurs de ses ancêtres, il peut émettre le même enregistrement plusieurs fois. En général, vous ne devriez pas avoir besoin d'attacher un gestionnaire à plus d'un enregistreur - si vous l'attachez simplement à l'enregistreur approprié qui est le plus élevé dans la hiérarchie des enregistreurs, alors il verra tous les événements enregistrés par tous les enregistreurs descendants, à condition que leur propagation le paramètre est laissé sur True. Un scénario courant consiste à attacher les gestionnaires uniquement à l'enregistreur racine et à laisser la propagation s'occuper du reste.

Donc, si vous voulez un gestionnaire personnalisé sur "test", et que vous ne voulez pas que ses messages soient également envoyés au gestionnaire racine, la réponse est simple: désactivez son propagateindicateur:

logger.propagate = False

1
C'est la meilleure réponse. Cela ne correspondait pas à l'objectif de l'affiche (erreur logique de codage) mais la plupart du temps, cela devrait être le cas.
Artem

Bravo. CECI est la cause réelle des doublons (pour les cas les plus généraux).
M. Duhart

C'était aussi le problème avec mon code. Merci beaucoup.
dur

Meilleure réponse jamais. Je vous remercie!
Foivos Ts

Pourquoi cela ne peut-il pas être par défaut? Je construis tous mes enregistreurs sous "root". + structure de répertoires, donc je peux facilement tout contrôler depuis le logger `` root ''
MortenB

8

Le gestionnaire est ajouté chaque fois que vous appelez de l'extérieur. Essayez de supprimer le gestionnaire après avoir terminé votre travail:

self.logger.removeHandler(ch)

1
J'ai utilisé logger.handlers.pop() en python 2.7, fait l'affaire
radtek

7

Je suis un débutant en python, mais cela a semblé fonctionner pour moi (Python 2.7)

while logger.handlers:
     logger.handlers.pop()

4

Dans mon cas, je devrais régler logger.propagate = Falsepour éviter la double impression.

Dans le code ci-dessous, si vous supprimez, logger.propagate = Falsevous verrez une double impression.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

C'est le problème que j'ai. Merci
q0987 le

Très bonne réponse; L'ajout logger.propagate = Falseétait la solution pour éviter la double journalisation dans une application Flask hébergée par Waitress, lors de la connexion à l' app.loggerinstance de Flask .
bluebinary

1

Un appel aux logging.debug()appels logging.basicConfig()s'il n'y a pas de gestionnaire racine installé. Cela se passait pour moi dans un cadre de test où je ne pouvais pas contrôler l'ordre dans lequel les cas de test étaient déclenchés. Mon code d'initialisation installait le second. La valeur par défaut utilise logging.BASIC_FORMAT que je ne voulais pas.


Je pense que c'est ce qui se passe pour moi. Comment empêcher la création automatique d'enregistreurs de console?
Robert le

@Robert il s'agit de s'assurer que vous êtes initialisé avec l'enregistreur souhaité, avant le premier appel de journalisation. Les frameworks de test peuvent obscurcir cela, mais il devrait y avoir un moyen de le faire. De plus, si vous faites du multitraitement, vous devez faire de même avec chaque processus.
JimB le

1

Il semble que si vous envoyez quelque chose à l'enregistreur (accidentellement) puis le configurez, il est trop tard. Par exemple, dans mon code j'avais

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

J'obtiendrais quelque chose comme (en ignorant les options de format)

look out
hello
hello

et tout a été écrit deux fois sur stdout. Je pense que c'est parce que le premier appel à logging.warningcrée un nouveau gestionnaire automatiquement, puis j'ai explicitement ajouté un autre gestionnaire. Le problème a disparu lorsque j'ai supprimé le premier logging.warningappel accidentel .


0

J'avais une situation étrange où les journaux de la console étaient doublés mais pas mes journaux de fichiers. Après une tonne de fouilles, j'ai compris.

Veuillez noter que les packages tiers peuvent enregistrer des enregistreurs. C'est quelque chose à surveiller (et dans certains cas ne peut être évité). Dans de nombreux cas, le code tiers vérifie s'il existe une racine gestionnaires de journalisation ; et s'il n'y en a pas, ils enregistrent un nouveau gestionnaire de console.

Ma solution était d'enregistrer mon enregistreur de console au niveau racine:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.