Come posso colorare l'output di registrazione di Python?


353

Qualche tempo fa, ho visto un'applicazione Mono con output colorato, presumibilmente a causa del suo sistema di log (perché tutti i messaggi erano standardizzati).

Ora, Python ha il loggingmodulo, che consente di specificare molte opzioni per personalizzare l'output. Quindi, immagino che qualcosa di simile sarebbe possibile con Python, ma non riesco a scoprire come farlo da nessuna parte.

C'è un modo per rendere l' loggingoutput del modulo Python a colori?

Quello che voglio (ad esempio) errori in rosso, messaggi di debug in blu o giallo e così via.

Naturalmente ciò richiederebbe probabilmente un terminale compatibile (lo sono i terminali più moderni); ma potrei tornare loggingall'output originale se il colore non è supportato.

Qualche idea su come posso ottenere un output colorato con il modulo di registrazione?


1
È necessario specificare che si desidera una soluzione multipiattaforma, sia Linux che Windows.
sorin,

1
Correlato se si utilizza Eclipse / PyDev: colorazione dei registri nella console di Eclipse
Tobias Kienzler

5
Forse puoi anche usare colorlog
Ehtesh Choudhury,

5
Puoi anche provare chromalog che ho scritto per supportare tutti i sistemi operativi e le versioni di Python (2.7 e 3. *)
prima del

1
Le soluzioni che scaricano effettivamente i codici ANSI nel file di registro sono una cattiva idea, ti cattureranno quando sei in cerca di qualcosa tra sei mesi ma dimenticano di consentire i caratteri ANSI nel tuo modello regex. Di seguito sono riportate alcune soluzioni che aggiungono il colore durante la visualizzazione del registro, anziché durante la scrittura del registro ...
Jonathan Hartley,

Risposte:


192

Sapevo già delle fughe di colore, le ho usate nel mio prompt di bash tempo fa. Grazie comunque.
Quello che volevo era integrarlo con il modulo di registrazione, cosa che alla fine ho fatto dopo un paio di tentativi ed errori.
Ecco cosa finisco con:

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

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

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

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

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

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

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

E per usarlo, crea il tuo 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)

Nel caso in cui qualcun altro ne abbia bisogno.

Fai attenzione se stai utilizzando più di un logger o gestore: ColoredFormattersta cambiando l'oggetto record, che viene passato ulteriormente ad altri gestori o propagato ad altri logger. Se hai configurato i registratori di file, ecc. Probabilmente non vuoi avere i colori nei file di registro. Per evitarlo, probabilmente è meglio semplicemente crearne una copia recordcon copy.copy()prima di manipolare l'attributo levelname, o ripristinare il levelname al valore precedente, prima di restituire la stringa formattata (credito a Michael nei commenti).


Dove sono definiti GIALLO, BIANCO, BLU, ecc.?
Swaroop CH,

1
@Swaroop - Questi sono codici di escape ANSI, che puoi leggere cercare su Google o trovare qui: en.wikipedia.org/wiki/ANSI_escape_code , o in alternativa pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
Brian M Caccia il

53
Non credo che dovresti creare una sottoclasse di logger proprio per questo - la tua risposta va bene per quanto riguarda la creazione di uno specializzato Formattere specificando il suo utilizzo su un StreamHandler. Ma non è necessaria una sottoclasse del logger. In effetti, l'uso di una classe logger aggiunge un gestore a ogni logger creato, che non è ciò che si desidera in genere.
Vinay Sajip,


6
Una nota a margine ColoredFormatter. Sta cambiando l'oggetto record, che viene passato ulteriormente ad altri gestori o propagato ad altri logger. Se hai configurato i registratori di file, ecc. Probabilmente non vuoi avere i colori nei file di registro. Per evitarlo, probabilmente è meglio crearne semplicemente una copia recordcon copy.copy()prima di manipolare l'attributo levelname o ripristinare il levelname al valore precedente, prima di restituire la stringa formattata.
Michael,

149

Anni fa ho scritto un gestore di stream colorato per uso personale. Poi mi sono imbattuto in questa pagina e ho trovato una raccolta di frammenti di codice che le persone stanno copiando / incollando :-(. Il mio gestore di stream attualmente funziona solo su UNIX (Linux, Mac OS X) ma il vantaggio è che è disponibile su PyPI (e GitHub ) ed è semplicissimo da usare. Ha anche una modalità di sintassi Vim :-). In futuro potrei estenderlo per funzionare su Windows.

Per installare il pacchetto:

$ pip install coloredlogs

Per confermare che funziona:

$ coloredlogs --demo

Per iniziare con il tuo codice:

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

