Jak mogę pokolorować dane wyjściowe logowania do Pythona?


352

Jakiś czas temu widziałem aplikację Mono z kolorowym wyjściem, prawdopodobnie ze względu na jej system logów (ponieważ wszystkie wiadomości były znormalizowane).

Teraz Python ma loggingmoduł, który pozwala określić wiele opcji dostosowywania danych wyjściowych. Więc wyobrażam sobie, że coś podobnego byłoby możliwe w Pythonie, ale nigdzie nie mogę się dowiedzieć, jak to zrobić.

Czy jest jakiś sposób, aby loggingmoduł Pythona wyświetlał w kolorze?

Czego chcę (na przykład) błędy na czerwono, komunikaty debugowania na niebiesko lub żółto i tak dalej.

Oczywiście wymagałoby to prawdopodobnie zgodnego terminala (większość współczesnych terminali); ale mógłbym wrócić do oryginalnego loggingwyjścia, jeśli kolor nie jest obsługiwany.

Jakieś pomysły, jak uzyskać kolorowe wydruki za pomocą modułu logowania?


1
Powinieneś określić, że chcesz mieć rozwiązanie wieloplatformowe - zarówno Linux, jak i Windows.
sorin,

1
Powiązane, jeśli używasz Eclipse / PyDev: Colorize logs in eclipse console
Tobias Kienzler

5
Być może możesz także użyć colorlog
Ehtesh Choudhury

5
Możesz także wypróbować chromalog, który napisałem do obsługi wszystkich systemów operacyjnych i wersji Pythona (2.7 i 3. *)
ereOn

1
Rozwiązania, które w rzeczywistości wyrzucają kody ANSI do pliku dziennika, są złym pomysłem, złapią cię, gdy będziesz szukał czegoś za sześć miesięcy, ale zapomnij o uwzględnieniu znaków ANSI we wzorcu wyrażeń regularnych. Istnieje kilka rozwiązań, które dodają kolor podczas przeglądania dziennika, a nie podczas zapisywania dziennika ...
Jonathan Hartley

Odpowiedzi:


192

Wiedziałem już o ucieczkach kolorów, użyłem ich w zachęcie do bashu jakiś czas temu. W każdym razie dzięki.
Chciałem zintegrować go z modułem rejestrowania, co ostatecznie zrobiłem po kilku próbach i błędach.
Oto co kończę z:

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)

Aby z niego skorzystać, stwórz własny Logger:

# Custom logger class with multiple destinations
class ColoredLogger(logging.Logger):
    FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s]  %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)"
    COLOR_FORMAT = formatter_message(FORMAT, True)
    def __init__(self, name):
        logging.Logger.__init__(self, name, logging.DEBUG)                

        color_formatter = ColoredFormatter(self.COLOR_FORMAT)

        console = logging.StreamHandler()
        console.setFormatter(color_formatter)

        self.addHandler(console)
        return


logging.setLoggerClass(ColoredLogger)

Na wypadek, gdyby ktoś tego potrzebował.

Uważaj, jeśli używasz więcej niż jednego programu rejestrującego lub procedury obsługi: ColoredFormatterzmienia obiekt rekordu, który jest przekazywany dalej do innych programów obsługi lub propagowany do innych programów rejestrujących. Jeśli skonfigurowałeś rejestratory plików itp., Prawdopodobnie nie chcesz mieć kolorów w plikach dziennika. Aby tego uniknąć, prawdopodobnie najlepiej po prostu utworzyć kopię recordz copy.copy()przed modyfikacją atrybutu levelname lub zresetować levelname do poprzedniej wartości, przed zwróceniem sformatowanego ciągu (podziękowania dla Michaela w komentarzach).


Gdzie jest zdefiniowany ŻÓŁTY, BIAŁY, NIEBIESKI itp.?
Swaroop CH,

1
@Swaroop - Są to kody ucieczkowe ANSI, które możesz przeczytać w Google, lub znaleźć tutaj: en.wikipedia.org/wiki/ANSI_escape_code lub alternatywnie pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
Brian M. , Polowanie

