Перейти к главному содержимому

Простой Logging в Python

Для логирования действий в python есть модуль logging.

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

В целом кратко:

  • Создается объект логгер
  • Устанавливается уровень сообщений, которые он будет воспринимать
  • Создаются хендлер(ы), которые будут заниматься выводом логов (в файл, в консоль...)
  • Устанавливается уровень вывода для каждого хендлера (например, в консоль могут выводиться только ошибки, а в файл записываться ошибки и предупреждения).
  • Создаются форматтеры (определяют формат вывода сообщений в логи)
  • Форматтеры привязываются к хендлерам
  • Хендлеры привязываются к логгерам (один логгер может иметь сразу несколько разных хендлеров одновременно, например, для вывода и в файл и в консоль).

Само собой всё это гибко настраивается и расширяется/дополняется при необходимости.

Пример начальной инициализации:

import logging

# создание root логгера
logger = logging.getLogger()
# установка уровня логирования для всего логгера
logger.setLevel(logging.INFO)

# создание формата для вывода сообщений
formatter = logging.Formatter("%(levelname)s: %(name)s: %(message)s")
#formatter = logging.Formatter("%(filename)s:%(lineno)d: %(name)s %(funcName)s: %(message)s")

# создание StreamHandler - то есть потокового вывода
handler = logging.StreamHandler()

# установка уровня логирования для текущего хендлера 
# (может отличаться от уровня для всего логгера)
handler.setLevel(logging.INFO)
# Привязка ранее созданного формата к хендлеру
handler.setFormatter(formatter)
# Привязка ранее созданного хендлера к логгеру
logger.addHandler(handler)

Вывод в лог сообщений разного уровня:

logger.critical("All is lost and nothing can be done.").
logger.error("Unable to open input file %s.", infile).
logger.warning("Config file not found. Using defaults").
logger.info("SSL verify disabled")
logger.debug("id=%d, name=%s, full_name=%s, url=%s", user_id, user_name, user_fname, url)

Передавать созданный логгер, например, в классы или подмодули не обязательно. Можно одной строкой создать дочерний логгер и он будет выводить через родительский, со всеми его привязками и настройками, без дополнительных настроек (само собой, при желании можно часть или все настройки поменять, не затрагивая родительский).

Создать дочерний логгер с заданным именем:

class NewShinyClass():

  def __init__(self):
    # создание дочернего логгера в классе
    self.logger = logging.getLogger('pygogs')

  def some_member(self):
    ...
    self.logger.debug("Entering advanced mode")
    ...

Если надо сделать условие на текущий реальный уровень логгирования (с учетом родительско-дочерних отношений логгеров) то так:

if self.logger.getEffectiveLevel() < logging.INFO:
  self.logger.debug("Scan source dirs...:")
  self.do_scan()
  self.logger.debug("Scan complete")

Если скрипт является консольным приложением с поддержкой аргументов командно строки, то можно сделать (как это обычно принято у консольных приложений) аргумент verbose или -v и чем больше таких аргументов, тем подробнее вывод.

В примере разбор аргументов сделан при помощи типового argparse.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import logging

__version__ = "1.0"

###############################################################################
def main():
  descr = "My super script"
  additional = "just example"
  parser = argparse.ArgumentParser (description=descr,  epilog=additional)
  parser.add_argument('--version', action='version', 
                      version='%(prog)s ' + __version__)
  parser.add_argument("-v", "--verbose",
                      action="count", default=0,
                      help="increase verbosity")

  args = parser.parse_args()

  # уровень логгирования в зависимости от количества параметров -v
  if args.verbose == 0:   lvl = logging.WARNING
  elif args.verbose == 1: lvl = logging.INFO
  else:                   lvl = logging.DEBUG

  logger = logging.getLogger()
  logger.setLevel(lvl)

  formatter = logging.Formatter("%(levelname)s: %(message)s")
  handler = logging.StreamHandler()
  handler.setLevel(lvl)
  handler.setFormatter(formatter)
  logger.addHandler(handler)

  .....

  return 0

###############################################################################
if __name__ == '__main__':
  ret = main()
  sys.exit(ret)

Отличное подробное видео на тему: https://youtu.be/jF_XglX0JgM