129 votes

Comment ajouter un champ personnalisé à la chaîne de format du journal Python ?

Ma chaîne de format actuelle est :

formatter = logging.Formatter('%(asctime)s : %(message)s')

et je veux ajouter un nouveau champ appelé app_name qui aura une valeur différente dans chaque script qui contient ce formateur.

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

Mais je ne suis pas sûr de savoir comment faire passer ça. app_name à l'enregistreur pour l'interpoler dans la chaîne de format. Je peux évidemment faire en sorte qu'elle apparaisse dans le message du journal en la passant à chaque fois, mais c'est un peu compliqué.

J'ai essayé :

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

mais aucune ne fonctionne.

177voto

unutbu Points 222216

Vous pourriez utiliser un LoggerAdapter afin que vous n'ayez pas à transmettre les informations supplémentaires à chaque appel à l'enregistrement :

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

journaux (quelque chose comme)

2013-07-09 17:39:33,596 Super App : The sky is so blue

Filtres peut également être utilisé pour ajouter des informations contextuelles.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

produit un enregistrement similaire.

59voto

mr2ert Points 4956

Vous devez passer le dict comme paramètre à extra pour le faire de cette façon.

logging.info('Log message', extra={'app_name': 'myapp'})

Preuve :

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

Par ailleurs, si vous essayez d'enregistrer un message sans passer par le dict, l'opération échouera.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

56voto

Ahmad Points 594

Python3

À partir de Python3.2, vous pouvez désormais utiliser les éléments suivants LogRecordFactory

import logging

logging.basicConfig(format="%(custom_attribute)s - %(message)s")

old_factory = logging.getLogRecordFactory()

def record_factory(*args, **kwargs):
    record = old_factory(*args, **kwargs)
    record.custom_attribute = "my-attr"
    return record

logging.setLogRecordFactory(record_factory)

>>> logging.info("hello")
my-attr - hello

Bien sûr, record_factory peut être personnalisé pour être n'importe quel appelable et la valeur de l'option custom_attribute pourrait être mis à jour si vous gardez une référence à l'appelable de la fabrique.

Pourquoi est-ce mieux que d'utiliser des adaptateurs/filtres ?

  • Vous n'avez pas besoin de faire circuler votre logger dans l'application.
  • Il fonctionne en fait avec des bibliothèques tierces qui utilisent leur propre enregistreur (en appelant simplement logger = logging.getLogger(..) ) aurait maintenant le même format de journal. (ce n'est pas le cas avec les filtres/adaptateurs où vous devez utiliser le même objet de journalisation).
  • Vous pouvez empiler/chaîner plusieurs usines

13voto

prmatta Points 1189

Une autre solution consiste à créer un LoggerAdapter personnalisé. Ceci est particulièrement utile lorsque vous ne pouvez pas changer le format OU si votre format est partagé avec du code qui n'envoie pas la clé unique (dans votre cas nom de l'application ) :

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

Et dans votre code, vous devez créer et initialiser votre logger comme d'habitude :

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

Enfin, vous devez créer l'adaptateur wrapper pour ajouter un préfixe si nécessaire :

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

La sortie ressemblera à quelque chose comme ceci :

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

2voto

rhn89 Points 133

J'ai trouvé cette question SO après l'avoir mise en œuvre moi-même. J'espère que cela aidera quelqu'un. Dans le code ci-dessous, j'induis une clé supplémentaire appelée claim_id dans le format de l'enregistreur. Il enregistrera le claim_id à chaque fois qu'il y aura une claim_id clé présente dans l'environnement. Dans mon cas d'utilisation, j'avais besoin d'enregistrer ces informations pour une fonction AWS Lambda.

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'

class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})

def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger

LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

Gist : https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X