logging pintar - ¿Cómo puedo colorear la salida de registro de Python?




en turtle (21)

Hace algún tiempo, vi una aplicación Mono con salida de color, probablemente debido a su sistema de registro (porque todos los mensajes estaban estandarizados).

Ahora, Python tiene el módulo de logging , que le permite especificar muchas opciones para personalizar la salida. Entonces, estoy imaginando que algo similar sería posible con Python, pero no puedo descubrir cómo hacer esto en cualquier lugar.

¿Hay alguna manera de hacer que el módulo de logging Python se logging en color?

Lo que quiero (por ejemplo) errores en rojo, mensajes de depuración en azul o amarillo, etc.

Por supuesto, esto probablemente requeriría un terminal compatible (la mayoría de los terminales modernos son); pero podría retroceder a la salida de logging original si el color no es compatible.

¿Alguna idea de cómo puedo obtener una salida de color con el módulo de registro?


Answers

import logging
import sys

colors = {'pink': '\033[95m', 'blue': '\033[94m', 'green': '\033[92m', 'yellow': '\033[93m', 'red': '\033[91m',
      'ENDC': '\033[0m', 'bold': '\033[1m', 'underline': '\033[4m'}

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)


def str_color(color, data):
    return colors[color] + str(data) + colors['ENDC']

params = {'param1': id1, 'param2': id2}

