Как я могу раскрасить вывод журнала Python? - PullRequest
296 голосов
/ 21 декабря 2008

Некоторое время назад я видел приложение Mono с цветным выводом, вероятно, из-за его системы журналов (потому что все сообщения были стандартизированы).

Теперь в Python есть модуль logging, который позволяет вам указать множество параметров для настройки вывода. Итак, я представляю, что подобное возможно с Python, но я не могу найти, как это сделать где-либо.

Есть ли способ сделать вывод модуля Python logging в цвете?

Что я хочу (например) ошибки в красном, отладочные сообщения в синем или желтом, и так далее.

Конечно, для этого, вероятно, потребуется совместимый терминал (большинство современных терминалов); но я могу вернуться к исходному выводу logging, если цвет не поддерживается.

Есть идеи, как получить цветной вывод с помощью модуля регистрации?

Ответы [ 24 ]

168 голосов
/ 21 декабря 2008

Я уже знал о побегах цветов, я использовал их в моем приглашении bash некоторое время назад. В любом случае, спасибо.
Я хотел интегрировать его с модулем регистрации, что я и сделал после нескольких попыток и ошибок.
Вот что я получаю в итоге:

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)

И чтобы использовать его, создайте свой собственный 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)

На всякий случай, если это кому-то еще нужно.

Будьте осторожны, если вы используете более одного регистратора или обработчика: ColoredFormatter изменяет объект записи, который передается другим обработчикам или распространяется другим регистраторам. Если вы настроили регистраторы файлов и т. Д., Вы, вероятно, не хотите иметь цвета в файлах журналов. Чтобы избежать этого, лучше всего просто создать копию record с copy.copy() перед манипулированием атрибутом levelname или сбросить имя уровня до предыдущего значения, прежде чем возвращать отформатированную строку (кредит Michael в комментариях).

117 голосов
/ 31 мая 2013

Несколько лет назад я написал обработчик цветного потока для собственного использования. Затем я наткнулся на эту страницу и обнаружил коллекцию фрагментов кода, которые люди копируют / вставляют :-(. Мой обработчик потока в настоящее время работает только в UNIX (Linux, Mac OS X), но преимущество в том, что он доступен в PyPI GitHub ), он прост в использовании и имеет режим синтаксиса Vim :-). В будущем я мог бы расширить его для работы в Windows.

Для установки пакета:

$ pip install coloredlogs

Чтобы подтвердить, что это работает:

$ coloredlogs --demo

Чтобы начать работу с собственным кодом:

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

Формат журнала по умолчанию, показанный в приведенном выше примере, содержит дату, время, имя хоста, имя регистратора, PID, уровень журнала и сообщение журнала. Вот как это выглядит на практике:

Screenshot of coloredlogs output

68 голосов
/ 26 августа 2009

Вот решение, которое должно работать на любой платформе. Если это не просто сказать мне, и я обновлю его.

Как это работает: на платформе, поддерживающей экранирование ANSI, использует их (не Windows), а в Windows использует вызовы API для изменения цветов консоли.

Скрипт взламывает метод logging.StreamHandler.emit из стандартной библиотеки, добавляя к нему оболочку.

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())
61 голосов
/ 03 ноября 2011

Быстрое и грязное решение для предопределенных уровней журнала и без определения нового класса.

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))
57 голосов
/ 31 мая 2014

Обновление : так как это зуд, о котором я так долго думал, я решил написать библиотеку для таких ленивых людей, как я, которые просто хотят делать простые вещи: zenlog

Colorlog отлично подходит для этого. Он доступен в PyPI (и, следовательно, устанавливается через pip install colorlog) и активно поддерживается .

Вот краткий фрагмент, который можно скопировать и вставить, чтобы настроить ведение журнала и распечатать приличные сообщения журнала:

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

Выход:

Colorlog output

14 голосов
/ 18 июня 2016

Вы можете импортировать модуль colorlog и использовать его ColoredFormatter для раскрашивания сообщений журнала.

Пример

Бойлер для основного модуля:

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

Код разрешает цвета только в сообщениях журнала, если установлен модуль colorlog и если выходные данные действительно передаются на терминал. Это позволяет избежать записи escape-последовательностей в файл при перенаправлении вывода журнала.

