Как использовать логирование в Python 3

Вступление

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

Модульlogging позволяет вести диагностический журнал, в котором записываются события, связанные с работой приложения, а также журнал аудита, в котором записываются события транзакций пользователя для анализа. Он особенно используется для записи событий в файл.

Зачем использовать модульlogging

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

Возможно, вы более знакомы с проверкой того, происходят ли события, используя операторprint() во всем вашем коде. Операторprint()does предоставляет основной способ отладки кода для решения проблем. Хотя встраивание операторовprint() в ваш код может отслеживать поток выполнения и текущее состояние вашей программы, это решение оказывается менее удобным в обслуживании, чем использование модуляlogging по нескольким причинам:

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

  • При использовании операторовprint(), разбросанных по всему коду, нет простого способа отключить те, которые обеспечивают вывод отладки.

  • Когда вы закончите отладку, становится трудно удалить все операторыprint().

  • Нет записи в журнале, которая содержит легкодоступную диагностическую информацию

Хорошей идеей будет выработать привычку использовать модульlogging в своем коде, поскольку он больше подходит для приложений, которые выходят за рамки простых сценариев Python и обеспечивают устойчивый подход к отладке.

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

Печать отладочных сообщений на консоль

Если вы привыкли использовать операторprint(), чтобы увидеть, что происходит в программе, вы, возможно, привыкли видеть программу, котораяdefines a class и создает экземпляры объектов, которые выглядят примерно так:

pizza.py

class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

В приведенном выше коде есть метод__init__ для определенияname иprice объекта классаPizza. Затем у него есть два метода: один называетсяmake() для приготовления пиццы, а другой называетсяeat() для еды пиццы. Эти два метода принимают параметрquantity, который инициализируется в1.

Теперь давайте запустим программу:

python pizza.py

Мы получим следующий вывод:

OutputPizza created: artichoke ($15)
Made 1 artichoke pizza(s)
Ate 1 pizza(s)
Pizza created: margherita ($12)
Made 2 margherita pizza(s)
Ate 1 pizza(s)

Хотя операторprint() позволяет нам увидеть, что код работает, мы можем использовать для этого модульlogging.

Давайте удалим или закомментируем операторыprint() по всему коду и добавимimport logging в начало файла:

pizza.py

import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

Модульlogging имеетdefault level изWARNING, который является уровнем вышеDEBUG. Поскольку в этом примере мы собираемся использовать модульlogging для отладки, нам необходимо изменить конфигурацию так, чтобы уровеньlogging.DEBUG возвращал нам информацию в консоль. Мы можем сделать это, добавив следующую строку подimport statement:

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

Этот уровеньlogging.DEBUG относится к постоянному целочисленному значению, на которое мы ссылаемся в приведенном выше коде, чтобы установить порог. УровеньDEBUG равен 10.

Теперь мы заменим все операторыprint() операторамиlogging.debug(). В отличие отlogging.DEBUG, который является константой,logging.debug() является методом модуляlogging. При работе с этим методом мы можем использовать тот жеstring, переданный вprint(), как показано ниже.

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

На этом этапе, когда мы запустим программу с помощью командыpython pizza.py, мы получим следующий вывод:

OutputDEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

Сообщения журнала имеют уровень серьезностиDEBUG, а также встроенное в них словоroot, которое относится к уровню вашего модуля Python. Модульlogging можно использовать с иерархией регистраторов, имеющих разные имена, так что вы можете использовать разные регистраторы для каждого из ваших модулей.

Например, вы можете установить регистраторы равными различным регистраторам, которые имеют разные имена и разные выходные данные:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
OutputDEBUG:module_1:Module 1 debugger
DEBUG:module_2:Module 2 debugger

Теперь, когда мы понимаем, как использовать модульlogging для вывода сообщений в консоль, давайте перейдем к использованию модуляlogging для вывода сообщений в файл.

Запись сообщений в файл