53
Nie sądzę, że powinieneś utworzyć właśnie podklasę programu rejestrującego - twoja odpowiedź jest w porządku, jeśli chodzi o utworzenie specjalizacji Formatteri określenie jej zastosowania na StreamHandler. Ale nie ma potrzeby tworzenia podklasy rejestratora. W rzeczywistości użycie klasy logger dodaje moduł obsługi do każdego utworzonego loggera, co nie jest tym, czego zwykle chcesz.
Vinay Sajip


6
Jedna uwaga do ColoredFormatter. Zmienia obiekt rekordu, który jest przekazywany dalej do innych programów obsługi lub propagowany do innych programów rejestrujących. Jeśli skonfigurowałeś rejestratory plików itp., Prawdopodobnie nie chcesz mieć kolorów w plikach dziennika. Aby tego uniknąć, prawdopodobnie najlepiej jest po prostu utworzyć kopię recordz copy.copy()przed manipulowaniem atrybutem levelname lub zresetować levelname do poprzedniej wartości przed zwróceniem sformatowanego ciągu.
Michael

148

Wiele lat temu napisałem kolorowy moduł obsługi strumienia na własny użytek. Potem natknąłem się na tę stronę i znalazłem kolekcję fragmentów kodu, które ludzie kopiują / wklejają :-(. Mój moduł obsługi strumienia działa obecnie tylko w systemie UNIX (Linux, Mac OS X), ale zaletą jest to, że jest dostępny w PyPI (i GitHub ) i jest bardzo prosty w użyciu. Posiada również tryb składni Vima :-). W przyszłości mogę rozszerzyć go na system Windows.

Aby zainstalować pakiet:

$ pip install coloredlogs

Aby potwierdzić, że działa:

$ coloredlogs --demo

Aby rozpocząć z własnym kodem:

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

Domyślny format dziennika pokazany w powyższym przykładzie zawiera datę, godzinę, nazwę hosta, nazwę rejestratora, PID, poziom dziennika i komunikat dziennika. Tak to wygląda w praktyce:

Zrzut ekranu przedstawiający kolorowe logi

UWAGA: Podczas korzystania z Git Bash w / MinTTY

Git Bash w Windows ma kilka udokumentowanych dziwactw: Winpty i Git Bash

Które w przypadku kodów ucieczkowych ANSI oraz przepisywania znaków i animacji przez ncurses należy poprzedzać poleceniami winpty.

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

2
dość zabawne, właśnie zamierzałem dodać link do „ pypi.python.org/pypi/coloredlogs/0.4.7 ” w tym wątku!
Iosu S.

1
Z jakiegoś powodu ciągle otrzymuję AttributeError: 'module' object has no attribute 'install'podczas używania coloredlogs.install(). Czy możesz to potwierdzić za pomocą najnowszej wersji.
con-f-use

11
To wygląda pięknie. Niestety, psuje wiele rzeczy; w szczególności unieważnia wywołania dziennika.basicConfig. Uniemożliwia to na przykład użycie niestandardowego formatyzatora.
Clément

@ Clément: Dwa (pokrywające się?) Pytania: (1) Co dokładnie rozumiesz przez „unieważnia połączenia z logowaniem.basicConfig” i (2) jaka byłaby alternatywa? Zarówno logging.basicConfig()i coloredlogs.install()zainstalować obsługi strumienia, który loguje do konsoli, więc bez „unieważnienie”, co można uzyskać duplikaty wiadomości ...
xolox

Spodziewałem się magii dla (1) lub (bardziej uzasadnionego) sposobu na określenie, coloredlogs.installktórego formatu użyć, jak w colorlogpakiecie.
Clément,

74

Oto rozwiązanie, które powinno działać na dowolnej platformie. Jeśli to nie tylko mi powie, a ja go zaktualizuję.

Jak to działa: na platformie obsługującej ucieczki ANSI używa ich (nie Windows), a na Windows używa wywołań API do zmiany kolorów konsoli.

Skrypt włamuje się do metody logging.StreamHandler.emit ze standardowej biblioteki, dodając do niej opakowanie.

TestColorer.py

# Usage: add Colorer.py near you script and import it.
import logging
import Colorer

logging.warn("a warning")
logging.error("some error")
logging.info("some info")

Colorer.py

