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?
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).
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:
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())
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))
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:
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
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)
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")
Sortie du terminal
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
Vous pouvez importer le module colorlog et utiliser son ColoredFormatter
pour coloriser les messages du journal.
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:
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)
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
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.
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)
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)
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 à:
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.
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)))`
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])
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")
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
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.
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'
},
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
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)
Logger("File Name").info("This shows green text")
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.