408 votes

Comment puis-je colorer la sortie de la journalisation Python ?

Il y a quelque temps, j'ai vu une application Mono avec une sortie colorée, vraisemblablement à cause de son système de logs (car tous les messages étaient standardisés).

Maintenant, Python a le logging qui vous permet de spécifier un grand nombre d'options pour personnaliser la sortie. J'imagine donc que quelque chose de similaire serait possible avec Python, mais je ne trouve nulle part comment le faire.

Y a-t-il un moyen de faire en sorte que le Python logging sortie du module en couleur ?

Ce que je veux (par exemple), ce sont des erreurs en rouge, des messages de débogage en bleu ou en jaune, et ainsi de suite.

Bien sûr, cela nécessiterait probablement un terminal compatible (la plupart des terminaux modernes le sont), mais je pourrais revenir à l'original logging si la couleur n'est pas prise en charge.

Avez-vous une idée de la manière dont je peux obtenir une sortie colorée avec le module de journalisation ?

1 votes

Vous devez préciser que vous souhaitez une solution multiplateforme - à la fois Linux et Windows.

1 votes

Relatif si vous utilisez Eclipse/PyDev : Coloriser les logs dans la console eclipse

7 votes

Peut-être pouvez-vous également utiliser colorlog

209voto

airmind Points 1342

Je connaissais déjà les échappatoires de couleurs, je les ai utilisées dans mon invite bash il y a quelque temps. Merci quand même.
Ce que je voulais, c'était l'intégrer au module de journalisation, ce que j'ai finalement fait après quelques essais et erreurs.
Voici ce à quoi j'aboutis :

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)

Et pour l'utiliser, créez votre propre 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)

Juste au cas où quelqu'un d'autre en aurait besoin.

Faites attention si vous utilisez plus d'un enregistreur ou gestionnaire : ColoredFormatter modifie l'objet d'enregistrement, qui est transmis à d'autres gestionnaires ou propagé à d'autres enregistreurs. Si vous avez configuré des enregistreurs de fichiers, etc., vous ne voulez probablement pas avoir les couleurs dans les fichiers d'enregistrement. Pour éviter cela, il est probablement mieux de créer simplement une copie de record avec copy.copy() avant de manipuler l'attribut levelname, ou pour réinitialiser le levelname à la valeur précédente, avant de retourner la chaîne formatée (crédit à Michael dans les commentaires).

0 votes

Où sont définis les termes JAUNE, BLANC, BLEU, etc. ?

1 votes

@Swaroop - Il s'agit de codes d'échappement ANSI, que vous pouvez consulter sur Google ou trouver ici : fr.wikipedia.org/wiki/ANSI_escape_code ou alternativement pueblo.sourceforge.net/doc/manual/ansi_color_codes.html

57 votes

Je ne pense pas qu'il faille créer une sous-classe de logger juste pour cela - votre réponse est bonne dans la mesure où vous créez un Formatter et en spécifiant son utilisation sur un StreamHandler . Mais il n'y a pas besoin d'une sous-classe de logger. En fait, l'utilisation d'une classe de logger ajoute un handler à chaque logger créé, ce qui n'est pas ce que vous voulez typiquement.

173voto

xolox Points 586

