web-dev-qa-db-fra.com

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, probablement à cause de son système de journalisation (car tous les messages étaient normalisés).

Maintenant, Python a le module logging, qui vous permet de spécifier de nombreuses options pour personnaliser la sortie. Donc, j'imagine que quelque chose de similaire serait possible avec Python, mais je ne peux pas savoir comment faire cela nulle part.

Est-il possible de rendre le module Python logging en couleur?

Ce que je veux (par exemple) des erreurs en rouge, des messages de débogage en bleu ou jaune, etc.

Bien sûr, cela nécessiterait probablement un terminal compatible (la plupart des terminaux modernes le sont); mais je pourrais revenir à la sortie originale logging si la couleur n'est pas supportée.

Des idées comment je peux obtenir une sortie colorée avec le module de journalisation?

313
airmind

Je connaissais déjà les échappées de couleurs, je les ai utilisées dans mon invite Bash il y a quelque temps. Merci quand même.
Je voulais l’intégrer au module de journalisation, ce que j’ai finalement fait après quelques essais et erreurs.
Voici ce que je me retrouve avec:

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 enregistreur:

# 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 plusieurs enregistreurs ou gestionnaires: ColoredFormatter modifie l'objet d'enregistrement, qui est ensuite transmis à d'autres gestionnaires ou propagé à d'autres enregistreurs. Si vous avez configuré des enregistreurs de fichiers, etc., vous ne souhaiterez probablement pas que les couleurs soient dans les fichiers journaux. Pour éviter cela, il est probablement préférable de simplement créer une copie de record avec copy.copy() avant de manipuler l'attribut levelname, ou de réinitialiser le nom de niveau à la valeur précédente, avant de retourner la chaîne formatée (crédit - Michael dans les commentaires).

178
airmind

Il y a des années, j'ai écrit un gestionnaire de flux de couleur pour mon propre usage. Je suis ensuite tombé sur cette page et ai trouvé une collection d’extraits de code copiés/collés : . Mon gestionnaire de flux ne fonctionne actuellement que sous UNIX (Linux, Mac OS X), mais l’avantage est qu’il est (disponible sur PyPI (et GitHub ) et il est extrêmement simple à utiliser. Il dispose également d'un mode de syntaxe Vim :-). À l'avenir, je pourrais l'étendre pour qu'il fonctionne sous Windows.

Pour installer le package:

$ 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 indiqué dans l'exemple ci-dessus contient la date, l'heure, le nom d'hôte, le nom du consignateur, le PID, le niveau de journalisation et le message de journalisation. Voici à quoi cela ressemble dans la pratique:

Screenshot of coloredlogs output

127
xolox

Voici une solution qui devrait fonctionner sur n'importe quelle plate-forme. Si cela ne suffit pas, dites-le-moi et je le mettrai à jour.

Comment cela fonctionne: sur la plate-forme prenant en charge les échappements ANSI, c'est leur utilisation (non-Windows) et sous Windows, elle utilise des appels d'API pour modifier 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())
70
sorin

Solution rapide et incorrecte pour les niveaux de journalisation prédéfinis et sans définition de 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))
62
ABC

Mise à jour : Parce que c'est une démangeaison que je voulais gratter depuis si longtemps, je suis allé de l'avant et j'ai écrit une bibliothèque pour des paresseux comme moi qui je veux juste des façons simples de faire les choses: zenlog

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

Voici un extrait de copie et de copie facile à coller pour configurer la journalisation et imprimer des messages de journal de bonne apparence:

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

59
rlafuente

J'ai mis à jour l'exemple à partir des étiquettes supportant airmind pour l'avant-plan et l'arrière-plan. Utilisez simplement les variables de couleur $ BLACK - $ WHITE dans votre chaîne de formatage de journal. Pour définir l'arrière-plan, utilisez simplement $ 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

Alors maintenant, vous pouvez simplement faire ce qui suit dans votre fichier de configuration:

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

