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


353

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

Ahora, Python tiene el loggingmódulo, que le permite especificar muchas opciones para personalizar la salida. Entonces, imagino que algo similar sería posible con Python, pero no puedo encontrar cómo hacer esto en ningún lado.

¿Hay alguna forma de hacer que la loggingsalida del módulo Python sea 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 lo son); pero podría recurrir a la loggingsalida original si el color no es compatible.

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


1
Debe especificar que desea una solución multiplataforma, tanto Linux como Windows.
sorin

1
Relacionado si usa Eclipse / PyDev: Colorear registros en la consola de eclipse
Tobias Kienzler

55
Quizás también pueda usar el registro de colores
Ehtesh Choudhury

55
También puede probar chromalog que escribí para admitir todos los sistemas operativos y las versiones de Python (2.7 y 3. *)
antes

1
Las soluciones que realmente descargan códigos ANSI en el archivo de registro son una mala idea, te atraparán cuando estés buscando algo dentro de seis meses, pero olvídate de permitir los caracteres ANSI en tu patrón de expresiones regulares. Hay algunas soluciones a continuación que agregan el color a medida que ve el registro, en lugar de como está escrito el registro ...
Jonathan Hartley

Respuestas:


192

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

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, cree su propio registrador:

# 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: ColoredFormatterestá 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., probablemente no desee tener los colores en los archivos de registro. Para evitar eso, probablemente sea mejor simplemente crear una copia de recordwith copy.copy()antes de manipular el atributo levelname, o restablecer el nivel al valor anterior, antes de devolver la cadena formateada (crédito a Michael en los comentarios).


¿Dónde se define AMARILLO, BLANCO, AZUL, etc.?
Swaroop CH

1
@Swaroop: esos son códigos de escape ANSI, que puede leer, buscar en Google, o encontrar aquí: en.wikipedia.org/wiki/ANSI_escape_code , o alternativamente pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
Brian M Hunt

53
No creo que deba crear una subclase de registrador solo por esto: su respuesta está bien en cuanto a crear un especializado Formattery especificar su uso en un StreamHandler. Pero no hay necesidad de una subclase de registrador. De hecho, el uso de una clase de registrador agrega un controlador a cada registrador creado, que no es lo que normalmente desea.
Vinay Sajip


66
Una nota al margen para ColoredFormatter. Está cambiando el objeto de registro, que se pasa a otros manejadores o se propaga a otros registradores. Si ha configurado los registradores de archivos, etc., probablemente no desee tener los colores en los archivos de registro. Para evitar eso, probablemente sea mejor, simplemente crear una copia de recordwith copy.copy()antes de manipular el atributo levelname, o restablecer el nivel al valor anterior, antes de devolver la cadena formateada.
Michael

149

Hace años escribí un controlador de flujo de color para mi propio uso. Luego me encontré con esta página y encontré una colección de fragmentos de código que la gente está copiando / pegando :-(. Mi controlador de flujo actualmente solo funciona en UNIX (Linux, Mac OS X) pero la ventaja es que está disponible en PyPI (y GitHub ) y es muy fácil 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. Así es como se ve en la práctica:

Captura de pantalla de salida de colorlogs

NOTA: Al usar Git Bash con MinTTY

Git Bash en Windows tiene algunas peculiaridades documentadas: Winpty y Git Bash

Para los códigos de escape ANSI y para la reescritura y animaciones de caracteres de estilo ncurses, debe prefijar los comandos con winpty.

$ winpty coloredlogs --demo
$ winpty python your_colored_logs_script.py

2
Curiosamente , ¡solo iba a agregar un enlace a " pypi.python.org/pypi/coloredlogs/0.4.7 " en este hilo!
Iosu S.

1
Por alguna razón sigo obteniendo AttributeError: 'module' object has no attribute 'install'cuando uso coloredlogs.install(). ¿Puedes confirmar eso con la última versión?
con-f-use

11
Esto se ve hermoso. Desafortunadamente, rompe muchas cosas; en particular, anula las llamadas a logging.basicConfig. Esto hace que sea imposible usar un formateador personalizado, por ejemplo.
Clément

@ Clément: Dos preguntas (superpuestas): (1) ¿Qué quiere decir exactamente con "anular llamadas a logging.basicConfig" y (2) cuál sería la alternativa? Tanto logging.basicConfig()y de coloredlogs.install()instalar un controlador de flujo que los registros a la consola, así que sin "vaciamiento" que obtendrían los mensajes duplicados ...
Xolox

Esperaba magia para (1) o (más razonablemente) una forma de saber coloredlogs.installqué formato usar, como en el colorlogpaquete.
Clément

74

Aquí hay una solución que debería funcionar en cualquier plataforma. Si no me lo dice, lo actualizaré.

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

El script piratea el método logging.StreamHandler.emit de la biblioteca estándar agregando 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())