#!/usr/bin/env python
# encoding: utf-8
import logging
# now we patch Python code to add color support to logging.StreamHandler
def add_coloring_to_emit_windows(fn):
        # add methods we need to the class
    def _out_handle(self):
        import ctypes
        return ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
    out_handle = property(_out_handle)

    def _set_color(self, code):
        import ctypes
        # Constants from the Windows API
        self.STD_OUTPUT_HANDLE = -11
        hdl = ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
        ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, code)

    setattr(logging.StreamHandler, '_set_color', _set_color)

    def new(*args):
        FOREGROUND_BLUE      = 0x0001 # text color contains blue.
        FOREGROUND_GREEN     = 0x0002 # text color contains green.
        FOREGROUND_RED       = 0x0004 # text color contains red.
        FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
        FOREGROUND_WHITE     = FOREGROUND_BLUE|FOREGROUND_GREEN |FOREGROUND_RED
       # winbase.h
        STD_INPUT_HANDLE = -10
        STD_OUTPUT_HANDLE = -11
        STD_ERROR_HANDLE = -12

        # wincon.h
        FOREGROUND_BLACK     = 0x0000
        FOREGROUND_BLUE      = 0x0001
        FOREGROUND_GREEN     = 0x0002
        FOREGROUND_CYAN      = 0x0003
        FOREGROUND_RED       = 0x0004
        FOREGROUND_MAGENTA   = 0x0005
        FOREGROUND_YELLOW    = 0x0006
        FOREGROUND_GREY      = 0x0007
        FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.

        BACKGROUND_BLACK     = 0x0000
        BACKGROUND_BLUE      = 0x0010
        BACKGROUND_GREEN     = 0x0020
        BACKGROUND_CYAN      = 0x0030
        BACKGROUND_RED       = 0x0040
        BACKGROUND_MAGENTA   = 0x0050
        BACKGROUND_YELLOW    = 0x0060
        BACKGROUND_GREY      = 0x0070
        BACKGROUND_INTENSITY = 0x0080 # background color is intensified.     

        levelno = args[1].levelno
        if(levelno>=50):
            color = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY 
        elif(levelno>=40):
            color = FOREGROUND_RED | FOREGROUND_INTENSITY
        elif(levelno>=30):
            color = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
        elif(levelno>=20):
            color = FOREGROUND_GREEN
        elif(levelno>=10):
            color = FOREGROUND_MAGENTA
        else:
            color =  FOREGROUND_WHITE
        args[0]._set_color(color)

        ret = fn(*args)
        args[0]._set_color( FOREGROUND_WHITE )
        #print "after"
        return ret
    return new

def add_coloring_to_emit_ansi(fn):
    # add methods we need to the class
    def new(*args):
        levelno = args[1].levelno
        if(levelno>=50):
            color = '\x1b[31m' # red
        elif(levelno>=40):
            color = '\x1b[31m' # red
        elif(levelno>=30):
            color = '\x1b[33m' # yellow
        elif(levelno>=20):
            color = '\x1b[32m' # green 
        elif(levelno>=10):
            color = '\x1b[35m' # pink
        else:
            color = '\x1b[0m' # normal
        args[1].msg = color + args[1].msg +  '\x1b[0m'  # normal
        #print "after"
        return fn(*args)
    return new

import platform
if platform.system()=='Windows':
    # Windows does not support ANSI escapes and we are using API calls to set the console color
    logging.StreamHandler.emit = add_coloring_to_emit_windows(logging.StreamHandler.emit)
else:
    # all non-Windows platforms are supporting ANSI escapes so we use them
    logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
    #log = logging.getLogger()
    #log.addFilter(log_filter())
    #//hdlr = logging.StreamHandler()
    #//hdlr.setFormatter(formatter())

3
Na tej podstawie napisałem klasę StreamHandler, patrz gist.github.com/mooware/a1ed40987b6cc9ab9c65 .
mooware

2
to zadziałało dla mnie! linia 90: powinna być args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
Rasika Perera

Podoba mi się to rozwiązanie. używam go obecnie. Widzę, że istnieje atrybut _set_color, czy istnieje sposób, aby to zrobić dla określonego komunikatu dziennika? edytuj , och, to tylko łatka na maszyny z Windows. byłoby miło dodać niestandardowe dla różnych przypadków użycia.
brizz

