Comment puis-je colorer la sortie de journalisation Python?

353

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 standardisés).

Maintenant, Python a le loggingmodule, 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 n'importe où.

Existe-t-il un moyen de rendre la loggingsortie du module Python 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 loggingsortie d' origine si la couleur n'est pas prise en charge.

Avez-vous des idées sur la façon d'obtenir une sortie colorée avec le module de journalisation?

airmind
la source
1
Vous devez spécifier que vous souhaitez une solution multiplateforme - à la fois Linux et Windows.
sorin
1
Connexes si vous utilisez Eclipse / PyDev: Coloriser les journaux dans la console Eclipse
Tobias Kienzler
5
Peut-être que vous pouvez également utiliser colorlog
Ehtesh Choudhury
5
Vous pouvez également essayer chromalog que j'ai écrit pour prendre en charge tous les systèmes d'exploitation et les versions Python (2.7 et 3. *)
ereOn
1
Les solutions qui vident réellement les codes ANSI dans le fichier journal sont une mauvaise idée, elles vous surprendront lorsque vous attendez quelque chose dans six mois, mais oubliez d'autoriser les caractères ANSI dans votre modèle d'expression régulière. Il existe quelques solutions ci-dessous qui ajoutent la couleur lorsque vous consultez le journal, plutôt que lorsque le journal est écrit ...
Jonathan Hartley

Réponses:

192

Je connaissais déjà les fuites de couleur, je les ai utilisées dans mon invite bash il y a un moment. 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 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.

Soyez prudent si vous utilisez plusieurs enregistreurs ou gestionnaires: ColoredFormattermodifie 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 voulez probablement pas avoir les couleurs dans les fichiers journaux. Pour éviter cela, il est probablement préférable de simplement créer une copie de recordavec copy.copy()avant de manipuler l'attribut levelname, ou de réinitialiser le levelname à la valeur précédente, avant de renvoyer la chaîne formatée (crédit à Michael dans les commentaires).

airmind
la source
Où est défini JAUNE, BLANC, BLEU, etc.?
Swaroop CH
1
@Swaroop - Ce sont des codes d'échappement ANSI, que vous pouvez consulter sur Google, ou trouver ici: en.wikipedia.org/wiki/ANSI_escape_code , ou encore pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
Brian M Hunt
53
Je ne crois pas que vous devriez créer une sous-classe de logger juste pour cela - votre réponse est correcte en ce qui concerne la création d'un spécialisé Formatteret la spécification de son utilisation sur un StreamHandler. Mais il n'y a pas besoin d'une sous-classe d'enregistreurs. En fait, l'utilisation d'une classe d'enregistreur ajoute un gestionnaire à chaque enregistreur créé, ce qui n'est généralement pas ce que vous souhaitez.
Vinay Sajip
6
Une note latérale à ColoredFormatter. Il 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 journaux. Pour éviter cela, il est probablement préférable de créer simplement une copie de recordaveccopy.copy() avant de manipuler l'attribut levelname, ou de réinitialiser le levelname à la valeur précédente, avant de renvoyer la chaîne formatée.
Michael
149

Il y a des années, j'ai écrit un gestionnaire de flux coloré pour mon propre usage. Ensuite, je suis tombé sur cette page et j'ai trouvé une collection d'extraits de code que les gens copient / collent :-(. 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 très simple à utiliser. Il a également un mode de syntaxe Vim :-). À l'avenir, je pourrais l'étendre à 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 de l'enregistreur, le PID, le niveau de journal et le message de journal. Voici à quoi cela ressemble en pratique:

Capture d'écran de la sortie des journaux de couleur

REMARQUE: lors de l'utilisation de Git Bash avec MinTTY

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

Avec lesquels pour les codes d'échappement ANSI et pour la réécriture de caractères de style ncurses et les animations, vous devez préfixer les commandes avec winpty.

