Como posso colorir a saída de log do Python?

353

Há algum tempo, vi um aplicativo Mono com saída colorida, provavelmente por causa de seu sistema de log (porque todas as mensagens foram padronizadas).

Agora, o Python possui o loggingmódulo, que permite especificar muitas opções para personalizar a saída. Então, estou imaginando que algo semelhante seria possível com o Python, mas não consigo descobrir como fazer isso em nenhum lugar.

Existe alguma maneira de fazer a loggingsaída do módulo Python em cores?

O que eu quero (por exemplo) erros em vermelho, mensagens de depuração em azul ou amarelo e assim por diante.

Obviamente, isso provavelmente exigiria um terminal compatível (a maioria dos terminais modernos é); mas eu poderia voltar à loggingsaída original se a cor não for suportada.

Alguma idéia de como obter impressões coloridas com o módulo de registro?

mente aérea
fonte
11
Você deve especificar que deseja uma solução multiplataforma - Linux e Windows.
Sorin
11
Relacionada se você usar o Eclipse / PyDev: registros Colorize no console do eclipse
Tobias KIENZLER
5
Talvez você também pode usar colorlog
Ehtesh Choudhury
5
Você também pode tentar chromalog que escrevi para suportar todos os sistemas operacionais e versões do Python (2,7 e 3. *)
ereOn
11
As soluções que realmente despejam códigos ANSI no arquivo de log são uma péssima idéia; elas serão exibidas quando você estiver aguardando algo em seis meses, mas esqueça de permitir os caracteres ANSI no seu padrão de regex. Existem algumas soluções abaixo que adicionam a cor como você visualizar o registo, em vez de como o log é escrito ...
Jonathan Hartley

Respostas:

192

Eu já sabia sobre as fugas de cores, usei-as no meu prompt do bash há um tempo. Obrigado mesmo assim.
O que eu queria era integrá-lo ao módulo de registro, o que finalmente fiz depois de algumas tentativas e erros.
Aqui está o que eu acabo com:

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)

E para usá-lo, crie seu próprio 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)

Apenas no caso de mais alguém precisar.

Tenha cuidado se estiver usando mais de um criador de logs ou manipulador: ColoredFormatterestá alterando o objeto de registro, que é passado ainda mais para outros manipuladores ou propagado para outros criadores de logs. Se você configurou registradores de arquivos, etc., provavelmente não deseja ter as cores nos arquivos de log. Para evitar isso, provavelmente é melhor simplesmente criar uma cópia de recordwith copy.copy()antes de manipular o atributo levelname ou redefinir o levelname para o valor anterior, antes de retornar a string formatada (crédito para Michael nos comentários).

mente aérea
fonte
Onde AMARELO, BRANCO, AZUL etc. são definidos?
Swaroop CH
11
@Swaroop - Esses são códigos de escape ANSI, que você pode ler no Google ou encontrar aqui: en.wikipedia.org/wiki/ANSI_escape_code ou, alternativamente, pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
Brian M Hunt
53
Eu não acredito que você deva criar uma subclasse de logger apenas para isso - sua resposta é ótima, tanto quanto criar um especialista Formattere especificar seu uso em a StreamHandler. Mas não há necessidade de uma subclasse de logger. De fato, o uso de uma classe de criador de logs adiciona um manipulador a cada criador de logs criado, que não é o que você normalmente deseja.
Vinay Sajip
6
Uma nota lateral para ColoredFormatter. Ele está alterando o objeto de registro, que é passado adiante para outros manipuladores ou propagado para outros registradores. Se você configurou registradores de arquivos, etc., provavelmente não deseja ter as cores nos arquivos de log. Para evitar isso, provavelmente é melhor simplesmente criar uma cópia de recordwith copy.copy()antes de manipular o atributo levelname ou redefinir o nome do nível para o valor anterior, antes de retornar a sequência formatada.
Michael
149

