¿Se puede modificar el formato de registro de Python según el nivel de registro de mensajes?

Estoy usando el mecanismo de logging de Python para imprimir la salida en la pantalla. Podría hacer esto con las declaraciones de impresión, pero quiero permitir una granularidad mejorada para que el usuario desactive ciertos tipos de salida. 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 crea, ya que hay un gran número de sentencias if / else para determinar cómo se debe registrar la salida.

Sí, puedes hacer esto teniendo una clase personalizada de Formatter :

 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 

Luego adjunte una instancia de MyFormatter a sus manejadores.

Acabo de encontrarme con este problema y tuve problemas para rellenar los “agujeros” que quedan en el ejemplo anterior. Aquí hay una versión de trabajo más completa que utilicé. Esperemos 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 su script:

 fmt = MyFormatter() hdlr = logging.StreamHandler(sys.stdout) hdlr.setFormatter(fmt) logging.root.addHandler(hdlr) logging.root.setLevel(DEBUG) 

Edición 2:

El registro de Python3 ha cambiado un poco. Vea aquí para un enfoque de Python3.

Y de nuevo como respuesta JS pero más compacta.

 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) 

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 formato. 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 estilos 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) 

En lugar de confiar en estilos o campos internos, también puede crear un formateador que delega a otros formateadores según el record.levelno (u otro criterio). Esta es una solución ligeramente más limpia en mi humilde opinión. El código a continuación debería funcionar para cualquier versión de python> = 2.7:

La forma simple se vería algo como esto:

 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 dictado como entrada aquí, pero obviamente también podrías usar tuplas, ** kwargs, lo que sea que haga flotar tu bote. Esto entonces sería usado como:

 formatter = VarFormatter({logging.INFO: '[%(message)s]', logging.WARNING: 'warning: %(message)s'}) <... attach formatter to logger ...> 

La solución anterior funciona con la versión 3.3.3. Sin embargo, con 3.3.4 se obtiene 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 la 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) 

Si solo está buscando omitir el formateo de ciertos niveles, puede hacer algo más simple que las otras respuestas como la siguiente:

 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.