+1 za kolor ANSI. W xterm możesz nawet uzyskać 256 kolorów na raz i możesz dynamicznie definiować paletę! Należy jednak pamiętać, że wszystkie wywołania funkcji rejestrowania powinny mieścić się w definicji funkcji, aby uniknąć potencjalnych problemów z blokowaniem importu podczas logowania poza definicją funkcji . Twój kod wygląda głównie dobrze; tylko trochę TestColorer.pymnie to niepokoi.
personal_cloud,

Powoduje to wyświetlanie kodów kolorów na początku i na końcu komunikatów dziennika w rzeczywistych plikach dziennika.
MehmedB

74

Aktualizacja : Ponieważ to jest świąd, który od dawna chciałem podrapać, napisałem bibliotekę dla leniwych ludzi takich jak ja, którzy chcą tylko prostych sposobów: zenlog

Colorlog jest do tego doskonały. Jest dostępny na PyPI (a zatem można go zainstalować pip install colorlog) i jest aktywnie utrzymywany .

Oto krótki fragment, który można skopiować i wkleić, aby skonfigurować rejestrowanie i wydrukować przyzwoicie wyglądające komunikaty dziennika:

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

Wynik:

Dane wyjściowe dziennika kolorów


4
Świetna odpowiedź; +1. Przykład kodu można jednak skrócić (czy trzy połączenia są setLevelnaprawdę potrzebne?)
Clément

1
Miałem nadzieję, że znajdę taką odpowiedź, jeśli przejdę wystarczająco długo. ☺ Mam nadzieję, że @airmind rozważy udzielenie przyjętej odpowiedzi, aby przyszli sprytni w pracy ludzie mogli znaleźć najlepszą bibliotekę z optymalnym lenistwem. 😉
Michael

Właśnie głosowałem to za przykłady wiadomości WYJŚCIA ^^
Agustin Barrachina

69

Szybkie i brudne rozwiązanie dla predefiniowanych poziomów dziennika i bez definiowania nowej klasy.

logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))

@ spiderplant0 rejestrowanie importu; # wklej kod z @ABC; spróbuj z logowaniem.warning („to jest test”). Zobaczysz wielką część „OSTRZEŻENIE: to test” w kolorze. Działa tylko na Linuksie btw
Riccardo Galli

3
Ponieważ tylko nazwa loglevel jest pokolorowana, musisz upewnić się, że nazwa loglevel jest w ogóle wydrukowana na konsoli. To nie dzieje się dla mnie po wyjęciu z pudełka. Pomoże w tym coś logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')takiego : gdzie oczywiście %(levelnames)sjest to ważne.
Sebastian

4
Najprostsze i najczystsze rozwiązanie do zastosowania i zrozumienia.
F. Santiago

1
Po prostu spróbuj w konsoli Linux. echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again". -eopcja echo interpretuje „\ 033” jako ósemkową formę znaku Escape ASCII. Ten specjalny symbol powoduje, że niektóre kompatybilne terminale interpretują kolejne znaki (do znakowania mwłącznie) jako polecenia specjalne. en.wikipedia.org/wiki/ANSI_escape_code
eugene-bright

1
Niewielka poprawa: umieść ten kod w środku if sys.sdterr.isatty():. W takim przypadku, jeśli przekierujesz dane wyjściowe do pliku, plik nie będzie zawierał tych znaków zmiany znaczenia.
lesnik

35

Kod 2020, dodatkowe pakiety nie są wymagane, Python 3

Zdefiniuj klasę

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)

Utwórz instancję rejestratora

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

I użyć!

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

Wynik wprowadź opis zdjęcia tutaj

Pełna kolorystyka wprowadź opis zdjęcia tutaj

Dla Windowsa

To rozwiązanie działa na Mac OS, terminalach IDE. Wygląda na to, że wiersz polecenia okna nie ma w ogóle kolorów. Oto instrukcje, jak je włączyć, których nie próbowałem https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/