Anos atrás, escrevi um manipulador de fluxo colorido para meu próprio uso. Então me deparei com esta página e encontrei uma coleção de trechos de código que as pessoas estão copiando / colando :-(. Atualmente, meu manipulador de fluxo só funciona no UNIX (Linux, Mac OS X), mas a vantagem é que ele está disponível no PyPI (e no GitHub ) e é muito simples de usar.Também possui um modo de sintaxe Vim :-). No futuro, eu poderia estendê-lo para funcionar no Windows.

Para instalar o pacote:

$ pip install coloredlogs

Para confirmar que funciona:

$ coloredlogs --demo

Para começar com seu próprio 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!

O formato de log padrão mostrado no exemplo acima contém a data, hora, nome do host, o nome do criador de logs, o PID, o nível do log e a mensagem de log. É assim na prática:

Captura de tela da saída do coloredlogs

NOTA: Ao usar o Git Bash com MinTTY

O Git Bash no Windows possui algumas peculiaridades documentadas: Winpty e Git Bash

Para códigos de escape ANSI e para reescrições e animações de caracteres no estilo ncurses, é necessário prefixar comandos com winpty.

$ winpty coloredlogs --demo
$ winpty python your_colored_logs_script.py
xolox
fonte
2
engraçado o suficiente, eu apenas adicionaria um link para " pypi.python.org/pypi/coloredlogs/0.4.7 " neste tópico!
Iosu S.
11
Por algum motivo, continuo recebendo AttributeError: 'module' object has no attribute 'install'ao usar coloredlogs.install(). Você pode confirmar isso com a versão mais recente.
con-f-use
11
Isso parece bonito. Infelizmente, isso quebra muitas coisas; em particular, anula as chamadas para logging.basicConfig. Isso impossibilita o uso de um formatador personalizado, por exemplo.
Clément
@ Clément: Duas perguntas (sobrepostas?): (1) O que você quer dizer exatamente com "chamadas inválidas para logging.basicConfig" e (2) qual seria a alternativa? Ambos logging.basicConfig()e coloredlogs.install()instalar um manipulador de fluxo que os logs para o console, assim, sem "anular" que se obtém mensagens duplicadas ...
Xolox
Eu esperava mágica (1) ou (mais razoavelmente) uma maneira de saber coloredlogs.installqual formato usar, como no colorlogpacote.
Clément
74

Aqui está uma solução que deve funcionar em qualquer plataforma. Se isso não me disser, eu irei atualizá-lo.

Como funciona: na plataforma que suporta escapes ANSI, você as usa (não Windows) e, no Windows, usa chamadas de API para alterar as cores do console.

O script hackea o método logging.StreamHandler.emit da biblioteca padrão, adicionando um wrapper a ele.

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())
sorin
fonte
3
Eu escrevi uma classe StreamHandler com base nisso, consulte gist.github.com/mooware/a1ed40987b6cc9ab9c65 .
mooware
2
isso funcionou para mim! linha 90: deve ser args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
Rasika Perera
Eu gosto desta solução. usando-o atualmente. Vejo que existe um atributo _set_color, existe uma maneira de fazer isso para uma mensagem de log específica? editar , oh veja que é apenas um patch para máquinas Windows. seria bom adicionar personalizado para diferentes casos de uso.
Brizz 25/05
+1 para cores ANSI. No xterm, você pode obter 256 cores por vez e definir a paleta dinamicamente! Observe, no entanto, que todas as chamadas para funções de log devem estar dentro de uma definição de função para evitar possíveis problemas de bloqueio de importação ao efetuar logon fora de uma definição de função . Seu código parece muito bom; apenas um pouquinho disso TestColorer.pyme preocupa.
Personal_cloud
Isso resulta em códigos de cores no início e no final das mensagens de log nos arquivos de log reais.
MehmedB
74

Atualização : Como essa é uma coceira que pretendo coçar há tanto tempo, escrevi uma biblioteca para pessoas preguiçosas como eu que só querem maneiras simples de fazer as coisas: zenlog