$ winpty coloredlogs --demo
$ winpty python your_colored_logs_script.py
xolox
la source
2
assez drôle, j'allais juste ajouter un lien vers " pypi.python.org/pypi/coloredlogs/0.4.7 " dans ce fil!
Iosu S.
1
Pour une raison quelconque, je continue à recevoir AttributeError: 'module' object has no attribute 'install'lors de l'utilisation coloredlogs.install(). Pouvez-vous confirmer cela avec la dernière version.
con-f-use du
11
Cela a l'air magnifique. Malheureusement, cela casse beaucoup de choses; en particulier, il annule les appels à logging.basicConfig. Cela rend impossible l'utilisation d'un formateur personnalisé, par exemple.
Clément
@ Clément: Deux questions (qui se chevauchent?): (1) Que voulez-vous dire exactement par "annule les appels à logging.basicConfig" et (2) quelle serait l'alternative? À la fois logging.basicConfig()et coloredlogs.install()installez un gestionnaire de flux qui se connecte à la console, donc sans "annuler", vous obtiendrez des messages en double ...
xolox
Je m'attendais à de la magie pour (1), ou (plus raisonnablement) à un moyen de dire coloredlogs.installquel format utiliser, comme dans le colorlogpaquet.
Clément
74

Voici une solution qui devrait fonctionner sur n'importe quelle plate-forme. S'il ne me le dit pas, je le mettrai à jour.

Comment cela fonctionne: sur la plate-forme prenant en charge les échappements ANSI les utilise (non Windows) et sur Windows, il utilise des appels API pour changer les couleurs de la console.

Le script pirate la méthode logging.StreamHandler.emit de la bibliothèque standard en y 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())
Sorin
la source
3
J'ai écrit une classe StreamHandler basée sur cela, voir gist.github.com/mooware/a1ed40987b6cc9ab9c65 .
mooware
2
cela a fonctionné pour moi! ligne 90: devrait l'être args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
Rasika Perera
J'aime cette solution. l'utiliser actuellement. Je vois qu'il y a un attribut _set_color, existe-t-il un moyen de le faire pour un message de journal spécifique? éditez , oh voyez ce n'est qu'un patch pour les machines Windows. serait bien d'ajouter de la coutume pour différents cas d'utilisation.
brizz
+1 pour la couleur ANSI. Dans xterm, vous pouvez même obtenir 256 couleurs à la fois et vous pouvez définir la palette dynamiquement! Notez cependant que tous les appels aux fonctions de journalisation doivent se trouver dans une définition de fonction pour éviter les problèmes potentiels de verrouillage d'importation lors de la journalisation en dehors d'une définition de fonction . Votre code semble généralement bon; juste ce petit peu TestColorer.pyme préoccupe.
personal_cloud
Il en résulte des codes de couleur au début et à la fin des messages de journal dans les fichiers journaux réels.
MehmedB
74

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

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

Voici un extrait rapide copiable et collable pour configurer la journalisation et imprimer des messages de journal d'apparence décente:

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

Production:

Sortie Colorlog

rlafuente
la source
4
Très bonne réponse; +1. L'exemple de code pourrait être coupé cependant (trois appels sont-ils setLevelvraiment nécessaires?)
Clément
1
J'espérais trouver une réponse comme celle-ci si je parcourais les réponses assez longtemps. ☺ J'espère que @airmind envisagera d'en faire la réponse acceptée, afin que les futurs professionnels du travail puissent trouver ce qui semble être la meilleure bibliothèque avec une paresse optimale. 😉
Michael Scheper
Je viens de voter pour les exemples de messages de la SORTIE ^^
Agustin Barrachina
69

Solution rapide et sale pour les niveaux de journal prédéfinis et sans définir 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))
abc
la source
@ spiderplant0 import logging; # collez le code de @ABC; essayez-le avec logging.warning ('ceci est un test'). Vous verrez la partie majuscule de "AVERTISSEMENT: c'est un test" colorée. Il fonctionne uniquement sur linux btw
Riccardo Galli
3
Étant donné que seul le nom du niveau de journalisation est coloré, vous devez vous assurer que le nom de niveau de journalisation est imprimé sur la console. Cela ne se produit pas pour moi. Quelque chose dans ce sens aidera: logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')où, bien sûr, %(levelnames)sc'est important.
Sebastian
4
La solution la plus simple et la plus propre à appliquer et à comprendre.
F. Santiago
1
Essayez simplement dans la console Linux. echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again". L' -eoption echo interprète "\ 033" comme une forme octale du symbole ASCII d'échappement. Ce symbole spécial fait que certains terminaux compatibles interprètent les caractères suivants (en char minclus) comme des commandes spéciales. en.wikipedia.org/wiki/ANSI_escape_code
eugene-bright
1
Amélioration mineure: mettez ce code à l'intérieur if sys.sdterr.isatty():. Dans ce cas, si vous redirigez la sortie vers un fichier, le fichier ne contiendra pas ces caractères d'échappement.
lesnik
36

