values - ¿Se puede modificar el formato de registro de Python dependiendo del nivel de registro de mensajes?
python logging level values (7)
Estoy usando el mecanismo de logging
de Python para imprimir el resultado en la pantalla. Podría hacer esto con declaraciones de impresión, pero quiero permitir una granularidad más precisa para que el usuario desactive ciertos tipos de resultados. Me gusta el formato impreso para errores, pero preferiría un formato más simple cuando el nivel de salida es "información".
Por ejemplo:
logger.error("Running cmd failed")
logger.info("Running cmd passed")
En este ejemplo, me gustaría que el formato del error se imprima de manera diferente:
# error Aug 27, 2009 - ERROR: Running cmd failed # info Running cmd passed
¿Es posible tener diferentes formatos para diferentes niveles de registro sin tener múltiples objetos de registro? Preferiría hacer esto sin modificar el registrador una vez que se haya creado, ya que hay una gran cantidad de sentencias if / else para determinar cómo se debe registrar la salida.
Acabo de toparme con este problema y tuve problemas para completar los "agujeros" que quedan en el ejemplo anterior. Aquí hay una versión más completa y funcional que utilicé. Espero que esto ayude a alguien:
# Custom formatter
class MyFormatter(logging.Formatter):
err_fmt = "ERROR: %(msg)s"
dbg_fmt = "DBG: %(module)s: %(lineno)d: %(msg)s"
info_fmt = "%(msg)s"
def __init__(self, fmt="%(levelno)s: %(msg)s"):
logging.Formatter.__init__(self, fmt)
def format(self, record):
# Save the original format configured by the user
# when the logger formatter was instantiated
format_orig = self._fmt
# Replace the original format with one customized by logging level
if record.levelno == logging.DEBUG:
self._fmt = MyFormatter.dbg_fmt
elif record.levelno == logging.INFO:
self._fmt = MyFormatter.info_fmt
elif record.levelno == logging.ERROR:
self._fmt = MyFormatter.err_fmt
# Call the original formatter class to do the grunt work
result = logging.Formatter.format(self, record)
# Restore the original format configured by the user
self._fmt = format_orig
return result
Editar:
Felicitaciones de Halloleo, aquí hay un ejemplo de cómo usar lo anterior en tu script:
fmt = MyFormatter()
hdlr = logging.StreamHandler(sys.stdout)
hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
logging.root.setLevel(DEBUG)
Editar 2:
El registro de Python3 ha cambiado un poco. Vea here para un acercamiento de Python3.
En lugar de depender de estilos o campos internos, también puede crear un formateador que delegue en otros formateadores dependiendo de record.levelno (u otros criterios). Esta es una solución ligeramente más limpia en mi humilde opinión. El siguiente código debería funcionar para cualquier versión de Python> = 2.7:
La manera simple se vería así:
class MyFormatter(logging.Formatter):
default_fmt = logging.Formatter(''%(levelname)s in %(name)s: %(message)s'')
info_fmt = logging.Formatter(''%(message)s'')
def format(self, record):
if record.levelno == logging.INFO:
return self.info_fmt.format(record)
else:
return self.default_fmt.format(record)
Pero podrías hacerlo más genérico:
class VarFormatter(logging.Formatter):
default_formatter = logging.Formatter(''%(levelname)s in %(name)s: %(message)s'')
def __init__(self, formats):
""" formats is a dict { loglevel : logformat } """
self.formatters = {}
for loglevel in formats:
self.formatters[loglevel] = logging.Formatter(formats[loglevel])
def format(self, record):
formatter = self.formatters.get(record.levelno, self.default_formatter)
return formatter.format(record)
Utilicé un dict como entrada aquí, pero obviamente también puedes usar tuplas, ** kwargs, lo que sea que flote tu bote. Esto luego se usaría como:
formatter = VarFormatter({logging.INFO: ''[%(message)s]'',
logging.WARNING: ''warning: %(message)s''})
<... attach formatter to logger ...>
Esta es una adaptación de la respuesta de estani a la nueva implementación de logging.Formatter
que ahora se basa en estilos de formateo. El mío se basa en ''{''
formato de estilo ''{''
, pero se puede adaptar. Podría ser refinado para ser más general y permitir la selección de estilo de formato y mensajes personalizados como argumentos para __init__
, también.
class SpecialFormatter(logging.Formatter):
FORMATS = {logging.DEBUG : logging._STYLES[''{'']("{module} DEBUG: {lineno}: {message}"),
logging.ERROR : logging._STYLES[''{'']("{module} ERROR: {message}"),
logging.INFO : logging._STYLES[''{'']("{module}: {message}"),
''DEFAULT'' : logging._STYLES[''{'']("{module}: {message}")}
def format(self, record):
# Ugly. Should be better
self._style = self.FORMATS.get(record.levelno, self.FORMATS[''DEFAULT''])
return logging.Formatter.format(self, record)
hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
La solución anterior funciona con la versión 3.3.3. Sin embargo, con 3.3.4 obtienes el siguiente error.
FORMATS = { logging.DEBUG : logging._STYLES[''{'']("{module} DEBUG: {lineno}: {message}"),
TypeError: el objeto ''tuple'' no se puede llamar
Después de buscar en la clase de registro Lib / logging__init __. Py encontré que una estructura de datos ha cambiado de 3.3.3 a 3.3.4 que causa el problema
3.3.3
_STYLES = {
''%'': PercentStyle,
''{'': StrFormatStyle,
''$'': StringTemplateStyle
}
3.3.4
_STYLES = {
''%'': (PercentStyle, BASIC_FORMAT),
''{'': (StrFormatStyle, ''{levelname}:{name}:{message} AA''),
''$'': (StringTemplateStyle, ''${levelname}:${name}:${message} BB''),
}
La solución actualizada es por lo tanto
class SpecialFormatter(logging.Formatter):
FORMATS = {logging.DEBUG : logging._STYLES[''{''][0]("{module} DEBUG: {lineno}: {message}"),
logging.ERROR : logging._STYLES[''{''][0]("{module} ERROR: {message}"),
logging.INFO : logging._STYLES[''{''][0]("{module}: {message}"),
''DEFAULT'' : logging._STYLES[''{''][0]("{module}: {message}")}
def format(self, record):
# Ugly. Should be better
self._style = self.FORMATS.get(record.levelno, self.FORMATS[''DEFAULT''])
return logging.Formatter.format(self, record)
Sí, puedes hacer esto teniendo una clase de Formatter
personalizada:
class MyFormatter(logging.Formatter):
def format(self, record):
#compute s according to record.levelno
#for example, by setting self._fmt
#according to the levelno, then calling
#the superclass to do the actual formatting
return s
A continuación, adjunte una instancia de MyFormatter
a sus controladores.
Si solo desea omitir el formateo de ciertos niveles, puede hacer algo más simple que las otras respuestas como las siguientes:
class FormatterNotFormattingInfo(logging.Formatter):
def __init__(self, fmt = ''%(levelname)s:%(message)s''):
logging.Formatter.__init__(self, fmt)
def format(self, record):
if record.levelno == logging.INFO:
return record.getMessage()
return logging.Formatter.format(self, record)
Esto también tiene la ventaja de trabajar antes y después de la versión 3.2 al no usar variables internas como self._fmt ni self._style.
Y de nuevo, como respuesta JS, pero más compacto.
class SpecialFormatter(logging.Formatter):
FORMATS = {logging.DEBUG :"DBG: %(module)s: %(lineno)d: %(message)s",
logging.ERROR : "ERROR: %(message)s",
logging.INFO : "%(message)s",
''DEFAULT'' : "%(levelname)s: %(message)s"}
def format(self, record):
self._fmt = self.FORMATS.get(record.levelno, self.FORMATS[''DEFAULT''])
return logging.Formatter.format(self, record)
hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)