Статьи

Окончательный Python Colorged Logger

Чтобы сделать отладку и диагностику на основе ведения журнала более увлекательной, я создал следующее улучшенное решение для ведения журнала Python. Он раскрашивает и корректирует выходные данные журнала, чтобы можно было более эффективно работать с длинными записями журнала в локальном терминале. Он основан на  пакете logutils от Vinay Sajip.

Снимок экрана 2013-03-14 в 6.04.36

Что оно делает

  • Целевые варианты использования отладки и диагностики, а не сценарии использования записи файла журнала
  • Определяет, используете ли вы реальный терминал или входите в файловый поток
  • Настраивает собственный обработчик журнала, который по-разному окрашивает части сообщений
  • Работает в UNIX или Windows

Пример более длинной записи протокола терминала, которая делает большой вывод в logging.DEBUG  level:

Снимок экрана 2013-03-14 в 6.10.39

Исходный код и примеры использования приведены ниже.

1. Дальнейшие идеи

Как сделать вашу жизнь еще проще в журнале Python

  • Сделайте это в правильном пакете или объедините с  logutils
  • Используйте информацию терминала для правильного отступа строк сообщений, чтобы 2+ строки делали отступ под начальной строкой и не нарушали структуру столбца (как получить ширину терминала в Python?)
  • Сделать запись в Python, чтобы сохранить полное имя функции, а не просто пару модуль / функция, которая бесполезна в больших проектах
  • Сделайте отслеживание обратных вызовов активируемым в iTerm 2. Обработчик щелчков по ссылкам iTerm 2 имеет поддержку регулярных выражений, но не знает о трассировках Python и нуждается в исправлении в iTerm 2

Пожалуйста, оставьте свои собственные идеи :)

2. Код

Исходный код (также в  Gist ) — для цветной версии исходного кода см.  Оригинальное сообщение в блоге :

# -*- coding: utf-8 -*-"""

  Python logging tuned to extreme.

"""

__author__ ="Mikko Ohtamaa <[email protected]>"
__license__ ="MIT"import logging

from logutils.colorize importColorizingStreamHandlerclassRainbowLoggingHandler(ColorizingStreamHandler):
  """ A colorful logging handler optimized for terminal debugging aestetichs.

  - Designed for diagnosis and debug mode output - not for disk logs

  - Highlight the content of logging message in more readable manner

  - Show function and line, so you can trace where your logging messages
  are coming from

  - Keep timestamp compact

  - Extra module/function output for traceability

  The class provide few options as member variables you
  would might want to customize after instiating the handler.
  """

  # Define color for message payload
  level_map ={
  logging.DEBUG:(None,'cyan',False),
  logging.INFO:(None,'white',False),
  logging.WARNING:(None,'yellow',True),
  logging.ERROR:(None,'red',True),
  logging.CRITICAL:('red','white',True),
  }

  date_format ="%H:%m:%S"

  #: How many characters reserve to function name logging
  who_padding =22

  #: Show logger name
  show_name =True

  def get_color(self, fg=None, bg=None, bold=False):
  """
  Construct a terminal color code

  :param fg: Symbolic name of foreground color

  :param bg: Symbolic name of background color

  :param bold: Brightness bit
  """
  params =[]
  if bg in self.color_map:
  params.append(str(self.color_map[bg]+40))
  if fg in self.color_map:
  params.append(str(self.color_map[fg]+30))
  if bold:
  params.append('1')

  color_code =''.join((self.csi,';'.join(params),'m'))

  return color_code

  def colorize(self, record):
  """
  Get a special format string with ASCII color codes.
  """

  # Dynamic message color based on logging level
  if record.levelno in self.level_map:
  fg, bg, bold = self.level_map[record.levelno]
  else:
  # Defaults
  bg =None
  fg ="white"
  bold =False

  # Magician's hat
  # https://www.youtube.com/watch?v=1HRa4X07jdE
  template =[
  "[",
  self.get_color("black",None,True),
  "%(asctime)s",
  self.reset,
  "] ",
  self.get_color("white",None,True)if self.show_name else"",
  "%(name)s "if self.show_name else"",
  "%(padded_who)s",
  self.reset,
  " ",
  self.get_color(bg, fg, bold),
  "%(message)s",
  self.reset,
  ]

  format ="".join(template)

  who =[self.get_color("green"),
  getattr(record,"funcName",""),
  "()",
  self.get_color("black",None,True),
  ":",
  self.get_color("cyan"),
  str(getattr(record,"lineno",0))]

  who ="".join(who)

  # We need to calculate padding length manualy
  # as color codes mess up string length based calcs
  unformatted_who = getattr(record,"funcName","")+"()"+ \
  ":"+ str(getattr(record,"lineno",0))

  if len(unformatted_who)< self.who_padding:
  spaces =" "*(self.who_padding - len(unformatted_who))
  else:
  spaces =""

  record.padded_who = who + spaces

  formatter = logging.Formatter(format, self.date_format)
  self.colorize_traceback(formatter, record)
  output = formatter.format(record)
  # Clean cache so the color codes of traceback don't leak to other formatters
  record.ext_text =None
  return output

  def colorize_traceback(self, formatter, record):
  """
  Turn traceback text to red.
  """
  if record.exc_info:
  # Cache the traceback text to avoid converting it multiple times
  # (it's constant anyway)
  record.exc_text ="".join([
  self.get_color("red"),
  formatter.formatException(record.exc_info),
  self.reset,
  ])

  def format(self, record):
  """
  Formats a record for output.

  Takes a custom formatting path on a terminal.
  """
  if self.is_tty:
  message = self.colorize(record)
  else:
  message = logging.StreamHandler.format(self, record)

  return message

if __name__ =="__main__":
  # Run test output on stdout
  import sys

  root_logger = logging.getLogger()
  root_logger.setLevel(logging.DEBUG)

  handler =RainbowLoggingHandler(sys.stdout)
  formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
  handler.setFormatter(formatter)
  root_logger.addHandler(handler)
  logger = logging.getLogger("test")

  def test_func():
  logger.debug("debug msg")
  logger.info("info msg")
  logger.warn("warn msg")

  def test_func_2():
  logger.error("error msg")
  logger.critical("critical msg")

  try:
  raiseRuntimeError("Opa!")
  exceptExceptionas e:
  logger.exception(e)

  test_func()
  test_func_2()