Code 2020, aucun package supplémentaire requis, Python 3

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)

Instantiate logger

# create logger with 'spam_application'
logger = logging.getLogger("My_app")
logger.setLevel(logging.DEBUG)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

ch.setFormatter(CustomFormatter())

logger.addHandler(ch)

Et utilise!

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

Résultat entrez la description de l'image ici

La palette de couleurs entrez la description de l'image ici

Pour les fenêtres

Cette solution fonctionne sur Mac OS, terminaux IDE. On dirait que l'invite de commande de la fenêtre n'a pas de couleurs du tout par défaut. Voici des instructions pour les activer, que je n'ai pas essayé https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/

Sergey Pleshakov
la source
1
J'exécute le test (python 3.7, windows), mais la journalisation n'affiche pas les couleurs:←[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
constructeur
Cela ne fonctionne malheureusement pas.
Joe
2
J'ai tellement aimé cette réponse que j'ai fait un repo pour elle, avec quelques incréments et une feuille de triche de couleurs ansi.
Teodoro
@constructor où l'utilisez-vous? Console IDE? terminal Windows?
Sergey Pleshakov
@Joe, qu'est-ce qui ne fonctionne pas exactement? quel est votre environnement et quelles erreurs obtenez-vous? Je voudrais réviser la solution pour la faire fonctionner sur toutes les plateformes
Sergey Pleshakov
17

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

Cela n'a rien d'extraordinaire, mais il est très simple à utiliser et ne modifie pas l'objet d'enregistrement, ce qui évite de consigner les séquences d'échappement ANSI dans un fichier journal si un gestionnaire de fichiers est utilisé. Cela n'affecte pas la mise en forme du message du journal.

Si vous utilisez déjà le formateur du module de journalisation , tout ce que vous avez à faire pour obtenir des noms de niveau colorés est de remplacer le formateur de vos gestionnaires de conseil par le ColoredFormatter. Si vous connectez une application entière, vous devez uniquement effectuer cette opération pour l'enregistreur de niveau supérieur.

colour_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 borne

Sortie borne

contenu 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

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

J'espère que quelqu'un trouvera cela utile et ce n'est pas tout simplement trop pareil. :)

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

KCJ
la source
2
BTW pour ajouter des couleurs au message lui-même, ajoutez simplement cette ligne avant return:colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
The Godfather
15

J'ai mis à jour l'exemple des balises supportant airmind pour le premier 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
camillobruni
la source
Grande amélioration. Cependant, le commentaire superne s'applique qu'à une ancienne version de Python, je suppose? Étant donné que cette réponse date de 2010. Cela a bien fonctionné pour moi avec Python 2.7
Joakim
14

Vous pouvez importer le module Colorlog et l'utiliser ColoredFormatterpour coloriser les messages du journal.

Exemple

Plaque de chaudière pour 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 de journal, si le module colorlog est installé et si la sortie est réellement envoyée à un terminal. Cela évite que les séquences d'échappement ne soient écrites dans un fichier lorsque la sortie du journal est redirigée.

En outre, un schéma de couleurs personnalisé est configuré qui convient mieux 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')

Production:

entrez la description de l'image ici

maxschlepzig
la source
2
Peut également utiliser colorlog.basicConfigau lieu de logging.basicConfigqui a de bonnes valeurs par défaut
MarSoft
1
Pour mémoire, colorlog ne fonctionne pas toujours directement sur les plateformes Windows (comme spécifié, la dépendance colorama est requise). Même avec cela, j'ai eu du mal à le faire fonctionner dans Anaconda / Spyder env. Vous devrez peut-être spécifier colorama.init (strip = False) par exemple dans escape_code.py (comme indiqué dans ce fil github.com/spyder-ide/spyder/issues/1917 )
Matt-Mac-Muffin
11

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

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

pseudo
la source
Vous pouvez trouver une implémentation mise à jour dans cet essentiel (maintenu par l'auteur du blog). Je l'utilise et fonctionne très bien. Merci d'avoir partagé.
noisebleed
11

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

L'inconvénient de leur solution est qu'elle modifie le message, et parce que cela modifie le message de journal réel, tout autre gestionnaire recevra également le message modifié.

Cela a entraîné des fichiers journaux avec des codes couleur dans notre cas, 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 couleur 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)
Ramonster
la source
7

Il y a des tonnes de réponses. Mais personne ne parle de décorateurs. Voici donc 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 le wrapper pour prendre un colorargument pour définir dynamiquement la couleur du message en utilisantlogger.debug("message", color=GREY)

EDIT: Voici donc le décorateur adapté pour définir les couleurs lors 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)
couché
la source
6