O Colorlog é excelente para isso. Está disponível no PyPI (e, portanto, instalável pip install colorlog) e é mantido ativamente .

Aqui está um trecho rápido de copiar e colar para configurar o log e imprimir mensagens de log com aparência 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")

Resultado:

Saída Colorlog

rlafuente
fonte
4
Ótima resposta; +1. O exemplo de código poderia ser aparadas embora (três chamadas para setLevelrealmente necessário?)
Clément
11
Eu esperava encontrar uma resposta como essa se eu pensasse nas respostas por tempo suficiente. @ Espero que a @airmind considere fazer disso a resposta aceita, para que futuras pessoas inteligentes em trabalho possam encontrar o que parece ser a melhor biblioteca com preguiça ideal. 😉
Michael Scheper
Eu acabei de votar neste artigo para exemplos de mensagens de OUTPUT ^^
Agustin Barrachina
69

Solução rápida e suja para níveis de log predefinidos e sem definir uma nova classe.

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))
abc
fonte
@ spiderplant0 registro de importação; # cole o código do @ABC; tente com logging.warning ('este é um teste'). Você verá a parte maiúscula de "AVISO: este é um teste" colorido. Funciona apenas em linux btw
Riccardo Galli
3
Como apenas o nome do nível de log é colorido, você deve certificar-se de que o nome do nível de log seja impresso no console. Isso não acontece imediatamente para mim. Algo nesse sentido ajudará: logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')Onde %(levelnames)sé claro que é importante.
26716 Sebastian
4
Solução mais simples e limpa para aplicar e entender.
F. Santiago
11
Basta experimentar no console do Linux. echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again". A -eopção echo interpreta "\ 033" como forma octal do símbolo Escape ASCII. Este símbolo especial faz com que alguns terminais compatíveis interpretem caracteres subseqüentes ( minclusive char ) como comandos especiais. pt.wikipedia.org/wiki/ANSI_escape_code
eugene-bright
11
Pequena melhoria: coloque esse código dentro if sys.sdterr.isatty():. Nesse caso, se você redirecionar a saída para o arquivo, o arquivo não conterá esses caracteres de escape.
lesnik 20/09/18
36

Código 2020, nenhum pacote adicional necessário, Python 3

Definir uma classe

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)

Instanciar logger

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

E use!

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

Resultado insira a descrição da imagem aqui

O esquema de cores insira a descrição da imagem aqui

Para Windows

Esta solução funciona no Mac OS, terminais IDE. Parece que o prompt de comando da janela não possui cores por padrão. Aqui estão as instruções sobre como ativá-los, que eu não tentei https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/

Sergey Pleshakov
fonte
11
Eu executo o teste (python 3.7, windows), mas o log não mostra cores:←[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
construtor
Infelizmente isso não funciona.
Joe
2
Gostei tanto dessa resposta que fiz um repo para ela, com alguns incrementos e uma folha de dicas de cores ansi.
Teodoro
@ construtor onde você o executa? Console IDE? terminal do windows?
Sergey Pleshakov
@ Joe, o que exatamente não funciona? qual é o seu ambiente e quais erros você recebe? Gostaria de revisar a solução para fazê-la funcionar em várias plataformas
Sergey Pleshakov
17

Bem, acho que devo adicionar minha variação do logger colorido.

Isso não é nada chique, mas é muito simples de usar e não altera o objeto de registro, evitando assim o registro das seqüências de escape ANSI em um arquivo de log se um manipulador de arquivo for usado. Não afeta a formatação da mensagem de log.

Se você já estiver usando o Formatador do módulo de registro , tudo o que você precisa fazer para obter nomes de níveis coloridos é substituir o Formatador dos manipuladores de aconselhamento pelo ColoredFormatter. Se você estiver registrando um aplicativo inteiro, precisará fazer isso apenas para o criador de logs de nível superior.

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

Exemplo 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

Saída terminal

Saída terminal

conteúdo 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

É claro que você pode obter o que quiser com a formatação das saídas do terminal e do arquivo de log. Somente o nível do log será colorido.

Espero que alguém ache isso útil e não seja apenas o mesmo. :)