Il formato di registro predefinito mostrato nell'esempio sopra contiene la data, l'ora, il nome host, il nome del logger, il PID, il livello di registro e il messaggio di registro. Ecco come appare in pratica:

Schermata dell'output di coloredlogs

NOTA: quando si utilizza Git Bash con MinTTY

Git Bash su Windows ha alcune stranezze documentate: Winpty e Git Bash

Che per i codici di escape ANSI e per la riscrittura e le animazioni dei caratteri in stile ncurses, è necessario aggiungere un prefisso ai comandi winpty.

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

2
abbastanza divertente, stavo per aggiungere un link a " pypi.python.org/pypi/coloredlogs/0.4.7 " in questa discussione!
Iosu S.

1
Per qualche motivo continuo ad AttributeError: 'module' object has no attribute 'install'usarlo coloredlogs.install(). Puoi confermarlo con l'ultima versione.
con-f-use

11
Questo sembra bellissimo. Sfortunatamente, rompe molte cose; in particolare, annulla le chiamate a logging.basicConfig. Ciò rende impossibile utilizzare un formattatore personalizzato, ad esempio.
Clément,

@ Clément: Due domande (sovrapposte?): (1) Cosa intendi esattamente con "svuota le chiamate a logging.basicConfig" e (2) quale sarebbe l'alternativa? Entrambi logging.basicConfig()e coloredlogs.install()installare un gestore di flusso che accede alla console, quindi senza "annullare" si otterrebbero messaggi duplicati ...
xolox

Mi aspettavo sia la magia per (1), sia (più ragionevolmente) un modo per dire coloredlogs.installquale formato usare, come nel colorlogpacchetto.
Clément,

74

Ecco una soluzione che dovrebbe funzionare su qualsiasi piattaforma. Se non mi dice e lo aggiornerò.

Come funziona: su piattaforma che supporta gli escape ANSI li sta usando (non Windows) e su Windows usa le chiamate API per cambiare i colori della console.

Lo script modifica il metodo logging.StreamHandler.emit dalla libreria standard aggiungendo un wrapper ad esso.

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
Ho scritto una classe StreamHandler basata su questo, vedi gist.github.com/mooware/a1ed40987b6cc9ab9c65 .
Mooware,

2
questo ha funzionato per me! linea 90: dovrebbe essere args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
Rasika Perera,

Mi piace questa soluzione. usandolo attualmente. Vedo che esiste un attributo _set_color, c'è un modo per farlo per un messaggio di registro specifico? modifica , oh vedi che è solo una patch per macchine Windows. sarebbe bello aggiungere personalizzato per diversi casi d'uso.
Brizz,

+1 per il colore ANSI. In xterm puoi persino ottenere 256 colori alla volta e puoi definire la tavolozza in modo dinamico! Si noti, tuttavia, che tutte le chiamate alle funzioni di registrazione devono rientrare nella definizione di una funzione per evitare potenziali problemi di blocco dell'importazione durante la registrazione al di fuori di una definizione di funzione . Il tuo codice sembra principalmente buono; solo un po ' TestColorer.pymi preoccupa.
personal_cloud

Ciò comporta codici colore all'inizio e alla fine dei messaggi di registro nei file di registro effettivi.
MehmedB,

74

Aggiornamento : poiché questo è un prurito che ho intenzione di grattare per così tanto tempo, sono andato avanti e ho scritto una libreria per persone pigre come me che vogliono solo modi semplici di fare le cose: zenlog

Colorlog è eccellente per questo. È disponibile su PyPI (e quindi installabile tramite pip install colorlog) ed è attivamente gestito .

Ecco un breve frammento di copia e incolla per impostare la registrazione e stampare messaggi di registro dall'aspetto 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")

Produzione:

Uscita colorlog


4
Bella risposta; +1. L'esempio di codice potrebbe essere tagliato comunque (sono setLeveldavvero necessarie tre chiamate ?)
Clément

1
Speravo di trovare una risposta come questa se avessi esaminato le risposte abbastanza a lungo. ☺ Spero che @airmind considererà la risposta accettata, così le persone che lavorano in futuro possono trovare quella che sembra essere la migliore biblioteca con pigrizia ottimale. 😉
Michael Scheper,

Ho appena votato questo per gli esempi di messaggi di OUTPUT ^^
Agustin Barrachina il

69

Soluzione rapida e sporca per livelli di registro predefiniti e senza definire una nuova 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))

@ spiderplant0 import logging; # incolla il codice da @ABC; provalo con logging.warning ('questo è un test'). Vedrai la parte maiuscola di "ATTENZIONE: questo è un test" colorato. Funziona solo su Linux
Riccardo Galli,

