Registro de datos variables con una nueva cadena de formato

85

Utilizo la función de registro para Python 2.7.3. La documentación para esta versión de Python dice :

el paquete de registro es anterior a las opciones de formato más nuevas, como str.format () y string.Template. Estas nuevas opciones de formato son compatibles ...

Me gusta el formato "nuevo" con llaves. Entonces estoy tratando de hacer algo como:

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

Y obtén el error:

TypeError: no todos los argumentos se convierten durante el formateo de cadenas

¿Qué extraño aquí?

PD: no quiero usar

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

porque en este caso el mensaje siempre se formatea independientemente del nivel del registrador.

MajesticRa
fuente
1
Puede hacer esto: log.debug("format this message%d" % 1)
ronak
1
debe configurar el Formatterpara usar '{' como estilo
mata
2
@ronak Gracias por el consejo pero no. Por favor, consulte la sección "ps" por qué. BTW log.debug ("formatear este mensaje% d", 1) - funciona bien.
MajesticRa
@mata ¿Cómo configurarlo? ¿Existe documentación directa para hacerlo?
MajesticRa
@mata lo he encontrado. Por favor, conviértala en una respuesta para que pueda establecerla como "respuesta correcta. Gracias una vez más.
MajesticRa

Respuestas:

38

EDITAR: eche un vistazo al StyleAdapterenfoque en la respuesta de @Dunes a diferencia de esta respuesta; permite usar estilos de formato alternativos sin el texto estándar mientras se llama a los métodos del registrador (debug (), info (), error (), etc.).


De los documentos: uso de estilos de formato alternativos :

Las llamadas de registro (logger.debug (), logger.info (), etc.) solo toman parámetros posicionales para el mensaje de registro real en sí, con parámetros de palabras clave que se utilizan solo para determinar opciones sobre cómo manejar la llamada de registro real (por ejemplo, el parámetro de palabra clave exc_info para indicar que se debe registrar información de rastreo, o el parámetro de palabra clave adicional para indicar información contextual adicional que se agregará al registro). Por lo tanto, no puede realizar llamadas de registro directamente usando la sintaxis str.format () o string.Template, porque internamente el paquete de registro usa% -formatting para fusionar la cadena de formato y los argumentos de las variables. No se cambiaría esto mientras se conserva la compatibilidad con versiones anteriores, ya que todas las llamadas de registro que están en el código existente usarán cadenas de% -format.

Y:

Sin embargo, hay una forma en la que puede usar el formato {} - y $ - para construir sus mensajes de registro individuales. Recuerde que para un mensaje puede usar un objeto arbitrario como una cadena de formato de mensaje, y que el paquete de registro llamará a str () en ese objeto para obtener la cadena de formato real.

Copie y pegue esto en el wherevermódulo:

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)

Entonces:

from wherever import BraceMessage as __

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

Nota: el formateo real se retrasa hasta que sea necesario, por ejemplo, si los mensajes DEBUG no se registran, el formateo no se realiza en absoluto.

jfs
fuente
4
A partir de Python 3.6, puede usar f-strings así:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose
11
@ P1h3r1e3d13 a diferencia del código de registro en la respuesta, las cadenas f '' realizan el formateo de inmediato.
jfs
1
Derecha. Funcionan aquí porque formatean y devuelven una cadena regular antes de llamar al método de registro. Eso puede ser relevante o no para alguien, así que creo que vale la pena mencionarlo como una opción.
Jacktose
3
@Jacktose Creo que los usuarios no deberían iniciar sesión usando f-strings, esto anula los servicios de agregación de registros (por ejemplo, centinela). Hay una buena razón por la que el registro stdlib pospone la plantilla de cadenas.
wim
31

Aquí hay otra opción que no tiene los problemas de palabras clave mencionados en la respuesta de Dunes. Solo puede manejar {0}argumentos posicionales ( ) y no argumentos de palabra clave ( {foo}). Tampoco requiere dos llamadas para formatear (usando el guión bajo). Tiene el factor ick de subclasificar 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

Lo usas así:

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

Por supuesto, puede eliminar la marca de verificación indicada con # optionalpara forzar que todos los mensajes a través del adaptador utilicen un formato de nuevo estilo.


Nota para cualquiera que lea esta respuesta años después : a partir de Python 3.2 , puede usar el parámetro de estilo con Formatterobjetos:

El registro (a partir de la versión 3.2) proporciona un soporte mejorado para estos dos estilos de formato adicionales. La clase Formatter se ha mejorado para tomar un parámetro de palabra clave opcional adicional llamado style. Este valor predeterminado es '%', pero otros valores posibles son '{'y '$', que corresponden a los otros dos estilos de formato. La compatibilidad con versiones anteriores se mantiene de forma predeterminada (como era de esperar), pero al especificar explícitamente un parámetro de estilo, tiene la capacidad de especificar cadenas de formato que funcionan con str.format()o string.Template.