1
Przeprowadzam test (python 3.7, windows), ale rejestrowanie nie pokazuje kolorów:←[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
konstruktor

To niestety nie działa.
Joe

2
Tak bardzo podobała mi się ta odpowiedź, że zrobiłem z niej repozytorium , z kilkoma przyrostami i kartą kolorów ansi.
Teodoro

@constructor gdzie go uruchomisz? Konsola IDE? terminal systemu Windows?
Sergey Pleshakov

@Joe co dokładnie nie działa? jakie jest twoje środowisko i jakie masz błędy? Chciałbym zrewidować to rozwiązanie, aby działało na różnych platformach
Sergey Pleshakov

17

Wydaje mi się, że równie dobrze mogę dodać moją odmianę kolorowego rejestratora.

Nie jest to nic szczególnego, ale jest bardzo prosty w użyciu i nie zmienia obiektu rekordu, dzięki czemu unika się rejestrowania sekwencji specjalnych ANSI w pliku dziennika, jeśli używana jest procedura obsługi pliku. Nie wpływa na formatowanie komunikatu dziennika.

Jeśli już korzystasz z modułu formatującego modułu rejestrowania , wszystko, co musisz zrobić, aby uzyskać nazwy poziomów kolorowych, to zastąpić program obsługi formaterów modułem rejestrującym za pomocą narzędzia ColorFormatter. Jeśli logujesz całą aplikację, musisz to zrobić tylko dla rejestratora najwyższego poziomu.

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)

Przykładowe użycie

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

Wyniki

Wyjście terminala

Wyjście terminala

zawartość 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

Oczywiście możesz uzyskać tyle fantazji, ile chcesz, formatując dane wyjściowe terminala i pliku dziennika. Tylko poziom dziennika zostanie pokolorowany.

Mam nadzieję, że ktoś uzna to za przydatne i nie jest to po prostu zbyt wiele tego samego. :)

Przykładowe pliki Pythona można pobrać z tej GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd


2
BTW, aby dodać kolory do samej wiadomości, po prostu dodaj ten wiersz przed return:colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
The Godfather

15

Zaktualizowałem przykład z tagów obsługujących Airmind dla pierwszego planu i tła. Wystarczy użyć zmiennych kolorów $ BLACK - $ WHITE w ciągu formatyzatora dziennika. Aby ustawić tło, użyj $ 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

Teraz możesz w prosty sposób wykonać następujące czynności w pliku konfiguracyjnym:

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

Wielka poprawa. Jednak superprzypuszczam, że komentarz dotyczy tylko niektórych starożytnych wersji Pythona? Ponieważ ta odpowiedź pochodzi z 2010 roku. Działa dobrze dla mnie z Python 2.7
Joakim,

14

Możesz zaimportować moduł colorlog i użyć go ColoredFormatterdo pokolorowania komunikatów dziennika.

Przykład

Płyta kotła dla modułu głównego:

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

Kod włącza kolory w komunikatach dziennika tylko wtedy, gdy moduł colorlog jest zainstalowany i jeśli dane wyjściowe faktycznie trafiają do terminala. Pozwala to uniknąć zapisywania sekwencji specjalnych w pliku, gdy dane wyjściowe dziennika są przekierowywane.

Ponadto konfiguracja kolorów niestandardowych jest lepiej dostosowana do terminali z ciemnym tłem.

Niektóre przykładowe rejestrowanie połączeń:

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

Wynik:

wprowadź opis zdjęcia tutaj


2
Można również użyć colorlog.basicConfigzamiast tego, logging.basicConfigktóry ma pewne dobre ustawienia domyślne
MarSoft

1
Dla przypomnienia, colorlog nie zawsze działa bezpośrednio na platformach Windows (jak podano, wymagana jest zależność colorama). Mimo to miałem problem z uruchomieniem go w środowisku Anaconda / Spyder env. Może być konieczne określenie colorama.init (strip = False) na przykład w pliku escape_code.py (jak wskazano w tym wątku github.com/spyder-ide/spyder/issues/1917 )
Matt-Mac-Muffin


11

Zmodyfikowałem oryginalny przykład dostarczony przez Sorina i podklasowałem StreamHandler do ColorizedConsoleHandler.

Minusem ich rozwiązania jest to, że modyfikuje komunikat, a ponieważ to modyfikuje rzeczywisty komunikat, każdy inny moduł obsługi otrzyma również zmodyfikowany komunikat.