Os arquivos de exemplo do Python podem ser baixados neste GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd

KCJ
fonte
2
BTW para adicionar cores à mensagem em si, basta adicionar esta linha antes return:colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
The Godfather
15

Atualizei o exemplo das tags de suporte do airmind para primeiro e segundo plano. Basta usar as variáveis ​​de cor $ BLACK - $ WHITE na string do formatador de log. Para definir o plano de fundo, 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

Então agora você pode simplesmente fazer o seguinte no seu arquivo de configuração:

[formatter_colorFormatter]
class=logging.ColorFormatter
format= $COLOR%(levelname)s $RESET %(asctime)s $BOLD$COLOR%(name)s$RESET %(message)s
camillobruni
fonte
Grande melhoria. No entanto, o comentário supersó se aplica a algumas versões antigas do Python, eu acho? Uma vez que esta resposta é a partir de 2010. Ele trabalhou muito bem para mim com Python 2.7
Joakim
14

Você pode importar o módulo colorlog e usá-lo ColoredFormatterpara colorir mensagens de log.

Exemplo

Caldeira para o 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__)

O código ativa apenas cores nas mensagens de log, se o módulo colorlog estiver instalado e se a saída for realmente para um terminal. Isso evita que sequências de escape sejam gravadas em um arquivo quando a saída do log é redirecionada.

Além disso, é configurado um esquema de cores personalizado que é mais adequado para terminais com fundo escuro.

Alguns exemplos de chamadas de log:

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

Resultado:

insira a descrição da imagem aqui

maxschlepzig
fonte
2
Também pode usar em colorlog.basicConfigvez de logging.basicConfigque tem alguns bons padrões
#
11
Para o registro, o colorlog nem sempre funciona diretamente nas plataformas Windows (conforme especificado, a dependência do colorama é necessária). Mesmo com isso, tive problemas para fazê-lo funcionar no ambiente Anaconda / Spyder. Pode ser necessário especificar colorama.init (strip = False), por exemplo, em escape_code.py (como indicado neste tópico github.com/spyder-ide/spyder/issues/1917 )
Matt-Mac-Muffin
11

Veja a seguinte solução. O manipulador de fluxo deve ser o responsável pela coloração; então, você tem a opção de colorir palavras em vez de apenas a linha inteira (com o Formatador).

http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html

usuario
fonte
Você pode encontrar uma implementação atualizado nesta essência (mantido pelo autor do blog). Estou usando e funciona muito bem. Obrigado por compartilhar.
noisebleed
11

Modifiquei o exemplo original fornecido por Sorin e subclasse StreamHandler para ColorizedConsoleHandler.

A desvantagem de sua solução é que ela modifica a mensagem e, como isso está modificando a mensagem de log real, outros manipuladores também receberão a mensagem modificada.

Isso resultou em arquivos de log com códigos de cores, no nosso caso, porque usamos vários registradores.

A classe abaixo funciona apenas em plataformas que suportam ansi, mas deve ser trivial adicionar os códigos de cores do Windows a ela.

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

Há toneladas de respostas. Mas ninguém está falando sobre decoradores. Então aqui está o meu.

Porque é muito mais simples.

Não há necessidade de importar nada, nem escrever nenhuma subclasse:

#!/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__)

Isso define os erros em vermelho, as mensagens de depuração em azul e assim por diante. Como perguntado na pergunta.

Poderíamos até adaptar o wrapper a um colorargumento para definir dinamicamente a cor da mensagem usandologger.debug("message", color=GREY)

EDIT: Então aqui está o decorador adaptado para definir cores em tempo de execução:

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)
deitado
fonte
6

