Dupliquer la sortie du journal lors de l'utilisation du module de journalisation Python

105

J'utilise python logger. Voici mon code:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Le problème que j'ai est que j'obtiens plusieurs entrées dans le fichier journal pour chaque logger.infoappel. Comment puis-je resoudre ceci?

user865438
la source
Travaille pour moi. Python 3.2 et Windows XP.
Zuljin
2
Êtes-vous sûr de ne pas créer plusieurs instances de journalisation?
Gandi
Oui. dans un fichier différent, je prends une nouvelle instance comme nous l'avons fait dans les projets Java. Veuillez me préciser si cela crée un problème ou non.
user865438

Réponses:

94

Le logging.getLogger()est déjà un singleton. ( Documentation )

Le problème est que chaque fois que vous appelez myLogger(), cela ajoute un autre gestionnaire à l'instance, ce qui entraîne la duplication des journaux.

Peut-être quelque chose comme ça?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger
Werner Smit
la source
3
Je pense que vous devriez avoir loggers.update (dict ((name, logger))) à la place.
acrophobie
pourquoi loggers.update(dict(name=logger))? n'est pas loggers[name] = loggerplus simple?
Ryan J McCall
@RyanJMcCall À l'époque, c'était la convention de codage que j'utilisais. Mais en examinant le code tel qu'il est maintenant, je constate qu'il est cassé. loggers.update(dict(name=logger))créera un dictionnaire avec une seule clé appelée nameet mettra continuellement à jour cette même clé. Je suis surpris que personne n'ait mentionné cela plus tôt car ce code est assez cassé :) Apportera les modifications nécessaires.
Werner Smit
Je vois que @acrophobie échappait à cela il y a longtemps. Merci.
Werner Smit
le loggersdictionnaire global n'est-il pas redondant avec logging.getLogger? puisque vous voulez vraiment simplement éviter d'ajouter des gestionnaires supplémentaires, il semble que vous préfériez les réponses ci-dessous qui vérifient directement les gestionnaires
mway
60

Depuis Python 3.2, vous pouvez simplement vérifier si les gestionnaires sont déjà présents et si c'est le cas, effacez-les avant d'ajouter de nouveaux gestionnaires. C'est assez pratique lors du débogage et le code inclut l'initialisation de votre enregistreur

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)
rm957377
la source
Bonne réponse, Thx :))
Gavriel Cohen
3
Notez que hasHandlers () retournera true dans pytest où un gestionnaire a été ajouté au journal racine, même si vos gestionnaires locaux / personnalisés n'ont pas encore été ajoutés. Le len (logger.handlers) (selon la réponse de Guillaume) renverra 0 dans ce cas, donc peut-être une meilleure option.
Grant
C'est la vraie solution que je recherchais.
XCanG
45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

fait le truc pour moi

en utilisant python 2.7

Guillaume Cisco
la source
1
Cela fonctionne même lorsque le module est rechargé (ce qui n'est pas le cas des autres réponses)
yco
3
Merci pour l'astuce, BTW pour vérifier si une liste est vide ou non, vous n'avez pas besoin d'utiliser l'opérateur "len" que vous pouvez utiliser directement si ma_liste: ..
rkachach
26

J'ai déjà utilisé le loggercomme singleton et vérifié if not len(logger.handlers), mais j'ai quand même eu des doublons : c'était la sortie formatée, suivie de la sortie non formatée.

Solution dans mon cas: logger.propagate = False

Crédits pour cette réponse et les documents .

Monsieur B.
la source
1
J'avais compris que la journalisation doublée provenait de RootLogger et de mon StreamHandler, mais je ne pouvais pas résoudre le problème (tout en gardant mon formateur sur StreamHandler) jusqu'à ce que cela fasse.
Xander YzWich
10

Vous appelez Logger.myLogger()plus d'une fois. Stockez l'instance de journalisation qu'elle renvoie quelque part et réutilisez -la .

Sachez également que si vous vous connectez avant l'ajout d'un gestionnaire, une valeur par défaut StreamHandler(sys.stderr)sera créée.

Matt Joiner
la source
En fait, j'essaie d'accéder à l'instance de l'enregistreur telle que nous l'utilisons en java, mais je ne sais pas s'il est nécessaire de créer une instance une seule fois pour tout un projet ou non.
user865438
1
@ user865483: Une seule fois. Tous les enregistreurs de bibliothèque standard sont des singletons.
Matt Joiner
5

Ceci est un ajout à la réponse de @ rm957377 mais avec une explication de pourquoi cela se produit . Lorsque vous exécutez une fonction lambda dans AWS, ils appellent votre fonction à partir d'une instance d'encapsulation qui reste active pendant plusieurs appels. Cela signifie que si vous appelez addHandler()dans le code de votre fonction, il continuera à ajouter des gestionnaires en double au singleton de journalisation à chaque exécution de la fonction. Le singleton de journalisation persiste pendant plusieurs appels de votre fonction lambda.

Pour résoudre ce problème, vous pouvez effacer vos gestionnaires avant de les définir via:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)
Tchad Befus
la source
D'une manière ou d'une autre, dans mon cas, les gestionnaires d'enregistreurs sont ajoutés lors de l'événement sur .info()appel, ce que je ne comprends pas.
Evgeny
4