Los documentos proporcionan el ejemplo logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Tenga en cuenta que en este caso aún no puede llamar al loggercon el nuevo formato. Es decir, lo siguiente todavía no funcionará:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either
Felipe
fuente
5
Su afirmación sobre Python 3 es incorrecta. El parámetro de estilo solo se aplica a la cadena de formato Formatter, no a los mensajes de registro individuales. La página a la que vinculó dice explícitamente: "No se cambiaría esto mientras se conserva la compatibilidad con versiones anteriores".
mhsmith
1
Gracias por ser honesto conmigo. La primera parte es menos útil ahora, pero la he reformulado en términos de Formatter, que es correcto ahora (creo). El StyleAdapter todavía funciona,
Felipe
@falstro - gracias por señalar eso. La versión actualizada debería funcionar ahora. Dado que BraceStringes una subclase de cadena, es seguro regresar desde__str__
Felipe
1
única respuesta que menciona style = "{", +1
Tom S.
24

Esta fue mi solución al problema cuando descubrí que el registro solo usa formato de estilo printf. Permite que las llamadas de registro sigan siendo las mismas, sin sintaxis especial como log.info(__("val is {}", "x")). El cambio requerido al código es envolver el registrador en un 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}

El uso es:

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

Vale la pena señalar que esta aplicación tiene problemas si las palabras clave que se utilizan para la sustitución de refuerzo incluyen level, msg, args, exc_info, extrao stack_info. Estos son nombres de argumentos utilizados por el logmétodo de Logger. Si necesita uno de estos nombres, modifíquelos processpara excluirlos o simplemente elimínelos log_kwargsde la _logllamada. En una nota adicional, esta implementación también ignora silenciosamente las palabras clave mal escritas destinadas al Logger (por ejemplo ectra).

Dunas
fuente
4
Python doc recomienda esta forma, docs.python.org/3/howto/…
eshizhan
23

La solución más sencilla sería utilizar el módulo excelentelogbook

import logbook
import sys

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

O el más completo:

>>> 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
Thomas Orozco
fuente
Esto se ve muy bien, pero ¿hay alguna manera de tener milisegundos en lugar de solo segundos?
Jeff
@Jeff seguro, el libro de registro te permite definir controladores personalizados y usar formatos de cadena personalizados.
Thomas Orozco
5
@Jeff Un par de años después: la precisión de tiempo predeterminada es milisegundos.
Jan Vlcinsky
12

Como mencionan otras respuestas, el formato de estilo de llave introducido en Python 3.2 solo se usa en la cadena de formato, no en los mensajes de registro reales.

Para habilitar el formato de estilo de llaves en el mensaje de registro real, podemos parchear un poco del código del registrador.

Lo siguiente parchea el loggingmódulo para crear una get_loggerfunción que devolverá un registrador que usa el formato de nuevo estilo para cada registro que maneja.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its 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

Uso:

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

Notas:

  • Totalmente compatible con los métodos de registro normales (solo reemplace logging.getLoggercon get_logger)
  • Solo afectará a los registradores específicos creados por la get_loggerfunción (no rompe los paquetes de terceros).
  • Si se vuelve a acceder al registrador desde una logging.getLogger()llamada normal , se seguirá aplicando el formato de nuevo estilo.
  • kwargs no son compatibles (hace que sea imposible de conflicto con el built-in exc_info, stack_info, stacklevely extra).
  • El impacto en el rendimiento debe ser mínimo (reescribiendo un solo puntero de función para cada mensaje de registro).
  • El formateo del mensaje se retrasa hasta que se envía (o no se produce si se filtra el mensaje de registro).
  • Los argumentos se almacenan en logging.LogRecordobjetos como de costumbre (útil en algunos casos con controladores de registro personalizados).
  • Al mirar el loggingcódigo fuente del módulo , parece que debería funcionar hasta Python 2.6 cuando str.formatse introdujo (pero solo lo probé en 3.5 y versiones posteriores)
pR0Ps
fuente
2
La única respuesta que considera que la cadena de depuración solo debe calcularse si se va a imprimir el mensaje del depurador. ¡Gracias!
Fafaman
2

Prueba logging.setLogRecordFactoryen 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)
nexcvon
fuente
Funciona, pero el problema es que se rompen los módulos de terceros que utilizan el %formato, ya que la fábrica de registros es global para el módulo de registro.
jtaylor
1

Creé un formateador personalizado, llamado ColorFormatter que maneja el problema de esta manera:

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…

Esto lo mantiene compatible con varias bibliotecas. El inconveniente es que probablemente no funcione debido a que podría intentar formatear la cadena dos veces.

Gringo Suave
fuente
0

Aquí hay algo realmente simple que funciona:

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

Entonces:

mydebuglog("hello {} {val}", "Python", val="World")
Maestros holandeses
fuente
0

Solución similar a pR0Ps', envoltura getMessagede LogRecordpor envoltura makeRecord(en lugar de handleen su respuesta) en los casos de Loggerque debe ser completamente nuevo formateo habilitados:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

Probé esto con Python 3.5.3.

Dragorn421
fuente