Outro remix menor da abordagem do airmind que mantém tudo em uma classe:

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 anexar o formatador a um manipulador, algo como:

handler.setFormatter(ColorFormatter())
logger.addHandler(handler)
gravitação
fonte
5

Uma ferramenta simples, mas muito flexível, para colorir QUALQUER texto do terminal é ' colout '.

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

Onde qualquer texto na saída de 'myprocess' que corresponda ao grupo 1 da regex será colorido com cor1, grupo 2 com cor2, etc.

Por exemplo:

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

ou seja, o primeiro grupo regex (parens) corresponde à data inicial no arquivo de log, o segundo grupo corresponde a um nome de arquivo python, número de linha e nome de função, e o terceiro grupo corresponde à mensagem de log que vem depois disso. Também uso uma sequência paralela de 'negrito / normais', bem como a sequência de cores. Isso se parece com:

arquivo de log com formatação colorida

Observe que linhas ou partes de linhas que não correspondem a nenhuma da minha regex ainda são ecoadas, portanto, isso não é como 'grep --color' - nada é filtrado para fora da saída.

Obviamente, isso é flexível o suficiente para que você possa usá-lo com qualquer processo, não apenas com os arquivos de log finais. Normalmente, eu apenas preparo um novo regex rapidamente, sempre que quiser colorir alguma coisa. Por esse motivo, prefiro o colout a qualquer ferramenta personalizada para colorir arquivos de log, porque só preciso aprender uma ferramenta, independentemente do que estiver colorindo: registro, saída de teste, sintaxe destacando trechos de código no terminal etc.

Ele também evita realmente despejar códigos ANSI no próprio arquivo de log, o que IMHO é uma péssima idéia, porque isso quebrará coisas como grepping para padrões no arquivo de log, a menos que você lembre-se sempre de combinar os códigos ANSI em seu regex grep.

