Registro de datos variables con nueva cadena de formato.

Yo uso la facilidad 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 nuevas opciones de formato, como str.format () y string.Template. Estas nuevas opciones de formato son compatibles …

Me gusta el formato ‘nuevo’ con llaves. Así que estoy tratando de hacer algo como:

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

Y obtener error:

TypeError: no todos los argumentos se convierten durante el formato de cadena

¿Qué extraño aquí?

PD no quiero usar

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

porque en este caso el mensaje siempre se está formateando independientemente del nivel del registrador.

EDITAR: eche un vistazo al enfoque de StyleAdapter en la respuesta de @Dunes a diferencia de esta respuesta; permite utilizar estilos de formato alternativos sin la placa de repetición al llamar a los métodos del registrador (depuración (), información (), error (), etc.).


De la documentación – 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 utilizados solo para determinar las 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 la información de rastreo, o el parámetro de palabra clave adicional para indicar que se agregue información contextual adicional al registro). Por lo tanto, no puede hacer llamadas de registro directamente usando str.format () o cadena. Utilice la syntax, porque internamente el paquete de registro usa% -formatting para combinar la cadena de formato y los argumentos variables. No cambiaría esto y preservaría la compatibilidad con versiones anteriores, ya que todas las llamadas de registro que existen en el código existente usarán cadenas de formato%.

Y:

Sin embargo, hay una forma en 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 wherever mó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 formato real se retrasa hasta que sea necesario, por ejemplo, si los mensajes DEBUG no se registran, el formato no se realiza en absoluto.

Aquí hay otra opción que no tiene los problemas de palabras clave mencionados en la respuesta de Dunes. Solo puede manejar argumentos posicionales ( {0} ) y no argumentos de palabras clave ( {foo} ). Tampoco requiere dos llamadas para formatear (usando el guión bajo). Tiene el ick-factor de subclasificación de 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 el cheque anotado con # optional para forzar que todos los mensajes a través del adaptador usen un formato de estilo nuevo.


Nota para cualquiera que lea esta respuesta años más tarde : a partir de Python 3.2 , puede usar el parámetro de estilo con objetos Formatter :

El registro (a partir de 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 . El 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 cabría esperar), pero al especificar explícitamente un parámetro de estilo, puede 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 todavía no puede llamar al logger con 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 

La solución más fácil sería utilizar el excelente módulo de logbook

 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 

Esta fue mi solución al problema cuando descubrí que el registro solo utiliza el formato de estilo printf. Permite que las llamadas de registro sigan siendo las mismas, sin una syntax especial como log.info(__("val is {}", "x")) . El cambio requerido para el 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} substiution", type="brace") 

Vale la pena señalar que esta implementación tiene problemas si las palabras clave utilizadas para la sustitución de llaves incluyen level , msg , args , exc_info , extra o stack_info . Estos son nombres de argumentos utilizados por el método de log del log . Si necesita uno de estos nombres, modifique el process para excluir estos nombres o simplemente elimine log_kwargs de la llamada _log . En una nota adicional, esta implementación también ignora silenciosamente las palabras mal escritas para el registrador (por ejemplo, ectra ).

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

A partir de Python 3.5, no hay una buena manera de usar el formato de estilo de llaves para registrar mensajes.

Sin embargo, como con la mayoría de las cosas en Python, hay una manera no agradable.

El siguiente mono-parches del módulo de logging para crear una función get_logger que devolverá un registrador que utiliza el formato de nuevo estilo para cada registro de 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 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 

Uso:

 >>> log = get_logger() >>> log.warning("{!r}", log)  

Notas:

  • Solo afectará a registradores específicos creados por la función get_logger .
  • Si se accede nuevamente al registrador desde una logging.getLogger() normal logging.getLogger() , el formato de nuevo estilo seguirá siendo válido.
  • los kwargs no son compatibles
  • El impacto del rendimiento debe ser mínimo (volver a escribir un puntero de función para cada mensaje de registro)
  • El formato del mensaje se retrasa hasta que se emite
  • No impide que los logging.LogRecord se almacenen en el logging.LogRecord Objetos logging.LogRecord (útiles en ciertos casos)
  • Al mirar el código fuente del módulo de logging , parece que debería funcionar hasta Python 2.6 cuando se introdujo str.format (pero solo se probó en Python 3.5).

Intente logging.setLogRecordFactory en 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) 

Aquí hay algo muy 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") 

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 sea eficaz debido a que posiblemente intente formatear la cadena dos veces.