Также настраиваемая цветовая схема лучше подходит для терминалов с темным фоном.

Некоторые примеры регистрации звонков:

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

Выход:

enter image description here

14 голосов
/ 28 марта 2010

Я обновил пример с поддержкой airmind тегов для переднего и заднего плана. Просто используйте переменные цвета $ BLACK - $ WHITE в вашей строке форматирования журнала. Чтобы установить фон, просто используйте $ 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

Так что теперь вы можете просто сделать следующее в вашем конфигурационном файле:

[formatter_colorFormatter]
class=logging.ColorFormatter
format= $COLOR%(levelname)s $RESET %(asctime)s $BOLD$COLOR%(name)s$RESET %(message)s
12 голосов
/ 29 сентября 2017

Ну, я думаю, я мог бы также добавить свой вариант цветного регистратора.

В этом нет ничего сложного, но он очень прост в использовании и не меняет объект записи, тем самым избегая записи escape-последовательностей ANSI в файл журнала, если используется обработчик файла. Не влияет на форматирование сообщения журнала.

Если вы уже используете Formatter модуля ведения журнала , все, что вам нужно сделать, чтобы получить цветные имена уровней, - это заменить обработчики советников Formatter на ColoredFormatter. Если вы регистрируете приложение целиком, вам нужно сделать это только для регистратора верхнего уровня.

colored_log.py

#!/usr/bin/env python

from copy import copy
from logging import Formatter

MAPPING = {
    'DEBUG'   : 37, # white
    'INFO'    : 36, # cyan
    'WARNING' : 33, # yellow
    'ERROR'   : 31, # red
    'CRITICAL': 41, # white on red bg
}

PREFIX = '\033['
SUFFIX = '\033[0m'

class ColoredFormatter(Formatter):

    def __init__(self, patern):
        Formatter.__init__(self, patern)

    def format(self, record):
        colored_record = copy(record)
        levelname = colored_record.levelname
        seq = MAPPING.get(levelname, 37) # default white
        colored_levelname = ('{0}{1}m{2}{3}') \
            .format(PREFIX, seq, levelname, SUFFIX)
        colored_record.levelname = colored_levelname
        return Formatter.format(self, colored_record)

Пример использования

app.py

#!/usr/bin/env python

import logging
from colored_log import ColoredFormatter

# Create top level logger
log = logging.getLogger("main")

# Add console handler using our custom ColoredFormatter
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
cf = ColoredFormatter("[%(name)s][%(levelname)s]  %(message)s (%(filename)s:%(lineno)d)")
ch.setFormatter(cf)
log.addHandler(ch)

# Add file handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.DEBUG)
ff = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(ff)
log.addHandler(fh)

# Set log level
log.setLevel(logging.DEBUG)

# Log some stuff
log.debug("app has started")
log.info("Logging to 'app.log' in the script dir")
log.warning("This is my last warning, take heed")
log.error("This is an error")
log.critical("He's dead, Jim")

# Import a sub-module 
import sub_module

sub_module.py

#!/usr/bin/env python

import logging
log = logging.getLogger('main.sub_module')

log.debug("Hello from the sub module")

Результаты

Клеммный выход

Terminal output

app.log content

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

Конечно, вы можете получить все, что захотите, с форматированием терминала и выводом файла журнала. Только уровень журнала будет раскрашен.

Я надеюсь, что кто-то найдет это полезным, и это не слишком много того же самого. :)

Файлы примеров Python можно скачать из этого GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd

10 голосов
/ 14 января 2011

Посмотрите на следующее решение. Обработчик потока должен быть тем, кто выполняет раскраску, тогда у вас есть возможность раскрасить слова, а не только всю строку (с помощью Formatter).

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

10 голосов
/ 05 февраля 2010

Я изменил исходный пример, предоставленный Сорином, и подклассифицированный StreamHandler в ColorizedConsoleHandler.

Недостатком их решения является то, что оно изменяет сообщение, и поскольку оно изменяет фактическое сообщение журнала, любые другие обработчики также получат измененное сообщение.

Это привело к созданию лог-файлов с цветными кодами в нашем случае, потому что мы используем несколько регистраторов.

Класс ниже работает только на платформах, которые поддерживают ANSI, но добавить к нему цветовые коды 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)
...