Jonathan Hartley
fonte
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)))`
Serhii Khachko
fonte
+1 Bom exemplo com os [9*mcódigos para as cores ANSI "brilhantes"! PS, sua última linha me preocupa um pouco, porque ainda não se sabe se o log fora de uma definição de função é seguro no Python .
personal_cloud
2

Aqui está a minha solução:

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])
veegee
fonte
1

A parte com a qual tive problemas foi configurar o formatador corretamente:

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)

E então para usar:

import coloured_log
import logging

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

coloured_log.close()    # restore colours
usuario
fonte
Era para ser pseudo-código (como _set_colour também faltava), mas adicionou algo. O principal problema foi saber como anexar o formatador corretamente.
Nick
Veja a solução "plumber jack". Eu acho que essa é uma maneira melhor de resolver o problema (ou seja, o manipulador deve fazer a coloração). stackoverflow.com/questions/384076/…
Nick
1

Enquanto as outras soluções parecem boas, elas têm alguns problemas. Alguns colorem as linhas inteiras que algumas vezes não são desejadas e outros omitem qualquer configuração que você possa ter todos juntos. A solução abaixo não afeta nada além da própria mensagem.

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)

Exemplo

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

Resultado

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

Como você vê, todo o resto ainda é produzido e permanece em sua cor inicial. Se você quiser alterar qualquer outra coisa além da mensagem, basta passar os códigos de cores log_formatno exemplo.

Pithikos
fonte
Quando eu uso, as mensagens são impressas duas vezes. Você sabe por quê?
Validus Oculus 15/10
@ você poderia elaborar? Ou seja, você quer dizer algo como [17:01:36]:WARNING:this should be yellowthis should be yellowou uma linha completa sendo impressa duas vezes?
Pithikos
Desculpe pela brevidade do comentário. O primeiro aconteceu: [17:01:36]: AVISO: isso deve ser amarelo \ nque deve ser amarelo. No entanto, eu só quero que o formatado seja mostrado, caso contrário, parece um lixo devido a logs redundantes.
Validus Oculus 15/10
@ MuratKarakuş não sabe por que isso acontece sem ter uma visão completa da implementação. Se você estiver usando um criador de logs personalizado, talvez esteja interferindo em algum momento? Uma solução rápida pode ser remover o 7s:%(message)sarquivo log_format.
Pithikos
1

Tenho dois envios a serem adicionados, um dos quais colore apenas a mensagem (ColoredFormatter) e outro colore a linha inteira (ColorizingStreamHandler). Isso também inclui mais códigos de cores ANSI do que as soluções anteriores.

Algum conteúdo foi obtido (com modificação) de: A postagem acima e http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .

Colorir apenas a mensagem:

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

Coloriza toda a linha:

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
ZetaSyanthis
fonte
1

Este é um Enum que contém os códigos de cores:

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'

Isso pode ser aplicado aos nomes de cada nível de log. Esteja ciente de que este é um truque 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))

Observe que seu formatador de log deve incluir o nome do nível de log

%(levelname)

por exemplo:

    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'
        },
Joe Heffer
fonte
1

FriendlyLog é outra alternativa. Funciona com Python 2 e 3 em Linux, Windows e MacOS.

SebiSebi
fonte
Ansioso para o novo PR para reduzir a desordem no caminho do módulo
mbspark 17/02
1

Que tal destacar também argumentos de mensagens de log com cores alternadas, além de colorir por nível? Eu escrevi recentemente um código simples para isso. Outra vantagem é que a chamada de log é feita com a formatação de chave do Python 3. ( "{}")

Veja o código e os exemplos mais recentes aqui: https://github.com/davidohana/colargulog

Código de log de amostra:

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)

Resultado:

insira a descrição da imagem aqui

Implementação:

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

Written by [email protected]
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
dux2
fonte
0

Use pyfancy .

Exemplo:

print(pyfancy.RED + "Hello Red!" + pyfancy.END)
WebMaster
fonte
A questão era ajustar a loggingfuncionalidade para usar uma biblioteca de cores separada.
Infectado Drake
0

Apenas mais uma solução, com as cores do 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)

chame uma vez de sua __main__função. Eu tenho algo assim lá:

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

também verifica se a saída é um console; caso contrário, nenhuma cor será usada.

yucer
fonte
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")

estifanos gebrehiwot
fonte
Para o console, você pode deixar de fora o nome do arquivo ou simplesmente filename = '' deve funcionar. modifique o basicConfig para incluir outras propriedades como número do arquivo, módulo ..
estifanos gebrehiwot 28/02
0

A solução a seguir funciona apenas com python 3, mas para mim parece mais clara.

A idéia é usar a fábrica de registros de log para adicionar atributos 'coloridos' aos objetos de registro de log e depois usar esses atributos 'coloridos' no formato de log.

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

Você pode modificar facilmente este exemplo para criar outros atributos coloridos (fe message_c) e, em seguida, usar esses atributos para obter o texto colorido (apenas) onde desejar.

(truque útil que descobri recentemente: tenho um arquivo com logs de depuração coloridos e sempre que quero aumentar temporariamente o nível de log do meu aplicativo, apenas tail -fo arquivo de log em um terminal diferente e vejo os logs de depuração na tela sem alterar qualquer configuração e reiniciar o aplicativo )

lesnik
fonte
0

Esta é outra variante Python3 do exemplo de airmind. Eu queria alguns recursos específicos que não vi nos outros exemplos

  • use cores para o terminal, mas não escreva caracteres não imprimíveis nos manipuladores de arquivos (eu defini 2 formatadores para isso)
  • capacidade de substituir a cor de uma mensagem de log específica
  • configurar o logger a partir de um arquivo (yaml neste caso)

Notas: Eu usei o colorama, mas você pode modificar isso para que não seja necessário. Também para o meu teste eu estava apenas correndo arquivo python por isso a minha classe é no módulo __main__Você teria que mudar (): __main__.ColoredFormatterpara o que seu 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")

resultado:

resultado

Scott
fonte