3
Dal momento che solo il nome loglevel è colorato, devi assicurarti che il nome loglevel sia stampato sulla console. Questo non succede per me. Qualcosa del genere aiuterà: logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')dove ovviamente %(levelnames)sè importante.
Sebastian,

4
La soluzione più semplice e pulita da applicare e comprendere.
F. Santiago,

1
Basta provare nella console di Linux. echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again". L' -eopzione echo interpreta "\ 033" come forma ottale del simbolo EscII ASCII. Questo simbolo speciale fa sì che alcuni terminali compatibili interpretino i caratteri successivi ( minclusi i caratteri ) come comandi speciali. en.wikipedia.org/wiki/ANSI_escape_code
eugene-bright

1
Miglioramento minore: inserisci questo codice if sys.sdterr.isatty():. In questo caso, se si reindirizza l'output su file, il file non conterrà questi caratteri di escape.
lesnik,

36

Codice 2020, nessun pacchetto aggiuntivo richiesto, Python 3

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

Registratore di istanze

# 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 usa!

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

Risultato inserisci qui la descrizione dell'immagine

La combinazione di colori inserisci qui la descrizione dell'immagine

Per windows

Questa soluzione funziona su Mac OS, terminali IDE. Sembra che il prompt dei comandi di Windows non abbia colori per impostazione predefinita. Ecco le istruzioni su come abilitarli, cosa che non ho provato https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/


1
Eseguo il test (python 3.7, windows), ma la registrazione non mostra i colori:←[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
costruttore

Purtroppo non funziona.
Joe,

2
Questa risposta mi è piaciuta così tanto che ho fatto un repository per essa, con alcuni incrementi e un cheat sheet di colori ansi.
Teodoro,

@constructor dove lo esegui? Console IDE? terminale di windows?
Sergey Pleshakov,

@Joe cosa non funziona esattamente? qual è il tuo ambiente e quali errori ricevi? Vorrei rivedere la soluzione per farla funzionare su più piattaforme
Sergey Pleshakov il

17

Bene, immagino che potrei anche aggiungere la mia variazione del logger colorato.

Non è niente di speciale, ma è molto semplice da usare e non modifica l'oggetto record, quindi evita di registrare le sequenze di escape ANSI in un file di registro se viene utilizzato un gestore di file. Non influisce sulla formattazione del messaggio di registro.

Se stai già utilizzando il Formatter del modulo di registrazione , tutto ciò che devi fare per ottenere nomi di livello colorati è sostituire il Formatter dei gestori dei consigli con il ColoredFormatter. Se stai registrando un'intera app, devi farlo solo per il logger di livello superiore.

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)

Esempio di utilizzo

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

risultati

Uscita terminale

Uscita terminale

contenuto 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

Naturalmente puoi ottenere la fantasia che vuoi con la formattazione dei risultati del terminale e dei file di registro. Verrà colorato solo il livello di registro.

Spero che qualcuno lo trovi utile e non è solo troppo dello stesso. :)

I file di esempio di Python possono essere scaricati da questo GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd


2
A proposito per aggiungere colori al messaggio stesso basta aggiungere questa riga prima return:colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
Il Padrino

15

Ho aggiornato l'esempio dai tag di supporto di airmind per primo piano e sfondo. Basta usare le variabili di colore $ NERO - $ BIANCO nella stringa del formattatore del registro. Per impostare lo sfondo basta usare $ 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

Quindi ora puoi semplicemente fare quanto segue nel tuo file di configurazione:

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

Grande miglioramento. Comunque il commento su supervale solo per alcune antiche versioni di Python immagino? Dal momento che questa risposta è del 2010. Ha funzionato bene con Python 2.7
Joakim,

14

È possibile importare il modulo colorlog e utilizzarlo ColoredFormatterper colorare i messaggi di registro.

Esempio

Caldaia per modulo principale:

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

Il codice abilita i colori nei messaggi di registro solo se il modulo colorlog è installato e se l'output effettivamente viene inviato a un terminale. Ciò evita che le sequenze di escape vengano scritte in un file quando viene reindirizzato l'output del registro.

Inoltre, viene impostata una combinazione di colori personalizzata che si adatta meglio ai terminali con sfondo scuro.

Alcuni esempi di chiamate di registrazione:

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

Produzione:

inserisci qui la descrizione dell'immagine


2
Inoltre può usare al colorlog.basicConfigposto del logging.basicConfigquale ha alcune buone impostazioni predefinite
MarSoft