Eh bien, je suppose que je pourrais aussi bien ajouter ma variante de l'enregistreur coloré.

Cela n'a rien d'extraordinaire, mais il est très simple à utiliser et ne modifie pas l'objet d'enregistrement, évitant ainsi de consigner les séquences d'échappement ANSI dans un fichier journal si un gestionnaire de fichiers est utilisé. Cela n’affecte pas le formatage des messages du journal.

Si vous utilisez déjà le Formatter du module de journalisation , tout ce que vous avez à faire pour obtenir des noms de niveaux colorés est de remplacer vos gestionnaires d’avocats Formatter par le ColoredFormatter. Si vous enregistrez une application entière, vous ne devez le faire que pour l'enregistreur de niveau supérieur.

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)

Exemple d'utilisation

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

Résultats

Sortie du terminal

Terminal output

app.log contenu

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

Bien sûr, vous pouvez obtenir autant de fantaisie que vous le souhaitez avec le formatage du terminal et les sorties du fichier journal. Seul le niveau de journalisation sera colorisé.

J'espère que quelqu'un trouvera cela utile et que ce ne sera pas trop de la même chose. :)

Les exemples de fichiers Python peuvent être téléchargés à partir de ce fichier GitHub Gist: https://Gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd

15
KCJ

Vous pouvez importer le module colorlog et utiliser son ColoredFormatter pour coloriser les messages du journal.

Exemple

Plaque de cuisson pour le module 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__)

Le code active uniquement les couleurs dans les messages du journal, si le module colorlog est installé et si la sortie est réellement transmise à un terminal. Cela évite que des séquences d'échappement ne soient écrites dans un fichier lorsque la sortie du journal est redirigée.

En outre, une palette de couleurs personnalisée est mieux adaptée aux terminaux avec un fond sombre.

Quelques exemples d'appels de journalisation:

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

Sortie:

enter image description here

14
maxschlepzig

J'ai modifié l'exemple original fourni par Sorin et sous-classé StreamHandler en un ColorizedConsoleHandler.

L'inconvénient de leur solution est qu'elle modifie le message et, comme cela modifie le message journal actuel, tous les autres gestionnaires recevront également le message modifié.

Cela a abouti à des fichiers journaux contenant des codes de couleur, car nous utilisons plusieurs enregistreurs.

La classe ci-dessous ne fonctionne que sur les plates-formes prenant en charge ansi, mais il devrait être trivial d'y ajouter les codes de couleurs de Windows.

import copy
import logging


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

Regardez la solution suivante. Le gestionnaire de flux doit être la chose qui fait la coloration, vous avez alors la possibilité de colorer des mots plutôt que la ligne entière (avec le formateur).

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

10
Nick

Il existe maintenant un module PyPi publié pour une sortie de journalisation colorée personnalisable:

https://pypi.python.org/pypi/Rainbow_logging_handler/

et

https://github.com/laysakura/Rainbow_logging_handler

  • Prend en charge Windows

  • Prend en charge Django

  • Couleurs personnalisables

Comme ceci est distribué sous la forme d'un Python Egg, il est très facile à installer pour n'importe quelle application Python.

9
Mikko Ohtamaa

Il y a des tonnes de réponses. Mais personne ne parle de décorateurs. Alors voici le mien.

Parce que c'est beaucoup plus simple.

Il n'est pas nécessaire d'importer quoi que ce soit, ni d'écrire une sous-classe:

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

Cela définit les erreurs en rouge, les messages de débogage en bleu, etc. Comme demandé dans la question.

Nous pourrions même adapter l'encapsuleur pour prendre un argument color afin de définir dynamiquement la couleur du message à l'aide de logger.debug("message", color=GREY).

EDIT: Alors voici le décorateur adapté pour définir les couleurs au moment de l'exécution:

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

Un autre remix mineur de l'approche de airmind qui maintient tout dans une 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)

Pour utiliser attacher le formateur à un gestionnaire, quelque chose comme:

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

Un outil simple mais très flexible pour colorer TOUT texte terminal est ' colout '.

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