Votre enregistreur doit fonctionner comme un singleton. Vous ne devriez pas le créer plus d'une fois. Voici un exemple à quoi cela pourrait ressembler:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")
Zuljin
la source
puis encore si je vais prendre l'instance différente dans un fichier différent. Supposons que dans le fichier 1 s = Logger () m = s.myLogger () et dans le fichier 2 s = Logger () Cela fonctionnera ou non m2 = s.myLogger ()
user865438
Je reçois toujours une copie du même journal plusieurs fois. J'ai un doute ici si le journal interne du fil en imprime plus d'un ou non. Aidez-moi à ce sujet.
user865438
1
@ user865438, nous n'avons pas à nous soucier de faire de l'implémentation un singleton (c'est déjà le cas). Pour vous connecter aux sous-modules, suivez le lien officiel du livre de recettes de journalisation . En gros, vous devez suivre la hiérarchie de dénomination lors de la dénomination des enregistreurs et il s'occupe du reste.
narayan
4

L'implémentation de logger est déjà un singleton.

Plusieurs appels à logging.getLogger ('someLogger') renvoient une référence au même objet de journalisation. Cela est vrai non seulement dans le même module, mais également dans tous les modules tant qu'il s'agit du même processus d'interprétation Python. C'est vrai pour les références au même objet; De plus, le code d'application peut définir et configurer un enregistreur parent dans un module et créer (mais pas configurer) un enregistreur enfant dans un module séparé, et tous les appels d'enregistreur à l'enfant passeront au parent. Voici un module principal

Source - Utilisation de la journalisation dans plusieurs modules

Donc, la façon dont vous devriez l'utiliser est -

Supposons que nous ayons créé et configuré un logger appelé 'main_logger' dans le module principal (qui configure simplement le logger, ne renvoie rien).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Maintenant dans un sous-module, si nous créons un enregistreur enfant suivant la hiérarchie de dénomination 'main_logger.sub_module_logger' , nous n'avons pas besoin de le configurer dans le sous-module. La simple création de l'enregistreur suivant la hiérarchie de dénomination est suffisante.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

Et cela n'ajoutera pas non plus de gestionnaire de doublons.

Voir cette question pour une réponse un peu plus détaillée.

Narayan
la source
1
redéfinir les gestionnaires après getLogger semble fonctionner pour moi: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek
2

Une sortie de l'enregistreur double (ou triple ou ..- basée sur le nombre de recharges) peut également se produire lorsque vous rechargez votre module via importlib.reload(pour la même raison que celle expliquée dans la réponse acceptée). J'ajoute cette réponse juste pour une référence future car il m'a fallu un certain temps pour comprendre pourquoi ma sortie est dupliquée (triple) cated.

rkuska
la source
1

Une solution de contournement simple est comme

logger.handlers[:] = [handler]

De cette façon, vous évitez d'ajouter un nouveau gestionnaire aux "gestionnaires" de la liste sous-jacente.

aihex
la source
1

En fin de compte, dans la plupart des cas, lorsque cela se produit, il suffit d'appeler logger.getLogger () une seule fois par module. Si vous avez plusieurs classes comme moi, je pourrais l'appeler ainsi:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Les deux auront alors leur propre nom de package complet et leur méthode d'enregistrement.

Harlin
la source
0

Vous pouvez obtenir la liste de tous les gestionnaires pour l'enregistreur particulier, vous pouvez donc faire quelque chose comme ça

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

Dans l'exemple ci-dessus, nous vérifions si le gestionnaire d'un fichier spécifié est déjà connecté à l'enregistreur, mais avoir accès à la liste de tous les gestionnaires vous donne la possibilité de décider sur quels critères vous devez ajouter un autre gestionnaire ou non.

Le plus recherché
la source
0

Eu ce problème aujourd'hui. Puisque mes fonctions étaient @staticmethod, les suggestions ci-dessus ont été résolues avec random ().

Ressemblant à quelque chose comme:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))
Pac-Man
la source
-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger
Avinash Kumar
la source
Veuillez ajouter une explication pour rendre cette réponse plus utile pour une utilisation à long terme.
Aminah Nuraini