3
Escribí una clase StreamHandler basada en esto, vea gist.github.com/mooware/a1ed40987b6cc9ab9c65 .
mooware

2
esto funcionó para mí! línea 90: debería ser args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
Rasika Perera

Me gusta esta solución usándolo actualmente. Veo que hay un atributo _set_color, ¿hay alguna manera de hacer esto para un mensaje de registro específico? editar , oh, mira, eso es solo un parche para máquinas Windows. Sería bueno agregar personalizado para diferentes casos de uso.
brizz

+1 para color ANSI. ¡En xterm incluso puede obtener 256 colores a la vez y puede definir la paleta dinámicamente! Sin embargo, tenga en cuenta que todas las llamadas a las funciones de registro deben estar dentro de una definición de función para evitar posibles problemas de bloqueo de importación al iniciar sesión fuera de una definición de función . Su código se ve mayormente bueno; solo ese poquito TestColorer.pyme preocupa.
personal_cloud

Esto da como resultado códigos de color al principio y al final de los mensajes de registro en los archivos de registro reales.
MehmedB

74

Actualización : Debido a que esta es una picazón que he tenido la intención de 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 pip install colorlog) y se mantiene activamente .

Aquí hay un fragmento rápido de 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:

Salida de colorlog


44
Gran respuesta; +1. Sin embargo, el ejemplo de código podría recortarse (¿son setLevelrealmente necesarias tres llamadas ?)
Clément

1
Esperaba encontrar una respuesta como esta si leía las respuestas lo suficiente. ☺ Espero que @airmind considere hacer de esta la respuesta aceptada, para que las futuras personas inteligentes en el trabajo puedan encontrar la que parece ser la mejor biblioteca con holgazanería óptima. 😉
Michael Scheper

Acabo de votar esto para los ejemplos de mensajes de OUTPUT ^^
Agustin Barrachina

69

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

@ spiderplant0 registro de importación; # pegue el código de @ABC; pruébelo con logging.warning ('esto es una prueba'). Verá la parte mayúscula de "ADVERTENCIA: esta es una prueba" coloreada. Funciona solo en Linux por cierto
Riccardo Galli

3
Como solo el nombre del nivel de registro está coloreado, debe asegurarse de que el nombre del nivel de registro esté impreso en la consola. Esto no sucede fuera de la caja para mí. Algo en este sentido ayudará: logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')donde, por supuesto, %(levelnames)ses importante.
Sebastian

44
La solución más simple y limpia para aplicar y comprender.
F. Santiago

1
Solo inténtalo en la consola de Linux. echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again". La -eopción echo interpreta "\ 033" como forma octal del símbolo de escape ASCII. Este símbolo especial hace que algunos terminales compatibles interpreten los caracteres posteriores (a char minclusive) como comandos especiales. es.wikipedia.org/wiki/ANSI_escape_code
eugene-bright

1
Mejora menor: ponga este código dentro if sys.sdterr.isatty():. En este caso, si redirige la salida al archivo, el archivo no contendrá estos caracteres de escape.
lesnik

36

Código 2020, no se requieren paquetes adicionales, Python 3

Define una clase

import logging

class CustomFormatter(logging.Formatter):
    """Logging Formatter to add colors and count warning / errors"""

    grey = "\x1b[38;21m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

Registrador de instancias

# create logger with 'spam_application'
logger = logging.getLogger("My_app")
logger.setLevel(logging.DEBUG)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

ch.setFormatter(CustomFormatter())

logger.addHandler(ch)

¡Y use!

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

Resultado ingrese la descripción de la imagen aquí

El esquema a todo color ingrese la descripción de la imagen aquí

Para ventanas

Esta solución funciona en Mac OS, terminales IDE. Parece que el símbolo del sistema de la ventana no tiene colores por defecto. Aquí hay instrucciones sobre cómo habilitarlas, que no he probado https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/