Un autre remix mineur de l'approche d'Airmind qui garde 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)
gravitation
la source
5

Un outil simple mais très flexible pour colorier N'IMPORTE QUEL 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 l'expression régulière sera coloré avec color1, 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 d'expression régulière (parens) correspond à la date initiale dans le fichier journal, le deuxième groupe correspond à un nom de fichier python, un numéro de ligne et un nom de fonction, et le troisième groupe correspond au message de journal qui vient après. J'utilise également une séquence parallèle de «gras / normaux» ainsi que la séquence de couleurs. Cela ressemble à ceci:

fichier journal avec mise en forme colorée

Notez que les lignes ou parties de lignes qui ne correspondent à aucun de mes regex sont toujours répercutées, donc ce n'est pas comme 'grep --color' - rien n'est filtré hors de la sortie.

De toute évidence, cela est suffisamment flexible pour que vous puissiez l'utiliser avec n'importe quel processus, et pas seulement avec des fichiers journaux. Habituellement, je prépare un nouveau regex à 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, indépendamment de ce que je colore: journalisation, sortie de test, syntaxe mettant en évidence des extraits de code dans le terminal, etc.

Cela évite également de vider les codes ANSI dans le fichier journal lui-même, ce qui à mon humble avis est une mauvaise idée, car cela cassera des choses comme la recherche de modèles dans le fichier journal, sauf si vous vous souvenez toujours de faire correspondre les codes ANSI dans votre expression régulière grep.

Jonathan Hartley
la source
4
import logging
import sys

colors = {'pink': '\033[95m', 'blue': '\033[94m', 'green': '\033[92m', 'yellow': '\033[93m', 'red': '\033[91m',
      'ENDC': '\033[0m', 'bold': '\033[1m', 'underline': '\033[4m'}

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)


def str_color(color, data):
    return colors[color] + str(data) + colors['ENDC']

params = {'param1': id1, 'param2': id2}