1
Per la cronaca, colorlog non funziona sempre direttamente su piattaforme Windows (come specificato, è richiesta la dipendenza colorama). Anche con quello, ho avuto problemi a farlo funzionare in Anaconda / Spyder env. Potrebbe essere necessario specificare colorama.init (strip = False) ad esempio in escape_code.py (come indicato in questa discussione github.com/spyder-ide/spyder/issues/1917 )
Matt-Mac-Muffin


11

Ho modificato l'esempio originale fornito da Sorin e ho eseguito la sottoclasse di StreamHandler in ColorizedConsoleHandler.

Il rovescio della medaglia della loro soluzione è che modifica il messaggio e, poiché ciò sta modificando il messaggio di log effettivo, anche altri gestori riceveranno anche il messaggio modificato.

Ciò ha comportato nel nostro caso file di log con colorcode in quanto utilizziamo più logger.

La classe seguente funziona solo su piattaforme che supportano ansi, ma dovrebbe essere banale aggiungere i codici colore di Windows ad esso.

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

Ci sono tonnellate di risposte. Ma nessuno parla di decoratori. Quindi ecco il mio.

Perché è molto più semplice.

Non è necessario importare nulla né scrivere alcuna sottoclasse:

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

Questo imposta gli errori in rosso, i messaggi di debug in blu e così via. Come chiesto nella domanda.

Potremmo persino adattare il wrapper per prendere un colorargomento per impostare dinamicamente il colore del messaggio usandologger.debug("message", color=GREY)

EDIT: Quindi ecco il decoratore adattato per impostare i colori in fase di esecuzione:

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

Un altro remix minore dell'approccio di airmind che mantiene tutto in una 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)

Per utilizzare allegare il formatter a un gestore, qualcosa del tipo:

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

5

Uno strumento semplice ma molto flessibile per colorare QUALSIASI testo terminale è ' colout '.

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

Dove qualsiasi testo nell'output di 'myprocess' che corrisponde al gruppo 1 del regex verrà colorato con color1, gruppo 2 con color2, ecc.

Per esempio:

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

vale a dire che il primo gruppo regex (parentesi) corrisponde alla data iniziale nel file di registro, il secondo gruppo corrisponde a un nome di file, numero di riga e nome della funzione di Python e il terzo gruppo corrisponde al messaggio di registro che segue. Uso anche una sequenza parallela di 'grassetto / normali' così come la sequenza di colori. Questo sembra:

file di log con formattazione colorata

Nota che le linee o parti di linee che non corrispondono a nessuna delle mie regex sono ancora echeggiate, quindi questo non è come 'grep --color' - nulla viene filtrato dall'output.

Ovviamente questo è abbastanza flessibile da poterlo utilizzare con qualsiasi processo, non solo per il loging dei file di log. Di solito ho appena tirato fuori una nuova regex al volo ogni volta che voglio colorare qualcosa. Per questo motivo, preferisco il colore a qualsiasi strumento personalizzato di colorazione dei file di log, perché ho solo bisogno di imparare uno strumento, indipendentemente da ciò che sto colorando: registrazione, output di test, sintassi evidenziando frammenti di codice nel terminale, ecc.

Evita anche di scaricare effettivamente i codici ANSI nel file di log stesso, il che IMHO è una cattiva idea, perché romperà cose come il grepping per i pattern nel file di log a meno che non ricordi sempre di abbinare i codici ANSI nel tuo regex 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 Bell'esempio con i [9*mcodici per i colori "luminosi" ANSI! PS, la tua ultima riga mi riguarda un po 'perché non è ancora noto se la registrazione al di fuori della definizione di una funzione sia sicura in Python .
personal_cloud

2

Ecco la mia soluzione:

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

Il bit con cui ho avuto problemi è stato l'impostazione corretta del formatter:

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 poi usare:

import coloured_log
import logging

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

coloured_log.close()    # restore colours

Doveva essere uno pseudo codice (dato che mancava anche _set_colour), ma ha aggiunto qualcosa. La cosa più problematica era sapere come collegare correttamente il formatter.
Nick,

Vedi la soluzione "jack idraulico". Penso che questo sia un modo migliore per risolvere il problema (cioè il gestore dovrebbe fare la colorazione). stackoverflow.com/questions/384076/…
Nick

1

Mentre le altre soluzioni sembrano soddisfacenti hanno alcuni problemi. Alcuni colorano le linee intere che a volte non sono richieste e altri omettono qualsiasi configurazione che potresti avere tutte insieme. La soluzione di seguito non riguarda altro che il messaggio stesso.

Codice

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)

Esempio

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

Produzione

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