Où tout texte dans la sortie de 'myprocess' qui correspond au groupe 1 de la regex sera coloré avec color1, le groupe 2 avec color2, etc.

Par exemple:

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

c'est-à-dire que le premier groupe de regex (parent) correspond à la date initiale dans le fichier journal, le deuxième groupe correspond à un nom de fichier python, _ numéro de ligne et nom de fonction, et le troisième groupe correspond au message de journal qui suit. J'utilise également une séquence parallèle de "gras/normales" ainsi que la séquence de couleurs. Cela ressemble à:

logfile with colored formatting

Notez que les lignes ou parties de lignes qui ne correspondent à aucune de mes expressions rationnelles sont toujours répercutées. Ce n'est donc pas comme 'grep --color' - rien n'est filtré dans la sortie.

Évidemment, cela est suffisamment flexible pour que vous puissiez l’utiliser avec n’importe quel processus, pas seulement avec les fichiers journaux. Habituellement, je crée simplement une nouvelle expression rationnelle à la volée chaque fois que je veux coloriser quelque chose. Pour cette raison, je préfère colout à tout outil de coloration de fichier journal personnalisé, car je n'ai besoin d'apprendre qu'un seul outil, quel que soit le processus de coloration: journalisation, sortie de test, coloration syntaxique d'extraits de code dans le terminal, etc.

Cela évite également le vidage des codes ANSI dans le fichier journal lui-même, ce qui est une mauvaise idée, car il va casser des choses telles que le grepping des motifs dans le fichier journal sauf si vous vous rappelez toujours de faire correspondre les codes ANSI dans votre regex grep.

5
Jonathan Hartley
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)))`
5
Serhii Khachko

Voici ma solution:

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

Code 2019 frais facile à ajouter sans paquets supplémentaires

Définir une 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)

Instanciez l'enregistreur

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

Et utilisez!

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

Résultat enter image description here

2
Sergey Pleshakov

Le problème qui me posait problème était la configuration correcte du formateur:

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)

Et puis utiliser:

import coloured_log
import logging

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

coloured_log.close()    # restore colours
1
Nick

Alors que les autres solutions semblent bien, ils ont quelques problèmes. Certains colorent les lignes entières, ce qui n'est pas toujours souhaitable, d'autres omettent toute configuration que vous pourriez avoir tous ensemble. La solution ci-dessous n'affecte que le message lui-même.

Code

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)

Exemple

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

sortie

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

Comme vous le voyez, tout le reste est encore imprimé et reste dans sa couleur initiale. Si vous souhaitez modifier autre chose que le message, vous pouvez simplement transmettre les codes de couleur à log_format dans l'exemple.

1
Pithikos

Ceci est un Enum contenant les codes de couleur:

class TerminalColour:
    """
    Terminal colour formatting codes
    """
    # https://stackoverflow.com/questions/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'

Ceci peut être appliqué aux noms de chaque niveau de journalisation. Sachez qu'il s'agit d'un piratage monstrueux.

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

Notez que votre formateur de journal doit inclure le nom du niveau de journalisation.

%(levelname)

par exemple:

    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
Joe Heffer

J'ai deux soumissions à ajouter, l'une colorisant uniquement le message (ColoredFormatter) et l'autre colorisant la ligne entière (ColorizingStreamHandler). Ceux-ci incluent également plus de codes de couleur ANSI que les solutions précédentes.

Une partie du contenu provient (avec modification) de: Le message ci-dessus, et http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .

Colorise le message uniquement:

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

Colorise la ligne entière:

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

Usage

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

0
estifanos gebrehiwot

Juste une autre solution, aux couleurs de ZetaSyanthis:

def config_log(log_level):

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

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

appelez-le une fois à partir de votre fonction __main__. J'ai quelque chose comme ça là:

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

il vérifie également que la sortie est une console, sinon aucune couleur n'est utilisée.

0
yucer
0
herrk

Utilisez pyfancy .

Exemple:

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