W naszym przypadku powstały pliki dziennika z kodami kolorów, ponieważ korzystamy z wielu rejestratorów.

Poniższa klasa działa tylko na platformach obsługujących ansi, ale dodawanie do niej kodów kolorów systemu Windows powinno być trywialne.

import copy
import logging


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


7

Istnieje mnóstwo odpowiedzi. Ale nikt nie mówi o dekoratorach. Więc tu jest moje.

Ponieważ jest to o wiele prostsze.

Nie trzeba niczego importować ani pisać żadnej podklasy:

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

Ustawia to błędy na czerwono, komunikaty debugowania na niebiesko i tak dalej. Jak zadane w pytaniu.

Możemy nawet dostosować opakowanie, aby wziąć colorargument, aby dynamicznie ustawić kolor wiadomości za pomocąlogger.debug("message", color=GREY)

EDYCJA: Oto dostosowany dekorator do ustawiania kolorów w czasie wykonywania:

def add_color(logger_method, _color):
  def wrapper(message, *args, **kwargs):
    color = kwargs.pop("color", _color)
    if isinstance(color, int):
      color = "\33[%dm" % color
    return logger_method(
      # the coloring is applied here.
      color+message+NO_COLOR,
      *args, **kwargs
    )
  return wrapper

# blah blah, apply the decorator...

# this is displayed in red.
logger.error("Launching %s." % __file__)
# this is displayed in blue
logger.error("Launching %s." % __file__, color=34)
# and this, in grey
logger.error("Launching %s." % __file__, color=GREY)

6

Kolejny drobny remiks podejścia airminda, który utrzymuje wszystko w jednej klasie:

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)

Aby użyć dołączyć formater do modułu obsługi, coś takiego:

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

5

Prostym, ale bardzo elastycznym narzędziem do kolorowania DOWOLNEGO tekstu terminala jest „ colout ”.

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

Gdzie dowolny tekst na wyjściu „myprocess”, który pasuje do grupy 1 wyrażenia regularnego, będzie pokolorowany kolorem 1, grupa 2 kolorem 2 itd.

Na przykład:

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

tzn. pierwsza grupa wyrażeń regularnych (parens) odpowiada początkowej dacie w pliku dziennika, druga grupa odpowiada nazwie pliku python, numerowi wiersza i nazwie funkcji, a trzecia grupa odpowiada późniejszemu komunikatowi dziennika. Używam również równoległej sekwencji „pogrubienia / normalnych”, a także sekwencji kolorów. To wygląda jak:

plik dziennika z kolorowym formatowaniem

Zauważ, że linie lub części linii, które nie pasują do żadnego z moich wyrażeń regularnych, są nadal powtarzane, więc to nie jest jak „grep - kolor” - nic nie jest odfiltrowywane z wyniku.

Oczywiście jest to na tyle elastyczne, że można go używać z dowolnym procesem, nie tylko dostosowywaniem plików dziennika. Zazwyczaj robię nowe wyrażenie regularne w locie za każdym razem, gdy chcę coś pokolorować. Z tego powodu wolę narzędzie colout niż dowolne niestandardowe narzędzie do kolorowania plików dziennika, ponieważ muszę nauczyć się tylko jednego narzędzia, niezależnie od tego, co koloruję: rejestrowanie, testowanie danych wyjściowych, podświetlanie składni fragmentów kodu w terminalu itp.

Unika także faktycznego zrzucania kodów ANSI do samego pliku dziennika, co IMHO jest złym pomysłem, ponieważ spowoduje to przerwanie takich rzeczy, jak szukanie wzorców w pliku dziennika, chyba że zawsze pamiętasz, aby dopasować kody ANSI w wyrażeniu regularnym grep.


4
import logging
import sys

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

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


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

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