Основная цель модуляlogging - записывать сообщения в файл, а не на консоль. Хранение файла сообщений со временем предоставляет вам данные, которые вы можете просмотреть и определить количественно, чтобы вы могли увидеть, какие изменения необходимо внести в ваш код.

Чтобы начать запись в файл, мы можем изменить методlogging.basicConfig(), включив параметрfilename. В этом случае назовем имя файлаtest.log:

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Приведенный выше код такой же, как и в предыдущем разделе, за исключением того, что теперь мы добавили имя файла для журнала, в который нужно напечатать. После запуска кода с помощью командыpython pizza.py в нашем каталоге должен появиться новый файл с именемtest.log.

Давайте откроем файлtest.log с помощью nano (или любого текстового редактора по вашему выбору):

nano test.log

Когда файл откроется, мы увидим следующее:

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

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

Давайте закроем файл с помощьюCTRL +x и вернемся в файлpizza.py, чтобы мы могли изменить код.

Мы сохраним большую часть кода без изменений, но изменим параметры в двух экземплярах пиццы,pizza_01 иpizza_02:

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# Modify the parameters of the pizza_01 object
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# Modify the parameters of the pizza_02 object
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

С этими изменениями давайте снова запустим программу с помощью командыpython pizza.py.

После запуска программы мы можем снова открыть наш файлtest.log с помощью nano:

nano test.log

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

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

Хотя эта информация, безусловно, полезна, мы можем сделать журнал более информативным, добавив дополнительныеLogRecord attributes. Прежде всего, мы хотели бы добавить удобочитаемую метку времени, которая сообщает нам, когда был создан LogRecord.

Мы можем добавить этот атрибут к параметру с именемformat, ссылаясь на него, как показано в таблице, со строкой%(asctime)s. Кроме того, чтобы сохранить имя уровняDEBUG, нам нужно будет включить строку%(levelname)s, а для сохранения строкового сообщения, которое мы просим регистратор распечатать, мы включим%(message)s. Каждый из этих атрибутов будет разделенcolon, как показано в добавленном ниже коде.

pizza.py

import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

Когда мы запускаем приведенный выше код с добавленными атрибутами с помощью командыpython pizza.py, мы получим новые строки, добавленные в наш файлtest.log, которые включают удобочитаемую метку времени в дополнение к имени уровняDEBUG и связанные сообщения, которые передаются в регистратор в виде строк.

OutputDEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: Sicilian ($18)
2017-05-01 16:28:54,593:DEBUG:Made 5 Sicilian pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 4 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: quattro formaggi ($16)
2017-05-01 16:28:54,593:DEBUG:Made 2 quattro formaggi pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 2 pizza(s)

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

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

Таблица уровней ведения журнала

Как разработчик, вы можете приписать уровень важности событию, которое фиксируется в регистраторе, добавив уровень серьезности. Уровни серьезности показаны в таблице ниже.

Уровни регистрации технически являются целыми числами (константа), и все они имеют приращение по 10, начиная сNOTSET, которое инициализирует регистратор с числовым значением 0.

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

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

уровень Числовое значение функция Использовал к

CRITICAL

50

logging.critical()

Показать серьезную ошибку, возможно, программа не может продолжить работу

ERROR

40

logging.error()

Показать более серьезную проблему

WARNING

30

logging.warning()

Укажите, что произошло или могло произойти что-то неожиданное

INFO

20

logging.info()

Убедитесь, что все работает, как ожидалось

DEBUG

10

logging.debug()

Диагностировать проблемы, показывать подробную информацию

Модульlogging устанавливает уровень по умолчанию наWARNING, поэтомуWARNING,ERROR иCRITICAL будут регистрироваться по умолчанию. В приведенном выше примере мы изменили конфигурацию, чтобы включить уровеньDEBUG с помощью следующего кода:

logging.basicConfig(level=logging.DEBUG)

Вы можете узнать больше о командах и работе с отладчиком вofficial logging documentation.

Заключение

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