logging.info('\nParams:' + str_color("blue", str(params)))`
Serhii Khachko
la source
+1 Bel exemple avec les [9*mcodes des couleurs ANSI "vives"! PS votre dernière ligne me préoccupe un peu car on ne sait pas encore si la journalisation en dehors d'une définition de fonction est sûre en Python .
personal_cloud
2

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

Le problème avec lequel j'ai eu du mal était de configurer correctement le 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
pseudo
la source
Il était censé être un pseudo-code (car _set_colour est également manquant), mais a ajouté quelque chose. Le problème le plus difficile était de savoir comment fixer correctement le formateur.
Nick
Voir la solution "cric plombier". Je pense que c'est une meilleure façon de résoudre le problème (c'est-à-dire que le gestionnaire devrait faire la colorisation). stackoverflow.com/questions/384076/…
Nick
1

Bien que les autres solutions semblent bonnes, elles présentent certains problèmes. Certains colorent les lignes entières, ce qui n'est parfois pas souhaité et certains omettent toute configuration que vous pourriez avoir tous ensemble. La solution ci-dessous n'affecte rien d'autre 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')

Production

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

Comme vous le voyez, tout le reste est toujours sorti et reste dans sa couleur initiale. Si vous souhaitez modifier autre chose que le message, vous pouvez simplement passer les codes de couleur à log_formatdans l'exemple.

Pithikos
la source
lorsque je l'utilise, les messages sont imprimés deux fois. est-ce que tu sais pourquoi?
Validus Oculus
@ pourriez-vous élaborer? À savoir que vous voulez dire quelque chose comme [17:01:36]:WARNING:this should be yellowthis should be yellowou une ligne complète imprimée deux fois?
Pithikos du
Désolé pour la brièveté du commentaire. Le premier s'est produit: [17:01:36]: AVERTISSEMENT: cela devrait être jaune \ nceci devrait être jaune. Cependant, je veux seulement que celui formaté soit affiché, sinon il ressemble à une poubelle en raison de journaux redondants.
Validus Oculus
@ MuratKarakuş ne sais pas pourquoi cela se produit sans avoir une vue complète sur la mise en œuvre. Si vous utilisez un enregistreur personnalisé, vous interférez peut-être à un moment donné? Une solution rapide pourrait être de supprimer le 7s:%(message)sdu log_format.
Pithikos du
1

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

Certains contenus proviennent (avec modification) de: l'article 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 toute la ligne:

class ColorizingStreamHandler(logging.StreamHandler):

    BLACK = '\033[0;30m'
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    BROWN = '\033[0;33m'
    BLUE = '\033[0;34m'
    PURPLE = '\033[0;35m'
    CYAN = '\033[0;36m'
    GREY = '\033[0;37m'

    DARK_GREY = '\033[1;30m'
    LIGHT_RED = '\033[1;31m'
    LIGHT_GREEN = '\033[1;32m'
    YELLOW = '\033[1;33m'
    LIGHT_BLUE = '\033[1;34m'
    LIGHT_PURPLE = '\033[1;35m'
    LIGHT_CYAN = '\033[1;36m'
    WHITE = '\033[1;37m'

    RESET = "\033[0m"

    def __init__(self, *args, **kwargs):
        self._colors = {logging.DEBUG: self.DARK_GREY,
                        logging.INFO: self.RESET,
                        logging.WARNING: self.BROWN,
                        logging.ERROR: self.RED,
                        logging.CRITICAL: self.LIGHT_RED}
        super(ColorizingStreamHandler, self).__init__(*args, **kwargs)

    @property
    def is_tty(self):
        isatty = getattr(self.stream, 'isatty', None)
        return isatty and isatty()

    def emit(self, record):
        try:
            message = self.format(record)
            stream = self.stream
            if not self.is_tty:
                stream.write(message)
            else:
                message = self._colors[record.levelno] + message + self.RESET
                stream.write(message)
            stream.write(getattr(self, 'terminator', '\n'))
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code
ZetaSyanthis
la source
1

Il s'agit d'une énumération contenant les codes de couleur:

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'

Cela peut être appliqué aux noms de chaque niveau de journal. Sachez qu'il s'agit d'un hack 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 journal

%(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'
        },
Joe Heffer
la source
1

FriendlyLog est une autre alternative. Il fonctionne avec Python 2 & 3 sous Linux, Windows et MacOS.

SebiSebi
la source
Dans l'attente du nouveau PR pour réduire l'encombrement du chemin du module
mbspark
1

Qu'en est-il de surligner également les arguments du message avec des couleurs alternées, en plus de la coloration par niveau? J'ai récemment écrit un code simple pour cela. Un autre avantage est que l'appel de journal est effectué avec une mise en forme de type accolade Python 3. ("{}" ).

Voir le dernier code et les exemples ici: https://github.com/davidohana/colargulog

Exemple de code d'enregistrement:

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)

Production:

entrez la description de l'image ici

La mise en oeuvre:

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

Written by [email protected]
License: Apache-2.0
"""

import logging
import logging.handlers
import re


class ColorCodes:
    grey = "\x1b[38;21m"
    green = "\x1b[1;32m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    blue = "\x1b[1;34m"
    light_blue = "\x1b[1;36m"
    purple = "\x1b[1;35m"
    reset = "\x1b[0m"


class ColorizedArgsFormatter(logging.Formatter):
    arg_colors = [ColorCodes.purple, ColorCodes.light_blue]
    level_fields = ["levelname", "levelno"]
    level_to_color = {
        logging.DEBUG: ColorCodes.grey,
        logging.INFO: ColorCodes.green,
        logging.WARNING: ColorCodes.yellow,
        logging.ERROR: ColorCodes.red,
        logging.CRITICAL: ColorCodes.bold_red,
    }

    def __init__(self, fmt: str):
        super().__init__()
        self.level_to_formatter = {}

        def add_color_format(level: int):
            color = ColorizedArgsFormatter.level_to_color[level]
            _format = fmt
            for fld in ColorizedArgsFormatter.level_fields:
                search = "(%\(" + fld + "\).*?s)"
                _format = re.sub(search, f"{color}\\1{ColorCodes.reset}", _format)
            formatter = logging.Formatter(_format)
            self.level_to_formatter[level] = formatter

        add_color_format(logging.DEBUG)
        add_color_format(logging.INFO)
        add_color_format(logging.WARNING)
        add_color_format(logging.ERROR)
        add_color_format(logging.CRITICAL)

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        msg = record.msg
        msg = msg.replace("{", "_{{")
        msg = msg.replace("}", "_}}")
        placeholder_count = 0
        # add ANSI escape code for next alternating color before each formatting parameter
        # and reset color after it.
        while True:
            if "_{{" not in msg:
                break
            color_index = placeholder_count % len(ColorizedArgsFormatter.arg_colors)
            color = ColorizedArgsFormatter.arg_colors[color_index]
            msg = msg.replace("_{{", color + "{", 1)
            msg = msg.replace("_}}", "}" + ColorCodes.reset, 1)
            placeholder_count += 1

        record.msg = msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        formatter = self.level_to_formatter.get(record.levelno)
        self.rewrite_record(record)
        formatted = formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted


class BraceFormatStyleFormatter(logging.Formatter):
    def __init__(self, fmt: str):
        super().__init__()
        self.formatter = logging.Formatter(fmt)

    @staticmethod
    def is_brace_format_style(record: logging.LogRecord):
        if len(record.args) == 0:
            return False

        msg = record.msg
        if '%' in msg:
            return False

        count_of_start_param = msg.count("{")
        count_of_end_param = msg.count("}")

        if count_of_start_param != count_of_end_param:
            return False

        if count_of_start_param != len(record.args):
            return False

        return True

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        record.msg = record.msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        self.rewrite_record(record)
        formatted = self.formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted
dux2
la source
0

Utilisez pyfancy .

Exemple:

print(pyfancy.RED + "Hello Red!" + pyfancy.END)
WebMaster
la source
La question était de modifier la loggingfonctionnalité pour utiliser une bibliothèque de coloriage distincte.
Infected Drake
0

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 depuis votre __main__fonction. J'ai quelque chose comme ça là-bas:

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.

yucer
la source
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)

Usage

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

estifanos gebrehiwot
la source
Pour la console, vous pouvez laisser de côté le nom de fichier ou simplement le nom de fichier = '' devrait fonctionner. modifiez basicConfig pour inclure d'autres propriétés comme le numéro de fichier, le module ..
estifanos gebrehiwot
0

La solution suivante fonctionne uniquement avec python 3, mais pour moi, cela semble plus clair.

L'idée est d'utiliser la fabrique d'enregistrements de journaux pour ajouter des attributs «colorés» aux journaux des objets d'enregistrement et d'utiliser ces attributs «colorés» au format de journal.

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

Vous pouvez facilement modifier cet exemple pour créer d'autres attributs colorés (par exemple, message_c), puis utiliser ces attributs pour obtenir du texte coloré (uniquement) où vous le souhaitez.

(astuce pratique que j'ai découverte récemment: j'ai un fichier avec des journaux de débogage colorés et chaque fois que je veux augmenter temporairement le niveau de journal de mon application, je viens tail -fdu fichier journal dans un terminal différent et voir les journaux de débogage à l'écran sans changer de configuration et redémarrer l'application )

lesnik
la source
0

Ceci est une autre variante Python3 de l'exemple d'Airmind. Je voulais des fonctionnalités spécifiques que je ne voyais pas dans les autres exemples

  • utilisez des couleurs pour le terminal mais n'écrivez pas de caractères non imprimables dans les gestionnaires de fichiers (j'ai défini 2 formateurs pour cela)
  • possibilité de remplacer la couleur d'un message de journal spécifique
  • configurer l'enregistreur à partir d'un fichier (yaml dans ce cas)

Remarques: J'ai utilisé colorama mais vous pouvez le modifier afin que ce ne soit pas obligatoire. Aussi pour mes tests, je ne faisais qu'exécuter un fichier python, donc ma classe est dans le module. __main__Vous devrez changer (): __main__.ColoredFormatterpour quel que soit votre module.

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

production:

production

Scott
la source