logging.info('\nParams:' + str_color("blue", str(params)))`

+1 Dobry przykład z [9*mkodami dla „jasnych” kolorów ANSI! PS twoja ostatnia linia trochę mnie niepokoi, ponieważ nie wiadomo jeszcze, czy logowanie poza definicją funkcji jest bezpieczne w Pythonie .
personal_cloud

2

Oto moje rozwiązanie:

class ColouredFormatter(logging.Formatter):
    RESET = '\x1B[0m'
    RED = '\x1B[31m'
    YELLOW = '\x1B[33m'
    BRGREEN = '\x1B[01;32m'  # grey in solarized for terminals

    def format(self, record, colour=False):
        message = super().format(record)

        if not colour:
            return message

        level_no = record.levelno
        if level_no >= logging.CRITICAL:
            colour = self.RED
        elif level_no >= logging.ERROR:
            colour = self.RED
        elif level_no >= logging.WARNING:
            colour = self.YELLOW
        elif level_no >= logging.INFO:
            colour = self.RESET
        elif level_no >= logging.DEBUG:
            colour = self.BRGREEN
        else:
            colour = self.RESET

        message = colour + message + self.RESET

        return message


class ColouredHandler(logging.StreamHandler):
    def __init__(self, stream=sys.stdout):
        super().__init__(stream)

    def format(self, record, colour=False):
        if not isinstance(self.formatter, ColouredFormatter):
            self.formatter = ColouredFormatter()

        return self.formatter.format(record, colour)

    def emit(self, record):
        stream = self.stream
        try:
            msg = self.format(record, stream.isatty())
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)


h = ColouredHandler()
h.formatter = ColouredFormatter('{asctime} {levelname:8} {message}', '%Y-%m-%d %H:%M:%S', '{')
logging.basicConfig(level=logging.DEBUG, handlers=[h])

1

Problemem było prawidłowe ustawienie formatyzatora:

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)

A następnie użyć:

import coloured_log
import logging

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

coloured_log.close()    # restore colours

Miał to być pseudo kod (ponieważ brakuje też _set_colour), ale coś dodał. Największym problemem było to, jak prawidłowo podłączyć formatyzator.
Nick

Zobacz rozwiązanie „hydraulik”. Myślę, że to lepszy sposób na rozwiązanie problemu (tzn. Przewodnik powinien wykonać koloryzację). stackoverflow.com/questions/384076/…
Nick

1

Podczas gdy inne rozwiązania wydają się w porządku, mają pewne problemy. Niektóre kolorują całe linie, które czasami nie są pożądane, a niektóre pomijają konfigurację, którą możesz mieć razem. Poniższe rozwiązanie nie wpływa na nic oprócz samej wiadomości.

Kod

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)

Przykład

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

Wynik

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

Jak widać, wszystko inne nadal jest generowane i pozostaje w pierwotnym kolorze. Jeśli chcesz zmienić cokolwiek innego niż wiadomość, możesz po prostu przekazać kody kolorów log_formatw przykładzie.


kiedy go używam, wiadomości są drukowane dwukrotnie. wiesz dlaczego?
Validus Oculus

@ mógłbyś opracować? Mianowicie masz na myśli coś takiego [17:01:36]:WARNING:this should be yellowthis should be yellowlub dwukrotne wydrukowanie pełnej linii?
Pithikos,

Przepraszamy za zwięzłość komentarza. To pierwsze zdarzyło się: [17:01:36]: OSTRZEŻENIE: powinno być żółte \ to powinno być żółte. Chcę jednak, aby był tylko sformatowany, w przeciwnym razie wygląda na śmieci ze względu na nadmiarowe dzienniki.
Validus Oculus

@ MuratKarakuş nie jestem pewien, dlaczego tak się dzieje bez pełnego obrazu realizacji. Jeśli używasz niestandardowego rejestratora, może w pewnym momencie przeszkadzasz? Szybkim rozwiązaniem może być usunięcie 7s:%(message)sz log_format.
Pithikos,

1

Mam dwa zgłoszenia do dodania, z których jeden koloruje tylko komunikat (ColoredFormatter), a drugi koloruje całą linię (ColorizingStreamHandler). Obejmują one również więcej kodów kolorów ANSI niż poprzednie rozwiązania.

Niektóre treści zostały pozyskane (z modyfikacjami) z: powyższego postu oraz http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .

Koloruje tylko wiadomość:

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

Koloruje całą linię:

class ColorizingStreamHandler(logging.StreamHandler):

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

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

    RESET = "\033[0m"

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

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

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

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code


1

To jest Enum zawierające kody kolorów:

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'

Można to zastosować do nazw każdego poziomu dziennika. Pamiętaj, że jest to potworny hack.

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

Zauważ, że twój formatyzator dziennika musi zawierać nazwę poziomu dziennika

%(levelname)

na przykład:

    LOGGING = {
...
        'verbose': {
            'format': '%(asctime)s %(levelname)s %(name)s:%(lineno)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '[%(asctime)s] %(levelname)s %(name)s %(message)s'
        },

1

FriendlyLog to kolejna alternatywa. Działa z Python 2 i 3 w systemach Linux, Windows i MacOS.


Czekam na nowy PR, aby zmniejszyć bałagan na ścieżce modułu
mbspark

1

Co powiesz na wyróżnianie również logów wiadomości z naprzemiennymi kolorami, oprócz kolorowania według poziomu? Ostatnio napisałem do tego prosty kod. Kolejną zaletą jest to, że wywołanie dziennika jest wykonywane przy użyciu formatu nawiasów klamrowych w języku Python 3. ("{}" ).

Zobacz najnowszy kod i przykłady tutaj: https://github.com/davidohana/colargulog

Przykładowy kod rejestracyjny:

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)

Wynik:

wprowadź opis zdjęcia tutaj

Realizacja:

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

Written by david.ohana@ibm.com
License: Apache-2.0
"""

import logging
import logging.handlers
import re


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


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

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

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

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

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

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

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

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

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


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

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

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

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

        if count_of_start_param != count_of_end_param:
            return False

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

        return True

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

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

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

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

0

Użyj pyfancy .

Przykład:

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

Pytanie polegało na poprawieniu loggingfunkcjonalności w celu użycia oddzielnej biblioteki kolorów.
Zarażony Drake

0

Kolejne rozwiązanie z kolorami 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)

nazwij to raz ze swojej __main__funkcji. Mam tam coś takiego:

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

sprawdza również, czy dane wyjściowe są konsolą, w przeciwnym razie nie zostaną użyte żadne kolory.


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)

Stosowanie

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


W przypadku konsoli możesz pominąć nazwę pliku lub po prostu nazwa pliku = '' powinna działać. zmodyfikuj basicConfig, aby uwzględnić inne właściwości, takie jak numer pliku, moduł ..
estifanos gebrehiwot

0

Poniższe rozwiązanie działa tylko z Pythonem 3, ale dla mnie wygląda to najwyraźniej.

Chodzi o to, aby użyć fabryki rekordów dziennika, aby dodać atrybuty „kolorowe” do obiektów rekordu dziennika, a następnie użyć tych atrybutów „kolorowych” w formacie dziennika.

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

Możesz łatwo zmodyfikować ten przykład, aby utworzyć inne kolorowe atrybuty (np. Message_c), a następnie użyć tych atrybutów, aby uzyskać kolorowy tekst (tylko) tam, gdzie chcesz.

(przydatna sztuczka, którą niedawno odkryłem: mam plik z kolorowymi dziennikami debugowania i kiedykolwiek chcę tymczasowo zwiększyć poziom dziennika mojej aplikacji, po prostu tail -fplik dziennika w innym terminalu i widzę dzienniki debugowania na ekranie bez zmiany konfiguracji i ponownego uruchamiania aplikacji )


0

To kolejny wariant przykładu airminda w Pythonie. Chciałem pewnych konkretnych funkcji, których nie widziałem w innych przykładach

  • używaj kolorów dla terminala, ale nie zapisuj znaków niedrukowalnych w procedurach obsługi plików (dla tego zdefiniowałem 2 formatery)
  • możliwość zastąpienia koloru dla określonego komunikatu dziennika
  • skonfiguruj program rejestrujący z pliku (w tym przypadku yaml)

Uwagi: Użyłem colorama, ale można to zmodyfikować, aby nie było wymagane. Również dla moich testów właśnie uruchomiłem plik Pythona, więc moja klasa jest w module. __main__Musisz zmienić (): __main__.ColoredFormatterna dowolny moduł.

pip install colorama pyyaml

logowanie.jaml

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

wynik:

wynik

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.