Come vedi, tutto il resto viene comunque emesso e rimane nel loro colore iniziale. Se vuoi cambiare qualcos'altro oltre al messaggio, puoi semplicemente passare i codici colore log_formatnell'esempio.


quando lo uso, i messaggi vengono stampati due volte. sai perché?
Validus Oculus,

@ potresti elaborare? Vale a dire intendi qualcosa di simile [17:01:36]:WARNING:this should be yellowthis should be yellowo una linea intera stampata due volte?
Pithikos,

Ci scusiamo per la brevità del commento. Il primo è successo: [17:01:36]: ATTENZIONE: questo dovrebbe essere giallo \ nquesto dovrebbe essere giallo. Tuttavia, voglio solo che venga mostrato quello formattato, altrimenti sembra una spazzatura a causa dei registri ridondanti.
Validus Oculus,

@ MuratKarakuş non so perché questo accada senza avere una visione completa dell'implementazione. Se stai usando un logger personalizzato forse stai interferendo ad un certo punto? Una soluzione rapida potrebbe essere quella di rimuovere 7s:%(message)sil log_format.
Pithikos,

1

Ho due osservazioni da aggiungere, una delle quali colora solo il messaggio (ColoredFormatter) e una colora l'intera linea (ColorizingStreamHandler). Questi includono anche più codici colore ANSI rispetto alle soluzioni precedenti.

Alcuni contenuti sono stati forniti (con modifiche) da: Il post sopra e http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .

Colora solo il messaggio:

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

Colora l'intera linea:

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

Questo è un Enum contenente i codici colore:

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'

Questo può essere applicato ai nomi di ciascun livello di registro. Sii consapevole del fatto che questo è un trucco mostruoso.

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

Si noti che il formattatore del registro deve includere il nome del livello di registro

%(levelname)

per esempio:

    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 è un'altra alternativa. Funziona con Python 2 e 3 su Linux, Windows e MacOS.


In attesa del nuovo PR per ridurre il disordine del percorso del modulo
mbspark,

1

Che dire di evidenziare anche gli argomenti dei messaggi di registro con colori alternati, oltre a colorare per livello? Di recente ho scritto un codice semplice per questo. Un altro vantaggio è che la chiamata di registro viene effettuata con la formattazione in stile parentesi Python 3. ( "{}").

Vedi gli ultimi codici ed esempi qui: https://github.com/davidohana/colargulog

Codice di registrazione di esempio:

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)

Produzione:

inserisci qui la descrizione dell'immagine

Implementazione:

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

Usa la fantasia .

Esempio:

print(pyfancy.RED + "Hello Red!" + pyfancy.END)

La domanda era modificare la loggingfunzionalità per utilizzare una libreria di colorazione separata.
Infetto Drake il

0

Solo un'altra soluzione, con i colori di 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)

chiamalo una volta dalla tua __main__funzione. Ho qualcosa del genere lì:

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

verifica inoltre che l'output sia una console, altrimenti non vengono utilizzati colori.


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


Per console è possibile escludere il nome file o semplicemente il nome file = '' dovrebbe funzionare. modificare basicConfig per includere altre proprietà come il numero del file, il modulo ..
estifanos gebrehiwot

0

La seguente soluzione funziona solo con Python 3, ma per me sembra molto chiara.

L'idea è di utilizzare la fabbrica di record di registro per aggiungere attributi 'colorati' per registrare oggetti record e di utilizzare questi attributi 'colorati' in formato 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()

È possibile modificare facilmente questo esempio per creare altri attributi colorati (fe message_c) e quindi utilizzare questi attributi per ottenere il testo colorato (solo) dove desiderato.

(trucco utile che ho scoperto di recente: ho un file con registri di debug colorati e ogni volta che desidero aumentare temporaneamente il livello di registro della mia applicazione ho solo tail -fil file di registro in un terminale diverso e vedo i registri di debug sullo schermo senza cambiare qualsiasi configurazione e riavviare l'applicazione )


0

Questa è un'altra variante di Python3 dell'esempio di airmind. Volevo alcune funzionalità specifiche che non vedevo negli altri esempi

  • usare i colori per il terminale ma non scrivere caratteri non stampabili nei gestori di file (per questo ho definito 2 formattatori)
  • possibilità di sovrascrivere il colore per un messaggio di registro specifico
  • configurare il logger da un file (yaml in questo caso)

Note: ho usato colorama ma è possibile modificarlo in modo che non sia necessario. Anche per i miei test stavo solo eseguendo il file Python, quindi la mia classe è nel modulo __main__Dovresti passare (): __main__.ColoredFormattera qualunque sia il tuo modulo.

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

produzione:

produzione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.