web-dev-qa-db-fra.com

Enregistrement de données variables avec une nouvelle chaîne de format

J'utilise la fonction de journalisation pour python 2.7.3. La documentation de cette version Python dit :

le package de journalisation est antérieur aux nouvelles options de formatage telles que str.format () et string.Template. Ces nouvelles options de formatage sont supportées ...

J'aime le nouveau format avec des accolades. J'essaie donc de faire quelque chose comme:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

Et obtenir une erreur:

TypeError: pas tous les arguments convertis lors du formatage de chaîne

Qu'est-ce qui me manque ici?

P.S. Je ne veux pas utiliser 

log.debug("format this message {0}".format(1))

car dans ce cas, le message est toujours formaté quel que soit le niveau de l’enregistreur.

69
MajesticRa

EDIT: jetez un oeil à l'approche StyleAdapter dans la réponse de @Dunes contrairement à cette réponse; cela permet d'utiliser d'autres styles de formatage sans le passe-partout tout en appelant les méthodes de l'enregistreur (debug (), info (), error (), etc.).


À partir de la documentation - Utilisation de styles de formatage différents :

Les appels de journalisation (logger.debug (), logger.info () etc.) ne prennent que paramètres de position pour le message de consignation lui-même, avec paramètres de mot clé utilisés uniquement pour déterminer les options de traitement de l'appel de journalisation réel (par exemple, le paramètre mot clé exc_info à indique que les informations de suivi doivent être consignées, ou le paramètre mot clé supplémentaire pour indiquer que des informations contextuelles supplémentaires doivent être ajoutées au journal). Donc, vous ne pouvez pas directement passer des appels de journalisation en utilisant Syntaxe str.format () ou string.Template, car en interne la journalisation package utilise% -formatting pour fusionner la chaîne de format et la variable arguments. Il n’y aurait pas moyen de changer cela tout en préservant l’arrière compatibilité, puisque tous les appels de journalisation existant dans le code utilisera des chaînes% -format.

Et:

Cependant, il existe un moyen d'utiliser {} - et $ - le formatage en construisez vos messages de journal individuels. Rappelez-le pour un message vous pouvez utiliser un objet arbitraire comme chaîne de format de message, et que le Le paquet de journalisation appellera str () sur cet objet pour obtenir le .__ réel. chaîne de format.

Copier-coller dans le module wherever:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

Ensuite:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

Remarque: le formatage réel est retardé jusqu'à ce qu'il soit nécessaire, par exemple, si les messages DEBUG ne sont pas enregistrés, le formatage n'est pas effectué du tout.

38
jfs

Voici une autre option qui ne présente pas les problèmes de mots clés mentionnés dans la réponse de Dunes. Il ne peut gérer que des arguments de position ({0}) et non des arguments de mot clé ({foo}). Il n’exige pas non plus que deux appels soient formatés (avec le trait de soulignement). Il a le facteur clé de sous-classement str:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

Vous l'utilisez comme ceci:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

Bien sûr, vous pouvez supprimer la vérification notée avec # optional pour forcer tous les messages de l'adaptateur à utiliser une mise en forme de nouveau style.


Note pour tous ceux qui liront cette réponse des années plus tard}: À partir de Python 3.2, vous pouvez utiliser le paramètre de style avec des objets Formatter:

La journalisation (à partir de la version 3.2) offre un support amélioré pour ces deux styles de formatage supplémentaires. Le La classe de formatage a été améliorée pour prendre un paramètre de mot clé supplémentaire facultatif nommé style. Ce La valeur par défaut est '%', mais les autres valeurs possibles sont '{' et '$', qui correspondent aux deux autres styles de mise en forme. La compatibilité ascendante est conservée par défaut (comme on peut s'y attendre), mais par en spécifiant explicitement un paramètre de style, vous avez la possibilité de spécifier des chaînes de format qui fonctionnent avec str.format() ou string.Template

La documentation fournit l'exemple logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Notez que dans ce cas, vous ne pouvez toujours pas appeler logger avec le nouveau format. C'est-à-dire que les éléments suivants ne fonctionnent toujours pas:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either
24
Felipe

La solution la plus simple serait d’utiliser le module excellent logbook

import logbook
import sys

logbook.StreamHandler(sys.stdout).Push_application()
logbook.debug('Format this message {k}', k=1)

Ou le plus complet:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).Push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1
19
Thomas Orozco

Telle était ma solution au problème lorsque j'ai découvert que la journalisation utilise uniquement le formatage de style printf. Il permet aux appels de consignation de rester les mêmes - pas de syntaxe spéciale telle que log.info(__("val is {}", "x")). La modification requise pour coder consiste à envelopper l'enregistreur dans une StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

L'utilisation est:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substiution", type="brace")

Il convient de noter que cette implémentation pose des problèmes si les mots clés utilisés pour la substitution d'accolade incluent level, msg, args, exc_info, extra ou stack_info. Ce sont des noms d'argument utilisés par la méthode log de Logger. Si vous avez besoin de l’un de ces noms, modifiez process pour exclure ces noms ou supprimez simplement log_kwargs de l’appel _log. Par ailleurs, cette implémentation ignore également les mots clés mal orthographiés destinés à l'enregistreur (par exemple, ectra).

18
Dunes

Comme d'autres réponses le mentionnent, la mise en forme de type accolade introduite dans Python 3.2 est uniquement utilisée dans la chaîne de formatage, pas dans les messages de journal réels.

Depuis Python 3.5, il n’existait aucun moyen pratique d’utiliser le formatage en accolade pour consigner les messages.

Cependant, comme avec la plupart des choses en Python, il n'y a pas moyen de faire ça.

Le module suivant corrige le module logging pour créer une fonction get_logger qui renverra un consignateur utilisant le formatage de style nouveau pour chaque enregistrement de journal traité.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    it's messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, Tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

Usage:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

Remarques:

  • N'affectera que les enregistreurs spécifiques créés par la fonction get_logger.
  • Si vous accédez à nouveau à l’enregistreur à partir d’un appel logging.getLogger() normal, le formatage de nouveau style s’appliquera toujours.
  • les kwargs ne sont pas supportés
  • La performance doit être minime (réécrire un seul pointeur de fonction pour chaque message du journal)
  • Le formatage du message est retardé jusqu'à sa sortie
  • N'empêche pas le stockage des arguments sur des objets logging.LogRecord (utile dans certains cas)
  • En regardant le code source du module logging , il semble que cela devrait fonctionner jusqu’à Python 2.6 lorsque str.format a été introduit (mais n’a été testé que sur Python 3.5).
10
pR0Ps

Essayez logging.setLogRecordFactory dans Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)
1
nexcvon

Voici quelque chose de très simple qui fonctionne:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

Ensuite:

mydebuglog("hello {} {val}", "Python", val="World")
1
Dutch Masters

J'ai créé un formateur personnalisé, appelé ColorFormatter qui gère le problème de la manière suivante:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

Cela le maintient compatible avec diverses bibliothèques ... L'inconvénient est qu'il n'est probablement pas performant en raison du risque de tentative de formatage de la chaîne deux fois.

0
Gringo Suave