1
Ejecuté la prueba (python 3.7, windows), pero el registro no muestra colores:←[38;21m2019-11-12 19:29:50,994 - My_app - DEBUG - debug message (test_colored_log.py:43)←[0m ←[38;21m2019-11-12 19:29:50,994 - My_app - INFO - info message (test_colored_log.py:44)←[0m ←[33;21m2019-11-12 19:29:50,994 - My_app - WARNING - warning message (test_colored_log.py:45)←[0m ←[31;21m2019-11-12 19:29:50,994 - My_app - ERROR - error message (test_colored_log.py:46)←[0m ←[31;1m2019-11-12 19:29:50,994 - My_app - CRITICAL - critical message (test_colored_log.py:47)←[0m
constructor

Esto no funciona desafortunadamente.
Joe

2
Me gustó tanto esta respuesta que hice un repositorio , con algunos incrementos y una hoja de trucos de colores ansi.
Teodoro

@constructor, ¿dónde lo ejecutas? Consola IDE? terminal de windows?
Sergey Pleshakov

@Joe, ¿qué no funciona exactamente? ¿Cuál es su entorno y qué errores obtiene? Me gustaría revisar la solución para que funcione en todas las plataformas
Sergey Pleshakov

17

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

Esto no es nada lujoso, pero es muy simple de usar y no cambia el objeto de registro, por lo que evita registrar las secuencias de escape ANSI en un archivo de registro si se utiliza 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 los nombres de los niveles coloreados es reemplazar los Formateadores de sus asesores de asesoramiento con el Formateador coloreado. Si está registrando una aplicación completa, solo necesita hacer esto para el registrador de nivel superior.

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

Ejemplo de uso

app.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

sub_module.py

#!/usr/bin/env python

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

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

Resultados

Salida terminal

Salida terminal

contenido de app.log

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 - DEBUG - Hello from the sub module

Por supuesto, puede obtener la elegancia que desee con el formato del terminal y las salidas del archivo de registro. Solo se coloreará el nivel de registro.

Espero que alguien encuentre esto ú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


2
Por cierto, para agregar colores al mensaje en sí, simplemente agregue esta línea antes return:colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
The Godfather

15

Actualicé el ejemplo de las etiquetas de soporte de airmind para primer plano y fondo. Simplemente use las variables de color $ BLACK - $ WHITE en su cadena de formateador de registro. Para establecer el fondo simplemente 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 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

Gran mejora. Sin embargo, supersupongo que el comentario solo se aplica a alguna versión antigua de Python. Dado que esta respuesta es de 2010. Funcionó bien para mí con Python 2.7
Joakim

14

Puede importar el módulo de registro de colores y usarlo ColoredFormatterpara colorear los mensajes de registro.

Ejemplo

Placa 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 permite colores en los mensajes de registro, si el módulo de registro de colores 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:

ingrese la descripción de la imagen aquí


2
También se puede usar en colorlog.basicConfiglugar de logging.basicConfigque tiene algunos valores predeterminados buenos
MarSoft

1
Para el registro, el registro de colores no siempre funciona directamente en las plataformas de Windows (como se especifica, se requiere la dependencia del colorama). Incluso con eso, tuve problemas para que funcione en Anaconda / Spyder env. Es posible que deba especificar colorama.init (strip = False), por ejemplo, en escape_code.py (como se indica en este hilo github.com/spyder-ide/spyder/issues/1917 )
Matt-Mac-Muffin el


11

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

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

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

La siguiente clase solo funciona en plataformas que admiten ansi, pero debería ser trivial agregarle los códigos de color 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)


7

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

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, etc. Como se hizo en la pregunta.

Incluso podríamos adaptar el contenedor para tomar un colorargumento para establecer dinámicamente el color del mensaje usandologger.debug("message", color=GREY)

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

6

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)

5

Una herramienta simple pero muy flexible para colorear CUALQUIER texto de 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 función, y el tercer grupo coincide con el mensaje de registro que viene después de eso. También uso una secuencia paralela de 'negrita / normal', así como la secuencia de colores. Esto se ve así:

archivo de registro con formato de color

Tenga en cuenta que las líneas o partes de líneas que no coinciden con ninguna de mis expresiones regulares todavía tienen eco, por lo que esto no es como 'grep --color': nada se filtra de la salida.

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

También evita realmente descargar códigos ANSI en el archivo de registro en sí, lo que en mi humilde opinión es una mala idea, porque romperá cosas como buscar patrones en el archivo de registro a menos que siempre recuerde hacer coincidir los códigos ANSI en su expresión regular grep.


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

+1 ¡Buen ejemplo con los [9*mcódigos para los colores ANSI "brillantes"! PD: su última línea me preocupa un poco porque aún no se sabe si iniciar sesión fuera de una definición de función es seguro en Python .
personal_cloud

2

Aquí está mi solución:

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

1

La parte con la que tuve problemas fue configurar el formateador correctamente:

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

Se suponía que era un pseudocódigo (como faltaba también _set_colour), pero he agregado algo. Lo que más le costó fue saber cómo conectar el formateador correctamente.
Nick

Vea la solución "plomero". Creo que esta es una mejor manera de resolver el problema (es decir, el controlador debe hacer la coloración). stackoverflow.com/questions/384076/…
Nick

1

Si bien las otras soluciones parecen estar bien, tienen algunos problemas. Algunos colorean las líneas enteras que algunas veces no se desean y otros omiten cualquier configuración que puedan 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 genera y permanece en su color inicial. Si desea cambiar algo más que el mensaje, simplemente puede pasar los códigos de color alog_format en el ejemplo.


cuando lo uso, los mensajes se imprimen dos veces. ¿Sabes por qué?
Validus Oculus

@ ¿podrías explicarlo? Es decir, ¿quieres decir algo así como [17:01:36]:WARNING:this should be yellowthis should be yellowuna línea completa que se imprime dos veces?
Pithikos

Perdón por la brevedad del comentario. Lo primero sucedió: [17:01:36]: ADVERTENCIA: esto debería ser amarillo \ nesto debería ser amarillo. Sin embargo, solo quiero que se muestre el formateado, de lo contrario parece una basura debido a registros redundantes.
Validus Oculus

@ MuratKarakuş no está seguro de por qué sucede esto sin tener una vista completa de la implementación. Si está utilizando un registrador personalizado, ¿puede estar interfiriendo en algún momento? Una solución rápida podría ser eliminar el 7s:%(message)sarchivo log_format.
Pithikos

1

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

Parte del contenido se ha obtenido (con modificación) 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


1

Esta es una enumeración que contiene los códigos de color:

class TerminalColour:
    """
    Terminal colour formatting codes
    """
    # /programming/287871/print-in-terminal-with-colors
    MAGENTA = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    GREY = '\033[0m'  # normal
    WHITE = '\033[1m'  # bright white
    UNDERLINE = '\033[4m'

Esto puede aplicarse a los nombres de cada nivel de registro. Ten en cuenta que este es un truco monstruoso.

logging.addLevelName(logging.INFO, "{}{}{}".format(TerminalColour.WHITE, logging.getLevelName(logging.INFO), TerminalColour.GREY))
logging.addLevelName(logging.WARNING, "{}{}{}".format(TerminalColour.YELLOW, logging.getLevelName(logging.WARNING), TerminalColour.GREY))
logging.addLevelName(logging.ERROR, "{}{}{}".format(TerminalColour.RED, logging.getLevelName(logging.ERROR), TerminalColour.GREY))
logging.addLevelName(logging.CRITICAL, "{}{}{}".format(TerminalColour.MAGENTA, logging.getLevelName(logging.CRITICAL), .GREY))

Tenga en cuenta que su formateador de registro debe incluir el nombre del nivel de registro

%(levelname)

por ejemplo:

    LOGGING = {
...
        'verbose': {
            'format': '%(asctime)s %(levelname)s %(name)s:%(lineno)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '[%(asctime)s] %(levelname)s %(name)s %(message)s'
        },

1

FriendlyLog es otra alternativa. Funciona con Python 2 y 3 en Linux, Windows y MacOS.


Mirando hacia el nuevo PR para reducir el desorden de la ruta del módulo
mbspark

1

¿Qué pasa con resaltar también los argumentos del mensaje de registro con colores alternos, además de colorear por nivel? Recientemente escribí un código simple para eso. Otra ventaja es que la llamada de registro se realiza con el formato de estilo de llave de Python 3. ( "{}")

Vea el último código y ejemplos aquí: https://github.com/davidohana/colargulog

Código de registro de muestra:

root_logger = logging.getLogger()
console_handler = logging.StreamHandler(stream=sys.stdout)
console_format = "%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s"
colored_formatter = ColorizedArgsFormatter(console_format)
console_handler.setFormatter(colored_formatter)
root_logger.addHandler(console_handler)

logger = logging.getLogger(__name__)
logger.info("Hello World")
logger.info("Request from {} handled in {:.3f} ms", socket.gethostname(), 11)
logger.info("Request from {} handled in {:.3f} ms", "127.0.0.1", 33.1)
logger.info("My favorite drinks are {}, {}, {}, {}", "milk", "wine", "tea", "beer")
logger.debug("this is a {} message", logging.getLevelName(logging.DEBUG))
logger.info("this is a {} message", logging.getLevelName(logging.INFO))
logger.warning("this is a {} message", logging.getLevelName(logging.WARNING))
logger.error("this is a {} message", logging.getLevelName(logging.ERROR))
logger.critical("this is a {} message", logging.getLevelName(logging.CRITICAL))
logger.info("Does old-style formatting also work? %s it is, but no colors (yet)", True)

Salida:

ingrese la descripción de la imagen aquí

Implementación:

"""
colargulog - Python3 Logging with Colored Arguments and new string formatting style

Written by david.ohana@ibm.com
License: Apache-2.0
"""

import logging
import logging.handlers
import re


class ColorCodes:
    grey = "\x1b[38;21m"
    green = "\x1b[1;32m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    blue = "\x1b[1;34m"
    light_blue = "\x1b[1;36m"
    purple = "\x1b[1;35m"
    reset = "\x1b[0m"


class ColorizedArgsFormatter(logging.Formatter):
    arg_colors = [ColorCodes.purple, ColorCodes.light_blue]
    level_fields = ["levelname", "levelno"]
    level_to_color = {
        logging.DEBUG: ColorCodes.grey,
        logging.INFO: ColorCodes.green,
        logging.WARNING: ColorCodes.yellow,
        logging.ERROR: ColorCodes.red,
        logging.CRITICAL: ColorCodes.bold_red,
    }

    def __init__(self, fmt: str):
        super().__init__()
        self.level_to_formatter = {}

        def add_color_format(level: int):
            color = ColorizedArgsFormatter.level_to_color[level]
            _format = fmt
            for fld in ColorizedArgsFormatter.level_fields:
                search = "(%\(" + fld + "\).*?s)"
                _format = re.sub(search, f"{color}\\1{ColorCodes.reset}", _format)
            formatter = logging.Formatter(_format)
            self.level_to_formatter[level] = formatter

        add_color_format(logging.DEBUG)
        add_color_format(logging.INFO)
        add_color_format(logging.WARNING)
        add_color_format(logging.ERROR)
        add_color_format(logging.CRITICAL)

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        msg = record.msg
        msg = msg.replace("{", "_{{")
        msg = msg.replace("}", "_}}")
        placeholder_count = 0
        # add ANSI escape code for next alternating color before each formatting parameter
        # and reset color after it.
        while True:
            if "_{{" not in msg:
                break
            color_index = placeholder_count % len(ColorizedArgsFormatter.arg_colors)
            color = ColorizedArgsFormatter.arg_colors[color_index]
            msg = msg.replace("_{{", color + "{", 1)
            msg = msg.replace("_}}", "}" + ColorCodes.reset, 1)
            placeholder_count += 1

        record.msg = msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        formatter = self.level_to_formatter.get(record.levelno)
        self.rewrite_record(record)
        formatted = formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted


class BraceFormatStyleFormatter(logging.Formatter):
    def __init__(self, fmt: str):
        super().__init__()
        self.formatter = logging.Formatter(fmt)

    @staticmethod
    def is_brace_format_style(record: logging.LogRecord):
        if len(record.args) == 0:
            return False

        msg = record.msg
        if '%' in msg:
            return False

        count_of_start_param = msg.count("{")
        count_of_end_param = msg.count("}")

        if count_of_start_param != count_of_end_param:
            return False

        if count_of_start_param != len(record.args):
            return False

        return True

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        record.msg = record.msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        self.rewrite_record(record)
        formatted = self.formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted


0

Solo otra solución, 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 es una consola, de lo contrario no se utilizan colores.


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


Para la consola, puede omitir el nombre de archivo o simplemente filename = '' debería funcionar. modificar basicConfig para incluir otras propiedades como número de archivo, módulo ..
estifanos gebrehiwot

0

La siguiente solución solo funciona con Python 3, pero para mí parece más clara.

La idea es usar la fábrica de registros de registro para agregar atributos 'coloreados' a los objetos de registro de registros y luego usar estos atributos 'coloreados' en formato de registro.

import logging
logger = logging.getLogger(__name__)

def configure_logging(level):

    # add 'levelname_c' attribute to log resords
    orig_record_factory = logging.getLogRecordFactory()
    log_colors = {
        logging.DEBUG:     "\033[1;34m",  # blue
        logging.INFO:      "\033[1;32m",  # green
        logging.WARNING:   "\033[1;35m",  # magenta
        logging.ERROR:     "\033[1;31m",  # red
        logging.CRITICAL:  "\033[1;41m",  # red reverted
    }
    def record_factory(*args, **kwargs):
        record = orig_record_factory(*args, **kwargs)
        record.levelname_c = "{}{}{}".format(
            log_colors[record.levelno], record.levelname, "\033[0m")
        return record

    logging.setLogRecordFactory(record_factory)

    # now each log record object would contain 'levelname_c' attribute
    # and you can use this attribute when configuring logging using your favorite
    # method.
    # for demo purposes I configure stderr log right here

    formatter_c = logging.Formatter("[%(asctime)s] %(levelname_c)s:%(name)s:%(message)s")

    stderr_handler = logging.StreamHandler()
    stderr_handler.setLevel(level)
    stderr_handler.setFormatter(formatter_c)

    root_logger = logging.getLogger('')
    root_logger.setLevel(logging.DEBUG)
    root_logger.addHandler(stderr_handler)


def main():
    configure_logging(logging.DEBUG)

    logger.debug("debug message")
    logger.info("info message")
    logger.critical("something unusual happened")


if __name__ == '__main__':
    main()

Puede modificar fácilmente este ejemplo para crear otros atributos coloreados (fe message_c) y luego usar estos atributos para obtener texto coloreado (solo) donde desee.

(Truco práctico que descubrí recientemente: tengo un archivo con registros de depuración de colores y cada vez que quiero aumentar temporalmente el nivel de registro de mi aplicación, solo tail -fel archivo de registro en un terminal diferente y veo registros de depuración en la pantalla sin cambiar ninguna configuración y reiniciar la aplicación )


0

Esta es otra variante de Python3 del ejemplo de airmind. Quería algunas características específicas que no vi en los otros ejemplos

  • use colores para el terminal pero no escriba caracteres no imprimibles en los controladores de archivos (definí 2 formateadores para esto)
  • capacidad de anular el color para un mensaje de registro específico
  • configurar el registrador desde un archivo (yaml en este caso)

Notas: Utilicé colorama pero puedes modificar esto para que no sea obligatorio. También para mi prueba, solo estaba ejecutando el archivo python, por lo que mi clase está en el módulo __main__. Tendría que cambiar (): __main__.ColoredFormattera cualquiera que sea su módulo.

pip install colorama pyyaml

logging.yaml

---
version: 1
disable_existing_loggers: False
formatters:
  simple:
    format: "%(threadName)s - %(name)s - %(levelname)s - %(message)s"
  color:
    format: "%(threadName)s - %(name)s - %(levelname)s - %(message)s"
    (): __main__.ColoredFormatter
    use_color: true

handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: color
    stream: ext://sys.stdout

  info_file_handler:
    class: logging.handlers.RotatingFileHandler
    level: INFO
    formatter: simple
    filename: app.log
    maxBytes: 20971520 
    backupCount: 20
    encoding: utf8

  error_file_handler:
    class: logging.handlers.RotatingFileHandler
    level: ERROR
    formatter: simple
    filename: errors.log
    maxBytes: 10485760 
    backupCount: 20
    encoding: utf8

root:
  level: DEBUG
  handlers: [console, info_file_handler, error_file_handler]

main.py

import logging
import logging.config
import os
from logging import Logger

import colorama
import yaml
from colorama import Back, Fore, Style

COLORS = {
    "WARNING": Fore.YELLOW,
    "INFO": Fore.CYAN,
    "DEBUG": Fore.BLUE,
    "CRITICAL": Fore.YELLOW,
    "ERROR": Fore.RED,
}


class ColoredFormatter(logging.Formatter):
    def __init__(self, *, format, use_color):
        logging.Formatter.__init__(self, fmt=format)
        self.use_color = use_color

    def format(self, record):
        msg = super().format(record)
        if self.use_color:
            levelname = record.levelname
            if hasattr(record, "color"):
                return f"{record.color}{msg}{Style.RESET_ALL}"
            if levelname in COLORS:
                return f"{COLORS[levelname]}{msg}{Style.RESET_ALL}"
        return msg


with open("logging.yaml", "rt") as f:
    config = yaml.safe_load(f.read())
    logging.config.dictConfig(config)

logger: Logger = logging.getLogger(__name__)
logger.info("Test INFO", extra={"color": Back.RED})
logger.info("Test INFO", extra={"color": f"{Style.BRIGHT}{Back.RED}"})
logger.info("Test INFO")
logger.debug("Test DEBUG")
logger.warning("Test WARN")

salida:

salida

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.