logging.info('\nParams:' + str_color("blue", str(params)))`

Tengo dos presentaciones para agregar, una de las cuales colorea solo el mensaje (ColoredFormatter) y una de las que colorea la línea completa (ColorizingStreamHandler). Estos también incluyen más códigos de color ANSI que las soluciones anteriores.

Parte del contenido se ha obtenido (con modificaciones) de: La publicación anterior y http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .

Colorea solo el mensaje:

class ColoredFormatter(logging.Formatter):
    """Special custom formatter for colorizing log messages!"""

    BLACK = '\033[0;30m'
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    BROWN = '\033[0;33m'
    BLUE = '\033[0;34m'
    PURPLE = '\033[0;35m'
    CYAN = '\033[0;36m'
    GREY = '\033[0;37m'

    DARK_GREY = '\033[1;30m'
    LIGHT_RED = '\033[1;31m'
    LIGHT_GREEN = '\033[1;32m'
    YELLOW = '\033[1;33m'
    LIGHT_BLUE = '\033[1;34m'
    LIGHT_PURPLE = '\033[1;35m'
    LIGHT_CYAN = '\033[1;36m'
    WHITE = '\033[1;37m'

    RESET = "\033[0m"

    def __init__(self, *args, **kwargs):
        self._colors = {logging.DEBUG: self.DARK_GREY,
                        logging.INFO: self.RESET,
                        logging.WARNING: self.BROWN,
                        logging.ERROR: self.RED,
                        logging.CRITICAL: self.LIGHT_RED}
        super(ColoredFormatter, self).__init__(*args, **kwargs)

    def format(self, record):
        """Applies the color formats"""
        record.msg = self._colors[record.levelno] + record.msg + self.RESET
        return logging.Formatter.format(self, record)

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code

Colorea toda la línea:

class ColorizingStreamHandler(logging.StreamHandler):

    BLACK = '\033[0;30m'
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    BROWN = '\033[0;33m'
    BLUE = '\033[0;34m'
    PURPLE = '\033[0;35m'
    CYAN = '\033[0;36m'
    GREY = '\033[0;37m'

    DARK_GREY = '\033[1;30m'
    LIGHT_RED = '\033[1;31m'
    LIGHT_GREEN = '\033[1;32m'
    YELLOW = '\033[1;33m'
    LIGHT_BLUE = '\033[1;34m'
    LIGHT_PURPLE = '\033[1;35m'
    LIGHT_CYAN = '\033[1;36m'
    WHITE = '\033[1;37m'

    RESET = "\033[0m"

    def __init__(self, *args, **kwargs):
        self._colors = {logging.DEBUG: self.DARK_GREY,
                        logging.INFO: self.RESET,
                        logging.WARNING: self.BROWN,
                        logging.ERROR: self.RED,
                        logging.CRITICAL: self.LIGHT_RED}
        super(ColorizingStreamHandler, self).__init__(*args, **kwargs)

    @property
    def is_tty(self):
        isatty = getattr(self.stream, 'isatty', None)
        return isatty and isatty()

    def emit(self, record):
        try:
            message = self.format(record)
            stream = self.stream
            if not self.is_tty:
                stream.write(message)
            else:
                message = self._colors[record.levelno] + message + self.RESET
                stream.write(message)
            stream.write(getattr(self, 'terminator', '\n'))
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code

Ya sabía acerca de los escapes de color, los usé en mi mensaje de bash hace un tiempo. Gracias de cualquier manera.
Lo que quería era integrarlo con el módulo de registro, lo que finalmente hice después de un par de intentos y errores.
Esto es lo que termino con:

BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

#The background is set with 40 plus the number of the color, and the foreground with 30

#These are the sequences need to get colored ouput
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"

def formatter_message(message, use_color = True):
    if use_color:
        message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
    else:
        message = message.replace("$RESET", "").replace("$BOLD", "")
    return message

COLORS = {
    'WARNING': YELLOW,
    'INFO': WHITE,
    'DEBUG': BLUE,
    'CRITICAL': YELLOW,
    'ERROR': RED
}

class ColoredFormatter(logging.Formatter):
    def __init__(self, msg, use_color = True):
        logging.Formatter.__init__(self, msg)
        self.use_color = use_color

    def format(self, record):
        levelname = record.levelname
        if self.use_color and levelname in COLORS:
            levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
            record.levelname = levelname_color
        return logging.Formatter.format(self, record)

Y para usarlo, crea tu propio Logger:

# Custom logger class with multiple destinations
class ColoredLogger(logging.Logger):
    FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s]  %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)"
    COLOR_FORMAT = formatter_message(FORMAT, True)
    def __init__(self, name):
        logging.Logger.__init__(self, name, logging.DEBUG)                

        color_formatter = ColoredFormatter(self.COLOR_FORMAT)

        console = logging.StreamHandler()
        console.setFormatter(color_formatter)

        self.addHandler(console)
        return


logging.setLoggerClass(ColoredLogger)

Por si alguien más lo necesita.

Tenga cuidado si está utilizando más de un registrador o controlador: ColoredFormatter está cambiando el objeto de registro, que se pasa a otros controladores o se propaga a otros registradores. Si ha configurado los registradores de archivos, etc., es probable que no quiera tener los colores en los archivos de registro. Para evitar eso, probablemente sea mejor crear una copia del record con copy.copy() antes de manipular el atributo levelname, o restablecer el levelname al valor anterior, antes de devolver la cadena con formato (crédito a Michael en los comentarios).


Aquí hay una solución que debería funcionar en cualquier plataforma. Si no solo me lo dice y lo actualizo.

Cómo funciona: en la plataforma que admite ANSI escapes los está utilizando (no Windows) y en Windows usa llamadas API para cambiar los colores de la consola.

La secuencia de comandos piratea el método logging.StreamHandler.emit de la biblioteca estándar y le agrega un contenedor.

TestColorer.py

# Usage: add Colorer.py near you script and import it.
import logging
import Colorer

logging.warn("a warning")
logging.error("some error")
logging.info("some info")

Colorer.py

#!/usr/bin/env python
# encoding: utf-8
import logging
# now we patch Python code to add color support to logging.StreamHandler
def add_coloring_to_emit_windows(fn):
        # add methods we need to the class
    def _out_handle(self):
        import ctypes
        return ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
    out_handle = property(_out_handle)

    def _set_color(self, code):
        import ctypes
        # Constants from the Windows API
        self.STD_OUTPUT_HANDLE = -11
        hdl = ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
        ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, code)

    setattr(logging.StreamHandler, '_set_color', _set_color)

    def new(*args):
        FOREGROUND_BLUE      = 0x0001 # text color contains blue.
        FOREGROUND_GREEN     = 0x0002 # text color contains green.
        FOREGROUND_RED       = 0x0004 # text color contains red.
        FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
        FOREGROUND_WHITE     = FOREGROUND_BLUE|FOREGROUND_GREEN |FOREGROUND_RED
       # winbase.h
        STD_INPUT_HANDLE = -10
        STD_OUTPUT_HANDLE = -11
        STD_ERROR_HANDLE = -12

        # wincon.h
        FOREGROUND_BLACK     = 0x0000
        FOREGROUND_BLUE      = 0x0001
        FOREGROUND_GREEN     = 0x0002
        FOREGROUND_CYAN      = 0x0003
        FOREGROUND_RED       = 0x0004
        FOREGROUND_MAGENTA   = 0x0005
        FOREGROUND_YELLOW    = 0x0006
        FOREGROUND_GREY      = 0x0007
        FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.

        BACKGROUND_BLACK     = 0x0000
        BACKGROUND_BLUE      = 0x0010
        BACKGROUND_GREEN     = 0x0020
        BACKGROUND_CYAN      = 0x0030
        BACKGROUND_RED       = 0x0040
        BACKGROUND_MAGENTA   = 0x0050
        BACKGROUND_YELLOW    = 0x0060
        BACKGROUND_GREY      = 0x0070
        BACKGROUND_INTENSITY = 0x0080 # background color is intensified.     

        levelno = args[1].levelno
        if(levelno>=50):
            color = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY 
        elif(levelno>=40):
            color = FOREGROUND_RED | FOREGROUND_INTENSITY
        elif(levelno>=30):
            color = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
        elif(levelno>=20):
            color = FOREGROUND_GREEN
        elif(levelno>=10):
            color = FOREGROUND_MAGENTA
        else:
            color =  FOREGROUND_WHITE
        args[0]._set_color(color)

        ret = fn(*args)
        args[0]._set_color( FOREGROUND_WHITE )
        #print "after"
        return ret
    return new

def add_coloring_to_emit_ansi(fn):
    # add methods we need to the class
    def new(*args):
        levelno = args[1].levelno
        if(levelno>=50):
            color = '\x1b[31m' # red
        elif(levelno>=40):
            color = '\x1b[31m' # red
        elif(levelno>=30):
            color = '\x1b[33m' # yellow
        elif(levelno>=20):
            color = '\x1b[32m' # green 
        elif(levelno>=10):
            color = '\x1b[35m' # pink
        else:
            color = '\x1b[0m' # normal
        args[1].msg = color + args[1].msg +  '\x1b[0m'  # normal
        #print "after"
        return fn(*args)
    return new

import platform
if platform.system()=='Windows':
    # Windows does not support ANSI escapes and we are using API calls to set the console color
    logging.StreamHandler.emit = add_coloring_to_emit_windows(logging.StreamHandler.emit)
else:
    # all non-Windows platforms are supporting ANSI escapes so we use them
    logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
    #log = logging.getLogger()
    #log.addFilter(log_filter())
    #//hdlr = logging.StreamHandler()
    #//hdlr.setFormatter(formatter())

Actualicé el ejemplo de las etiquetas de soporte de airmind para el primer plano y el fondo. Simplemente use las variables de color $ BLACK - $ WHITE en la cadena del formateador de registro. Para establecer el fondo solo use $ BG-BLACK - $ BG-WHITE.

import logging

BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

COLORS = {
    'WARNING'  : YELLOW,
    'INFO'     : WHITE,
    'DEBUG'    : BLUE,
    'CRITICAL' : YELLOW,
    'ERROR'    : RED,
    'RED'      : RED,
    'GREEN'    : GREEN,
    'YELLOW'   : YELLOW,
    'BLUE'     : BLUE,
    'MAGENTA'  : MAGENTA,
    'CYAN'     : CYAN,
    'WHITE'    : WHITE,
}

RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ  = "\033[1m"

class ColorFormatter(logging.Formatter):

    def __init__(self, *args, **kwargs):
        # can't do super(...) here because Formatter is an old school class
        logging.Formatter.__init__(self, *args, **kwargs)

    def format(self, record):
        levelname = record.levelname
        color     = COLOR_SEQ % (30 + COLORS[levelname])
        message   = logging.Formatter.format(self, record)
        message   = message.replace("$RESET", RESET_SEQ)\
                           .replace("$BOLD",  BOLD_SEQ)\
                           .replace("$COLOR", color)
        for k,v in COLORS.items():
            message = message.replace("$" + k,    COLOR_SEQ % (v+30))\
                             .replace("$BG" + k,  COLOR_SEQ % (v+40))\
                             .replace("$BG-" + k, COLOR_SEQ % (v+40))
        return message + RESET_SEQ

logging.ColorFormatter = ColorFormatter

Así que ahora puedes simplemente hacer lo siguiente en tu archivo de configuración:

[formatter_colorFormatter]
class=logging.ColorFormatter
format= $COLOR%(levelname)s $RESET %(asctime)s $BOLD$COLOR%(name)s$RESET %(message)s

Hace años escribí un controlador de flujo de color para mi propio uso. Luego encontré esta página y encontré una colección de fragmentos de código que las personas están copiando / pegando :-(. Mi controlador de secuencias actualmente solo funciona en UNIX (Linux, Mac OS X) pero la ventaja es que está disponible en PyPI (y GitHub ) y es muy sencillo de usar. También tiene un modo de sintaxis Vim :-). En el futuro podría extenderlo para que funcione en Windows.

Para instalar el paquete:

$ pip install coloredlogs

Para confirmar que funciona:

$ coloredlogs --demo

Para comenzar con su propio código:

$ python
> import coloredlogs, logging
> coloredlogs.install()
> logging.info("It works!")
2014-07-30 21:21:26 peter-macbook root[7471] INFO It works!

El formato de registro predeterminado que se muestra en el ejemplo anterior contiene la fecha, la hora, el nombre de host, el nombre del registrador, el PID, el nivel de registro y el mensaje de registro. Esto es lo que parece en la práctica:



Actualización : como esta es una picazón que he querido rascar durante tanto tiempo, seguí adelante y escribí una biblioteca para personas perezosas como yo, que solo quieren formas simples de hacer las cosas: zenlog

Colorlog es excelente para esto. Está disponible en PyPI (y, por lo tanto, se puede instalar a través de pip install colorlog ) y se mantiene activamente .

Aquí hay un fragmento rápido para copiar y pegar para configurar el registro e imprimir mensajes de registro de aspecto decente:

import logging
LOG_LEVEL = logging.DEBUG
LOGFORMAT = "  %(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s"
from colorlog import ColoredFormatter
logging.root.setLevel(LOG_LEVEL)
formatter = ColoredFormatter(LOGFORMAT)
stream = logging.StreamHandler()
stream.setLevel(LOG_LEVEL)
stream.setFormatter(formatter)
log = logging.getLogger('pythonConfig')
log.setLevel(LOG_LEVEL)
log.addHandler(stream)

log.debug("A quirky message only developers care about")
log.info("Curious users might want to know this")
log.warn("Something is wrong and any user should be informed")
log.error("Serious stuff, this is red for a reason")
log.critical("OH NO everything is on fire")

Salida:


Puede importar el módulo de registro de color y usar su ColoredFormatter para colorear los mensajes de registro.

Ejemplo

Boilerplate para el módulo principal:

import logging
import os
import sys
try:
    import colorlog
except ImportError:
    pass

def setup_logging():
    root = logging.getLogger()
    root.setLevel(logging.DEBUG)
    format      = '%(asctime)s - %(levelname)-8s - %(message)s'
    date_format = '%Y-%m-%d %H:%M:%S'
    if 'colorlog' in sys.modules and os.isatty(2):
        cformat = '%(log_color)s' + format
        f = colorlog.ColoredFormatter(cformat, date_format,
              log_colors = { 'DEBUG'   : 'reset',       'INFO' : 'reset',
                             'WARNING' : 'bold_yellow', 'ERROR': 'bold_red',
                             'CRITICAL': 'bold_red' })
    else:
        f = logging.Formatter(format, date_format)
    ch = logging.StreamHandler()
    ch.setFormatter(f)
    root.addHandler(ch)

setup_logging()
log = logging.getLogger(__name__)

El código solo habilita los colores en los mensajes de registro, si el módulo de registro de color está instalado y si la salida realmente va a un terminal. Esto evita que las secuencias de escape se escriban en un archivo cuando se redirige la salida del registro.

Además, se configura un esquema de color personalizado que es más adecuado para terminales con fondo oscuro.

Algunos ejemplos de llamadas de registro:

log.debug   ('Hello Debug')
log.info    ('Hello Info')
log.warn    ('Hello Warn')
log.error   ('Hello Error')
log.critical('Hello Critical')

Salida:


El problema con el que tuve problemas fue configurar correctamente el formateador:

class ColouredFormatter(logging.Formatter):    
    def __init__(self, msg):
        logging.Formatter.__init__(self, msg)
        self._init_colour = _get_colour()

    def close(self):
        # restore the colour information to what it was
        _set_colour(self._init_colour)

    def format(self, record):        
        # Add your own colourer based on the other examples
        _set_colour( LOG_LEVEL_COLOUR[record.levelno] )
        return logging.Formatter.format(self, record)         

def init():
    # Set up the formatter. Needs to be first thing done.
    rootLogger = logging.getLogger()
    hdlr = logging.StreamHandler()
    fmt = ColouredFormatter('%(message)s')
    hdlr.setFormatter(fmt)
    rootLogger.addHandler(hdlr)

Y luego usar:

import coloured_log
import logging

coloured_log.init()
logging.info("info")    
logging.debug("debug")    

coloured_log.close()    # restore colours

Otro remix menor del enfoque de Airmind que mantiene todo en una clase:

class ColorFormatter(logging.Formatter):
  FORMAT = ("[$BOLD%(name)-20s$RESET][%(levelname)-18s]  "
            "%(message)s "
            "($BOLD%(filename)s$RESET:%(lineno)d)")

  BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

  RESET_SEQ = "\033[0m"
  COLOR_SEQ = "\033[1;%dm"
  BOLD_SEQ = "\033[1m"

  COLORS = {
    'WARNING': YELLOW,
    'INFO': WHITE,
    'DEBUG': BLUE,
    'CRITICAL': YELLOW,
    'ERROR': RED
  }

  def formatter_msg(self, msg, use_color = True):
    if use_color:
      msg = msg.replace("$RESET", self.RESET_SEQ).replace("$BOLD", self.BOLD_SEQ)
    else:
      msg = msg.replace("$RESET", "").replace("$BOLD", "")
    return msg

  def __init__(self, use_color=True):
    msg = self.formatter_msg(self.FORMAT, use_color)
    logging.Formatter.__init__(self, msg)
    self.use_color = use_color

  def format(self, record):
    levelname = record.levelname
    if self.use_color and levelname in self.COLORS:
      fore_color = 30 + self.COLORS[levelname]
      levelname_color = self.COLOR_SEQ % fore_color + levelname + self.RESET_SEQ
      record.levelname = levelname_color
    return logging.Formatter.format(self, record)

Para usar adjuntar el formateador a un controlador, algo como:

handler.setFormatter(ColorFormatter())
logger.addHandler(handler)

Modifiqué el ejemplo original proporcionado por Sorin y subclasificé StreamHandler a ColorizedConsoleHandler.

La desventaja de su solución es que modifica el mensaje, y como eso está modificando el mensaje de registro real, cualquier otro manejador recibirá el mensaje modificado también.

Esto resultó en archivos de registro con códigos de colores en nuestro caso porque usamos múltiples registradores.

La siguiente clase solo funciona en plataformas compatibles con ansi, pero debería ser trivial agregarle los códigos de colores de Windows.

import copy
import logging


class ColoredConsoleHandler(logging.StreamHandler):
    def emit(self, record):
        # Need to make a actual copy of the record
        # to prevent altering the message for other loggers
        myrecord = copy.copy(record)
        levelno = myrecord.levelno
        if(levelno >= 50):  # CRITICAL / FATAL
            color = '\x1b[31m'  # red
        elif(levelno >= 40):  # ERROR
            color = '\x1b[31m'  # red
        elif(levelno >= 30):  # WARNING
            color = '\x1b[33m'  # yellow
        elif(levelno >= 20):  # INFO
            color = '\x1b[32m'  # green
        elif(levelno >= 10):  # DEBUG
            color = '\x1b[35m'  # pink
        else:  # NOTSET and anything else
            color = '\x1b[0m'  # normal
        myrecord.msg = color + str(myrecord.msg) + '\x1b[0m'  # normal
        logging.StreamHandler.emit(self, myrecord)

Solución rápida y sucia para niveles de registro predefinidos y sin definir una nueva clase.

logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))

Una solución más, con los colores de ZetaSyanthis:

def config_log(log_level):

    def set_color(level, code):
        level_fmt = "\033[1;" + str(code) + "m%s\033[1;0m" 
        logging.addLevelName( level, level_fmt % logging.getLevelName(level) )

    std_stream = sys.stdout
    isatty = getattr(std_stream, 'isatty', None)
    if isatty and isatty():
        levels = [logging.DEBUG, logging.CRITICAL, logging.WARNING, logging.ERROR]
        for idx, level in enumerate(levels):
            set_color(level, 30 + idx )
        set_color(logging.DEBUG, 0)
    logging.basicConfig(stream=std_stream, level=log_level)

Llámalo una vez desde tu __main__función. Tengo algo como esto allí:

options, arguments = p.parse_args()
log_level = logging.DEBUG if options.verbose else logging.WARNING
config_log(log_level)

También verifica que la salida sea una consola, de lo contrario no se utilizan colores.


Una herramienta simple pero muy flexible para colorear CUALQUIER texto terminal es ' colout '.

pip install colout
myprocess | colout REGEX_WITH_GROUPS color1,color2...

Donde cualquier texto en la salida de 'myprocess' que coincida con el grupo 1 de la expresión regular se coloreará con color1, grupo 2 con color2, etc.

Por ejemplo:

tail -f /var/log/mylogfile | colout '^(\w+ \d+ [\d:]+)|(\w+\.py:\d+ .+\(\)): (.+)$' white,black,cyan bold,bold,normal

es decir, el primer grupo de expresiones regulares (parens) coincide con la fecha inicial en el archivo de registro, el segundo grupo coincide con un nombre de archivo de python, número de línea y nombre de la función, y el tercer grupo coincide con el mensaje de registro que viene después de eso. También utilizo una secuencia paralela de "negrita / normal", así como la secuencia de colores. Esto se ve como

Tenga en cuenta que las líneas o partes de líneas que no coinciden con mi regex todavía tienen eco, así que esto no es como 'grep --color' - nada se filtra fuera de la salida.

Obviamente, esto es lo suficientemente flexible como para que pueda usarlo con cualquier proceso, no solo siguiendo los archivos de registro. Por lo general, solo levanto un nuevo regex sobre la marcha cada vez que quiero colorear algo. Por esta razón, prefiero colout a cualquier herramienta personalizada para colorear archivos de log, porque solo necesito aprender una herramienta, independientemente de lo que esté coloreando: registro, salida de prueba, resaltado de sintaxis, fragmentos de código en el terminal, etc.

También evita el hecho de volcar códigos ANSI en el archivo de registro, lo cual, en mi humilde opinión, es una mala idea, porque romperá cosas como grepping para patrones en el archivo de registro a menos que siempre recuerde que coincida con los códigos ANSI en su regex grep.


import logging

logging.basicConfig(filename="f.log" filemode='w', level=logging.INFO,
                    format = "%(logger_name)s %(color)s  %(message)s %(endColor)s")


class Logger(object):
    __GREEN = "\033[92m"
    __RED = '\033[91m'
    __ENDC = '\033[0m'

    def __init__(self, name):
        self.logger = logging.getLogger(name)
        self.extra={'logger_name': name, 'endColor': self.__ENDC, 'color': self.__GREEN}


    def info(self, msg):
        self.extra['color'] = self.__GREEN
        self.logger.info(msg, extra=self.extra)

    def error(self, msg):
        self.extra['color'] = self.__RED
        self.logger.error(msg, extra=self.extra)

Uso

Logger("File Name").info("This shows green text")


Si bien las otras soluciones parecen estar bien, tienen algunos problemas. Algunos colorean las líneas completas, lo que algunas veces no se desea, y otros omiten cualquier configuración que pueda tener todos juntos. La solución a continuación no afecta nada más que el mensaje en sí.

Código

class ColoredFormatter(logging.Formatter):
    def format(self, record):
        if record.levelno == logging.WARNING:
            record.msg = '\033[93m%s\033[0m' % record.msg
        elif record.levelno == logging.ERROR:
            record.msg = '\033[91m%s\033[0m' % record.msg
        return logging.Formatter.format(self, record)

Ejemplo

logger = logging.getLogger('mylogger')
handler = logging.StreamHandler()

log_format = '[%(asctime)s]:%(levelname)-7s:%(message)s'
time_format = '%H:%M:%S'
formatter = ColoredFormatter(log_format, datefmt=time_format)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.warn('this should be yellow')
logger.error('this should be red')

Salida

[17:01:36]:WARNING:this should be yellow
[17:01:37]:ERROR  :this should be red

Como puede ver, todo lo demás aún se imprime y permanece en su color inicial. Si desea cambiar algo más que el mensaje, simplemente puede pasar los códigos de color log_formaten el ejemplo.


Hay toneladas de respuestas. Pero ninguno habla de decoradores. Así que aquí está la mía.

Porque es mucho más simple.

No es necesario importar nada, ni escribir ninguna subclase:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import logging


NO_COLOR = "\33[m"
RED, GREEN, ORANGE, BLUE, PURPLE, LBLUE, GREY = \
    map("\33[%dm".__mod__, range(31, 38))

logging.basicConfig(format="%(message)s", level=logging.DEBUG)
logger = logging.getLogger(__name__)

# the decorator to apply on the logger methods info, warn, ...
def add_color(logger_method, color):
  def wrapper(message, *args, **kwargs):
    return logger_method(
      # the coloring is applied here.
      color+message+NO_COLOR,
      *args, **kwargs
    )
  return wrapper

for level, color in zip((
  "info", "warn", "error", "debug"), (
  GREEN, ORANGE, RED, BLUE
)):
  setattr(logger, level, add_color(getattr(logger, level), color))

# this is displayed in red.
logger.error("Launching %s." % __file__)

Esto establece los errores en rojo, los mensajes de depuración en azul, y así sucesivamente. Como se pregunta en la pregunta.

Incluso podríamos adaptar la envoltura para tomar un argumento de color para establecer dinámicamente el color del mensaje usando logger.debug("message", color=GREY)

EDIT: Así que aquí está el decorador adaptado para establecer colores en tiempo de ejecución:

def add_color(logger_method, _color):
  def wrapper(message, *args, **kwargs):
    color = kwargs.pop("color", _color)
    if isinstance(color, int):
      color = "\33[%dm" % color
    return logger_method(
      # the coloring is applied here.
      color+message+NO_COLOR,
      *args, **kwargs
    )
  return wrapper

# blah blah, apply the decorator...

# this is displayed in red.
logger.error("Launching %s." % __file__)
# this is displayed in blue
logger.error("Launching %s." % __file__, color=34)
# and this, in grey
logger.error("Launching %s." % __file__, color=GREY)

Aquí está mi solución:

2017-09-29 00:32:23,434 - main - DEBUG - app has started
2017-09-29 00:32:23,434 - main - INFO - Logging to 'app.log' in the script dir
2017-09-29 00:32:23,435 - main - WARNING - This is my last warning, take heed
2017-09-29 00:32:23,435 - main - ERROR - This is an error
2017-09-29 00:32:23,435 - main - CRITICAL - He's dead, Jim
2017-09-29 00:32:23,435 - main.sub_module - ERROR - Hello from the sub module

Bueno, supongo que también podría agregar mi variación del registrador de colores.

Esto no es nada sofisticado, pero es muy simple de usar y no cambia el objeto de registro, por lo que evita el registro de las secuencias de escape ANSI en un archivo de registro si se usa un controlador de archivos. No afecta el formato del mensaje de registro.

Si ya está utilizando el formateador del módulo de registro , todo lo que tiene que hacer para obtener nombres de nivel de color es reemplazar el manejador de abogados Formatter por el ColoredFormatter. Si está registrando una aplicación completa, solo necesita hacer esto para el registrador de nivel superior.

colored_log.py

class ColouredFormatter(logging.Formatter):
    RESET = '\x1B[0m'
    RED = '\x1B[31m'
    YELLOW = '\x1B[33m'
    BRGREEN = '\x1B[01;32m'  # grey in solarized for terminals

    def format(self, record, colour=False):
        message = super().format(record)

        if not colour:
            return message

        level_no = record.levelno
        if level_no >= logging.CRITICAL:
            colour = self.RED
        elif level_no >= logging.ERROR:
            colour = self.RED
        elif level_no >= logging.WARNING:
            colour = self.YELLOW
        elif level_no >= logging.INFO:
            colour = self.RESET
        elif level_no >= logging.DEBUG:
            colour = self.BRGREEN
        else:
            colour = self.RESET

        message = colour + message + self.RESET

        return message


class ColouredHandler(logging.StreamHandler):
    def __init__(self, stream=sys.stdout):
        super().__init__(stream)

    def format(self, record, colour=False):
        if not isinstance(self.formatter, ColouredFormatter):
            self.formatter = ColouredFormatter()

        return self.formatter.format(record, colour)

    def emit(self, record):
        stream = self.stream
        try:
            msg = self.format(record, stream.isatty())
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)


h = ColouredHandler()
h.formatter = ColouredFormatter('{asctime} {levelname:8} {message}', '%Y-%m-%d %H:%M:%S', '{')
logging.basicConfig(level=logging.DEBUG, handlers=[h])

Ejemplo de uso

app.py

#!/usr/bin/env python

from copy import copy
from logging import Formatter

MAPPING = {
    'DEBUG'   : 37, # white
    'INFO'    : 36, # cyan
    'WARNING' : 33, # yellow
    'ERROR'   : 31, # red
    'CRITICAL': 41, # white on red bg
}

PREFIX = '\033['
SUFFIX = '\033[0m'

class ColoredFormatter(Formatter):

    def __init__(self, patern):
        Formatter.__init__(self, patern)

    def format(self, record):
        colored_record = copy(record)
        levelname = colored_record.levelname
        seq = MAPPING.get(levelname, 37) # default white
        colored_levelname = ('{0}{1}m{2}{3}') \
            .format(PREFIX, seq, levelname, SUFFIX)
        colored_record.levelname = colored_levelname
        return Formatter.format(self, colored_record)

sub_module.py

#!/usr/bin/env python

import logging
from colored_log import ColoredFormatter

# Create top level logger
log = logging.getLogger("main")

# Add console handler using our custom ColoredFormatter
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
cf = ColoredFormatter("[%(name)s][%(levelname)s]  %(message)s (%(filename)s:%(lineno)d)")
ch.setFormatter(cf)
log.addHandler(ch)

# Add file handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.DEBUG)
ff = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(ff)
log.addHandler(fh)

# Set log level
log.setLevel(logging.DEBUG)

# Log some stuff
log.debug("app has started")
log.info("Logging to 'app.log' in the script dir")
log.warning("This is my last warning, take heed")
log.error("This is an error")
log.critical("He's dead, Jim")

# Import a sub-module 
import sub_module

Resultados

Salida de terminal

contenido de app.log

#!/usr/bin/env python

import logging
log = logging.getLogger('main.sub_module')

log.debug("Hello from the sub module")

Por supuesto, puede obtener todo lo que quiera con el formato del terminal y las salidas de archivos de registro. Sólo el nivel de registro será coloreado.

Espero que alguien lo encuentre útil y no sea mucho más de lo mismo. :)

Los archivos de ejemplo de Python se pueden descargar de este GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd


Lea https://web.archive.org/web/20090207081238/http://faqts.com/knowledge_base/view.phtml/aid/2609/fid/378 , que puede ayudarlo más:

Intente la función dormir en el módulo de tiempo.

import time
time.sleep(60)

Y ponga esto en un bucle while y una instrucción solo se ejecutará en el minuto ... Eso le permite ejecutar una instrucción en intervalos predefinidos sin importar cuánto demore el comando (siempre que tome menos de un minuto o 5 o 60 minutos). o lo que sea que configures para) Por ejemplo, quería ejecutar un ping una vez por minuto. Si solo time.sleep(60) o time.sleep(45) incluso, el ping no siempre llevará la misma cantidad de tiempo. Aquí está el código :)

time.sleep(time.localtime(time.time())[5])

El [5] simplemente extrae los segundos del valor de retorno de time.localtime() .

Lo bueno de time.sleep es que admite números de punto flotante.

import time
time.sleep(0.1) 

http://python.org/doc/current/lib/module-time.html





python logging colors