Il y a des années, j'ai écrit un gestionnaire de flux coloré pour mon usage personnel. Puis je suis tombé sur cette page et j'ai trouvé une collection de bouts de code que les gens copient/collent :-(. Mon gestionnaire de flux ne fonctionne pour l'instant que sous UNIX (Linux, Mac OS X) mais l'avantage est que c'est un outil qui peut être utilisé par tous les utilisateurs. disponible sur PyPI (et GitHub ) et il est très simple à utiliser. Il possède également un mode syntaxique Vim :-). Dans le futur, je pourrais l'étendre pour qu'il fonctionne sous Windows.

Pour installer le paquet :

$ pip install coloredlogs

Pour confirmer que cela fonctionne :

$ coloredlogs --demo

Pour commencer avec votre propre code :

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

Le format de journal par défaut présenté dans l'exemple ci-dessus contient la date, l'heure, le nom d'hôte, le nom de l'enregistreur, le PID, le niveau de journal et le message du journal. Voici à quoi cela ressemble dans la pratique :

Screenshot of coloredlogs output

NOTE : Lorsque vous utilisez Git Bash avec MinTTY

Git Bash sur Windows a quelques bizarreries documentées : Winpty et Git Bash

Pour les codes d'échappement ANSI et pour la réécriture et l'animation des caractères à la manière de ncurses, vous devez préfixer les commandes par l'expression winpty .

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

2 votes

C'est drôle, j'allais justement ajouter un lien vers " pypi.python.org/pypi/coloredlogs/0.4.7 " dans ce fil !

1 votes

Pour une raison quelconque, je continue à obtenir AttributeError: 'module' object has no attribute 'install' lors de l'utilisation de coloredlogs.install() . Pouvez-vous confirmer cela avec la dernière version.

13 votes

C'est très beau. Malheureusement, il casse beaucoup de choses ; en particulier, il annule les appels à logging.basicConfig. Il est donc impossible d'utiliser un formateur personnalisé, par exemple.

95voto

rlafuente Points 93

Mise à jour : Parce que c'est une démangeaison que je voulais gratter depuis si longtemps, j'ai pris les devants et écrit une bibliothèque pour les personnes paresseuses comme moi qui veulent juste des moyens simples de faire les choses : zenlog

Colorlog est excellent pour cela. C'est disponible sur PyPI (et donc installable par le biais de pip install colorlog ) et est activement maintenu .

Voici un extrait rapide à copier-coller pour configurer la journalisation et imprimer des messages de journalisation de qualité :

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

Sortie :

Colorlog output

7 votes

Excellente réponse ; +1. L'exemple de code pourrait cependant être allégé (il y a trois appels à setLevel vraiment nécessaire ?)

1 votes

J'espérais trouver une réponse de ce genre si je passais en revue les réponses assez longtemps. J'espère que @airmind envisagera de faire de cette réponse la réponse acceptée, afin que les futures personnes intelligentes au travail puissent trouver ce qui semble être la meilleure bibliothèque avec une paresse optimale.

0 votes

J'ai juste upvoted ceci pour les exemples de messages de l'OUTPUT ^^

77voto

sorin Points 23747

Voici une solution qui devrait fonctionner sur n'importe quelle plateforme. Si ce n'est pas le cas, dites-le moi et je la mettrai à jour.

Comment cela fonctionne : sur les plateformes supportant les échappatoires ANSI, il les utilise (non-Windows) et sur Windows, il utilise les appels API pour changer les couleurs de la console.

Le script pirate la méthode logging.StreamHandler.emit de la bibliothèque standard en lui ajoutant un wrapper.

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 votes

J'ai écrit une classe StreamHandler basée sur cela, voir gist.github.com/mooware/a1ed40987b6cc9ab9c65 .

2 votes

Cela a fonctionné pour moi ! ligne 90 : devrait être args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal .

0 votes

J'aime cette solution. Je l'utilise actuellement. Je vois qu'il y a un attribut _set_color, y a-t-il un moyen de faire cela pour un message de journal spécifique ? modifier Je vois que ce n'est qu'un correctif pour les machines Windows. Ce serait bien d'ajouter un correctif personnalisé pour différents cas d'utilisation.

77voto

ABC Points 191

Solution rapide et sale pour les niveaux de logs prédéfinis et sans définir une nouvelle 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))

0 votes

@spiderplant0 import logging ; # collez le code de @ABC ; essayez-le avec logging.warning('this is a test'). Vous verrez la partie en majuscule de "WARNING : this is a test" colorée. Cela fonctionne sur linux seulement btw

3 votes

Comme seul le nom du niveau de journal est coloré, vous devez vous assurer que le nom du niveau de journal est bien imprimé sur la console. Cela ne se produit pas d'emblée pour moi. Quelque chose de ce genre pourrait vous aider : logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s') Où bien sûr le %(levelnames)s est important.

5 votes

La solution la plus simple et la plus propre à appliquer et à comprendre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X