Как сохранить лог в переменную python
Перейти к содержимому

Как сохранить лог в переменную python

  • автор:

Настройка журналирования (логирования) в Python с примерами

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

Для начала подключим модуль для работы с логами, создадим в глобальной переменной объект логгера log и напишем пару сообщений:

import logging log = logging.getLogger(__name__) def main(): log.debug('Отладочное сообщение') log.info('Информационное сообщение') log.warning('Сообщение с предостережением') log.error('Сообщение об ошибке') log.fatal('Фатальная ошибка, срочно остановить работу') if __name__ == '__main__': main()
  • debug — отладочная информация, которая используется во время разработки / поиска ошибок.
  • info — информационная информация о статусе исполнения программы.
  • warning — информация о потенциально опасных или нежелательных операциях (попытка получения несанкционированного доступа, невозможность отправки почты пользователю и т.д.).
  • error — об ошибках, произошедших во время работы программы (некритичная ошибка базы данных, падение соединения до используемого сервиса).
  • fatal — критичные ошибки, которые не позволяют приложению более нормально работать.

В случае кода выше на выход получаем:

Сообщение с предостережением Сообщение об ошибке Фатальная ошибка, срочно остановить работу

Это не все сообщения, ибо по умолчанию установлен уровень уведомлений, игнорирующий debug и info , дабы не засорять логи отладочной информацией.

Во время разработки или отладки полезно включить логи отладки. Поэтому добавим небольшую настройку лога:

import logging logging.basicConfig(level=logging.DEBUG) # 

Помимо этого логи можно сделать информативнее (или установив более удобный формат), добавив время сообщения, уровень важности сообщения, место возникновения вплоть до строки:

import logging logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s(%(lineno)d) - %(message)s", ) log = logging.getLogger(__name__)

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

2021-06-21 16:10:55,866 - [DEBUG] - __main__ - (main.py).main(11) - Отладочное сообщение 2021-06-21 16:10:55,867 - [INFO] - __main__ - (main.py).main(12) - Информационное сообщение 2021-06-21 16:10:55,867 - [WARNING] - __main__ - (main.py).main(13) - Сообщение с предостережением 2021-06-21 16:10:55,867 - [ERROR] - __main__ - (main.py).main(14) - Сообщение об ошибке 2021-06-21 16:10:55,867 - [CRITICAL] - __main__ - (main.py).main(15) - Фатальная ошибка, срочно остановить работу

Сообщения логов могут содержать переменные. Для этого сначала задаём формат в виде printf-строки, после чего аргументами передаём данные для printf-форматирования:

def main(): log.warning( 'Пользователь #%d не имеет прав доступа на "%s"', 1234, 'Чтение файлов пользователей', )

В нашем случае будут подставлены идентификатор пользователя и название права доступа:

2021-06-21 16:20:35,872 - [WARNING] - __main__ - (main.py).main(14) - Пользователь #1234 не имеет прав доступа на "Чтение файлов пользователей"

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

  • threading.local - глобальные переменные, однако для каждого потока (thread) они локальны.
  • contextvars.ContextVar - глобальные переменные, локальные для каждого асинхронного контекста при программировании с asyncio.

К примеру, у нас используется aiohttp приложение, в middleware мы получаем ip и идентификатор пользователя. Чтобы не протаскивать эти данные всюду, не забывать с ними вызывать логгер, мы сложим их в глобальную переменную ContextVar , а в нашем специальном логгере будем использовать эту глобальную переменную:

from contextvars import ContextVar import logging SOME_LOG_EXTRA = ContextVar('SOME_LOG_EXTRA', default=<>) SOME_LOG_EXTRA.set() logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s(%(lineno)d) - %(message)s", ) class CustomAdapter(logging.LoggerAdapter): def process(self, msg, kwargs): return '%s %s' % (self.extra.get(), msg), kwargs log = CustomAdapter(logging.getLogger(__name__), SOME_LOG_EXTRA) def main(): log.warning( 'Пользователь не имеет прав доступа на "%s"', 'Чтение файлов пользователей', ) if __name__ == '__main__': main()

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

2021-06-21 16:35:30,030 - [WARNING] - __main__ - (main.py).main(22) - Пользователь не имеет прав доступа на "Чтение файлов пользователей"

До этого все сообщения выводились на стандартный поток вывода (stdout). Однако, часто нужно настроить вывод в какой-то файл или вообще отправлять в сеть. Тогда нам нужно настроить обработчик вывода нашего логгера. В модуле logging есть множество обработчиков - найти их можно по слову Handler . В случае, если нам нужен вывод в файл - используем файловый обработчик - FileHandler :

import logging logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s(%(lineno)d) - %(message)s", handlers=[logging.FileHandler('./file.log', encoding='utf-8')] )

Также часто нам нужно настраивать не один логгер, а сразу несколько - помните, что лучше иметь как минимум по одному логгеру на Python модуль. Для этого подойдёт настройка в виде dictConfig , ведь basicConfig для этих целей уже не хватает.

В общем виде dictConfig выглядит так:

import logging.config logging.config.dictConfig(< 'version': 1, 'disable_existing_loggers': True, 'formatters': < 'standard': < 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s' >, >, 'handlers': < 'default': < 'level': 'INFO', 'formatter': 'standard', 'class': 'logging.FileHandler', 'filename': './file.log', 'mode': 'a', 'encoding': 'utf-8', >, >, 'loggers': < '': < # root logger 'handlers': ['default'], 'level': 'DEBUG', 'propagate': True, >, 'some.group.*': < 'level': 'WARNING', >, 'some.module': < 'level': 'ERROR', >>, >)

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

Далее описываются логгеры по названиям (либо же, если вы использовали __name__ - по модулям). Также можно указывать сразу группу логгеров, указывая их по маске some.group.* .

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

Аналогично с форматированием - описываем в formatters .

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

Логирование в Python: руководство разработчика

Сталкивались ли вы с трудностями при отладке Python-кода? Если это так — то изучение того, как наладить логирование (журналирование, logging) в Python, способно помочь вам упростить задачи, решаемые при отладке.

Если вы — новичок, то вы, наверняка, привыкли пользоваться командой print() , выводя с её помощью определённые значения в ходе работы программы, проверяя, работает ли код так, как от него ожидается. Использование print() вполне может оправдать себя при отладке маленьких Python-программ. Но, когда вы перейдёте к более крупным и сложным проектам, вам понадобится постоянный журнал, содержащий больше информации о поведении вашего кода, помогающий вам планомерно отлаживать и отслеживать ошибки.

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

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

Начало работы с Python-модулем logging

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

import logging

Встроенный модуль логирования Python даёт нам простой в использовании функционал и предусматривает пять уровней логирования. Чем выше уровень — тем серьёзнее неприятность, о которой сообщает соответствующая запись. Самый низкий уровень логирования — это debug (10) , а самый высокий — это critical (50) .

Дадим краткие характеристики уровней логирования:

  • Debug (10) : самый низкий уровень логирования, предназначенный для отладочных сообщений, для вывода диагностической информации о приложении.
  • Info (20) : этот уровень предназначен для вывода данных о фрагментах кода, работающих так, как ожидается.
  • Warning (30) : этот уровень логирования предусматривает вывод предупреждений, он применяется для записи сведений о событиях, на которые программист обычно обращает внимание. Такие события вполне могут привести к проблемам при работе приложения. Если явно не задать уровень логирования — по умолчанию используется именно warning .
  • Error (40) : этот уровень логирования предусматривает вывод сведений об ошибках — о том, что часть приложения работает не так как ожидается, о том, что программа не смогла правильно выполниться.
  • Critical (50) : этот уровень используется для вывода сведений об очень серьёзных ошибках, наличие которых угрожает нормальному функционированию всего приложения. Если не исправить такую ошибку — это может привести к тому, что приложение прекратит работу.

В следующем фрагменте кода показано использование вышеперечисленных уровней логирования при выводе нескольких сообщений. Здесь используется синтаксическая конструкция logging.() .

logging.debug("A DEBUG Message") logging.info("An INFO") logging.warning("A WARNING") logging.error("An ERROR") logging.critical("A message of CRITICAL severity")

Ниже приведён результат выполнения этого кода. Как видите, сообщения, выведенные с уровнями логирования warning , error и critical , попадают в консоль. А сообщения с уровнями debug и info — не попадают.

WARNING:root:A WARNING ERROR:root:An ERROR CRITICAL:root:A message of CRITICAL severity

Это так из-за того, что в консоль выводятся лишь сообщения с уровнями от warning и выше. Но это можно изменить, настроив логгер и указав ему, что в консоль надо выводить сообщения, начиная с некоего, заданного вами, уровня логирования.

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

Обратите внимание на то, что в примере, который мы будем тут разбирать, весь код находится в файле main.py . Когда мы производим рефакторинг существующего кода или добавляем новые модули — мы сообщаем о том, в какой файл (имя которого построено по схеме .py ) попадает новый код. Это поможет вам воспроизвести у себя то, о чём тут идёт речь.

Логирование в файл

Для того чтобы настроить простую систему логирования в файл — можно воспользоваться конструктором basicConfig() . Вот как это выглядит:

logging.basicConfig(level=logging.INFO, filename="py_log.log",filemode="w") logging.debug("A DEBUG Message") logging.info("An INFO") logging.warning("A WARNING") logging.error("An ERROR") logging.critical("A message of CRITICAL severity")

Поговорим о логгере root , рассмотрим параметры basicConfig() :

  • level : это — уровень, на котором нужно начинать логирование. Если он установлен в info — это значит, что все сообщения с уровнем debug игнорируются.
  • filename : этот параметр указывает на объект обработчика файла. Тут можно указать имя файла, в который нужно осуществлять логирование.
  • filemode : это — необязательный параметр, указывающий режим, в котором предполагается работать с файлом журнала, заданным параметром filename . Установка filemode в значение w (write, запись) приводит к тому, что логи перезаписываются при каждом запуске модуля. По умолчанию параметр filemode установлен в значение a (append, присоединение), то есть — в файл будут попадать записи из всех сеансов работы программы.

После выполнения модуля main можно будет увидеть, что в текущей рабочей директории был создан файл журнала, py_log.log .

В текущей рабочей директории был создан файл журнала py_log.log

Так как мы установили уровень логирования в значение info — в файл попадут записи с уровнем info и с более высокими уровнями.

Так как уровень логирования установлен в info, в файл, помимо сообщений уровня warning, error и critical, попадут и записи уровня info

Записи в лог-файле имеют формат :: . По умолчанию , имя логгера, установлено в root , так как мы пока не настраивали пользовательские логгеры.

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

Помимо базовой информации, выводимой в лог, может понадобится снабдить записи отметками времени, указывающими на момент вывода той или иной записи. Это упрощает анализ логов. Сделать это можно, воспользовавшись параметром конструктора format :

logging.basicConfig(level=logging.INFO, filename="py_log.log",filemode="w", format="%(asctime)s %(levelname)s %(message)s") logging.debug("A DEBUG Message") logging.info("An INFO") logging.warning("A WARNING") logging.error("An ERROR") logging.critical("A message of CRITICAL severity")

Благодаря настройке параметра format к записям добавлены временные метки, представленные в виде, удобном для восприятия

Существуют и многие другие атрибуты записи лога, которыми можно воспользоваться для того чтобы настроить внешний вид сообщений в лог-файле. Настраивая поведение логгера root — так, как это показано выше, проследите за тем, чтобы конструктор logging.basicConfig() вызывался бы лишь один раз. Обычно это делается в начале программы, до использования команд логирования. Последующие вызовы конструктора ничего не изменят — если только не установить параметр force в значение True .

Логирование значений переменных и исключений

Модифицируем файл main.py . Скажем — тут будут две переменных — x и y , и нам нужно вычислить значение выражения x/y . Мы знаем о том, что при y=0 мы столкнёмся с ошибкой ZeroDivisionError . Обработать эту ошибку можно в виде исключения с использованием блока try/except .

Далее — нужно залогировать исключение вместе с данными трассировки стека. Чтобы это сделать — можно воспользоваться logging.error(message, exc_info=True) . Запустите следующий код и посмотрите на то, как в файл попадают записи с уровнем логирования info , указывающие на то, что код работает так, как ожидается.

x = 3 y = 4 logging.info(f"The values of x and y are and .") try: x/y logging.info(f"x/y successful with result: .") except ZeroDivisionError as err: logging.error("ZeroDivisionError",exc_info=True)

В файл журнала попали записи с уровнем логирования info

Теперь установите значение y в 0 и снова запустите модуль.

Исследуя лог-файл py_log.log , вы увидите, что сведения об исключении были записаны в него вместе со стек-трейсом.

x = 4 y = 0 logging.info(f"The values of x and y are and .") try: x/y logging.info(f"x/y successful with result: .") except ZeroDivisionError as err: logging.error("ZeroDivisionError",exc_info=True)

В журнале появилась запись об ошибке вместе с результатами трассировки стека

Теперь модифицируем код так, чтобы в нём имелись бы списки значений x и y , для которых нужно вычислить коэффициенты x/y . Для логирования исключений ещё можно воспользоваться конструкцией logging.exception() .

x_vals = [2,3,6,4,10] y_vals = [5,7,12,0,1] for x_val,y_val in zip(x_vals,y_vals): x,y = x_val,y_val logging.info(f"The values of x and y are and .") try: x/y logging.info(f"x/y successful with result: .") except ZeroDivisionError as err: logging.exception("ZeroDivisionError")

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

Записи об успешных попытках вычисления коэффициента и об исключении

Настройка логирования с помощью пользовательских логгеров, обработчиков и форматировщиков

Отрефакторим код, который у нас уже есть. Объявим функцию test_division .

def test_division(x,y): try: x/y logger2.info(f"x/y successful with result: .") except ZeroDivisionError as err: logger2.exception("ZeroDivisionError")

Объявление этой функции находится в модуле test_div . В модуле main будут лишь вызовы функций. Настроим пользовательские логгеры в модулях main и test_div , проиллюстрировав это примерами кода.

Настройка пользовательского логгера для модуля test_div
import logging logger2 = logging.getLogger(__name__) logger2.setLevel(logging.INFO) # настройка обработчика и форматировщика для logger2 handler2 = logging.FileHandler(f".log", mode='w') formatter2 = logging.Formatter("%(name)s %(asctime)s %(levelname)s %(message)s") # добавление форматировщика к обработчику handler2.setFormatter(formatter2) # добавление обработчика к логгеру logger2.addHandler(handler2) logger2.info(f"Testing the custom logger for module . ") def test_division(x,y): try: x/y logger2.info(f"x/y successful with result: .") except ZeroDivisionError as err: logger2.exception("ZeroDivisionError")
Настройка пользовательского логгера для модуля main
import logging from test_div import test_division # получение пользовательского логгера и установка уровня логирования py_logger = logging.getLogger(__name__) py_logger.setLevel(logging.INFO) # настройка обработчика и форматировщика в соответствии с нашими нуждами py_handler = logging.FileHandler(f".log", mode='w') py_formatter = logging.Formatter("%(name)s %(asctime)s %(levelname)s %(message)s") # добавление форматировщика к обработчику py_handler.setFormatter(py_formatter) # добавление обработчика к логгеру py_logger.addHandler(py_handler) py_logger.info(f"Testing the custom logger for module . ") x_vals = [2,3,6,4,10] y_vals = [5,7,12,0,1] for x_val,y_val in zip(x_vals,y_vals): x,y = x_val, y_val # вызов test_division test_division(x,y) py_logger.info(f"Call test_division with args and ") 

Разберёмся с тем, что происходит коде, где настраиваются пользовательские логгеры.

Сначала мы получаем логгер и задаём уровень логирования. Команда logging.getLogger(name) возвращает логгер с заданным именем в том случае, если он существует. В противном случае она создаёт логгер с заданным именем. На практике имя логгера устанавливают с использованием специальной переменной name , которая соответствует имени модуля. Мы назначаем объект логгера переменной. Затем мы, используя команду logging.setLevel(level) , устанавливаем нужный нам уровень логирования.

Далее мы настраиваем обработчик. Так как мы хотим записывать сведения о событиях в файл, мы пользуемся FileHandler . Конструкция logging.FileHandler(filename) возвращает объект обработчика файла. Помимо имени лог-файла, можно, что необязательно, задать режим работы с этим файлом. В данном примере режим ( mode ) установлен в значение write . Есть и другие обработчики, например — StreamHandler , HTTPHandler , SMTPHandler .

Затем мы создаём объект форматировщика, используя конструкцию logging.Formatter(format) . В этом примере мы помещаем имя логгера (строку) в начале форматной строки, а потом идёт то, чем мы уже пользовались ранее при оформлении сообщений.

Потом мы добавляем форматировщик к обработчику, пользуясь конструкцией вида .setFormatter() . А в итоге добавляем обработчик к объекту логгера, пользуясь конструкцией .addHandler() .

Теперь можно запустить модуль main и исследовать сгенерированные лог-файлы.

Лог-файл main.logЛог-файл test_div.log

Рекомендации по организации логирования в Python

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

  • Устанавливайте оптимальный уровень логирования. Логи полезны лишь тогда, когда их можно использовать для отслеживания важных ошибок, которые нужно исправлять. Подберите такой уровень логирования, который соответствует специфике конкретного приложения. Вывод в лог сообщений о слишком большом количестве событий может быть, с точки зрения отладки, не самой удачной стратегией. Дело в том, что при таком подходе возникнут сложности с фильтрацией логов при поиске ошибок, которым нужно немедленно уделить внимание.
  • Конфигурируйте логгеры на уровне модуля. Когда вы работаете над приложением, состоящим из множества модулей — вам стоит задуматься о том, чтобы настроить свой логгер для каждого модуля. Установка имени логгера в name помогает идентифицировать модуль приложения, в котором имеются проблемы, нуждающиеся в решении.
  • Включайте в состав сообщений логов отметку времени и обеспечьте единообразное форматирование сообщений. Всегда включайте в сообщения логов отметки времени, так как они полезны в деле поиска того момента, когда произошла ошибка. Единообразно форматируйте сообщения логов, придерживаясь одного и того же подхода в разных модулях.
  • Применяйте ротацию лог-файлов ради упрощения отладки. При работе над большим приложением, в состав которого входит несколько модулей, вы, вполне вероятно, столкнётесь с тем, что размер ваших лог-файлов окажется очень большим. Очень длинные логи сложно просматривать в поисках ошибок. Поэтому стоит подумать о ротации лог-файлов. Сделать это можно, воспользовавшись обработчиком RotatingFileHandler , применив конструкцию, которая строится по следующей схеме: logging.handlers.RotatingFileHandler(filename, maxBytes, backupCount) . Когда размер текущего лог-файла достигнет размера maxBytes , следующие записи будут попадать в другие файлы, количество которых зависит от значения параметра backupCount . Если установить этот параметр в значение K — у вас будет K файлов журнала.

Сильные и слабые стороны логирования

Теперь, когда мы разобрались с основами логирования в Python, поговорим о сильных и слабых сторонах этого механизма.

Мы уже видели, как логирование позволяет поддерживать файлы журналов для различных модулей, из которых состоит приложение. Мы, кроме того, можем конфигурировать подсистему логирования и подстраивать её под свои нужды. Но эта система не лишена недостатков. Даже когда уровень логирования устанавливают в значение warning , или в любое значение, которое выше warning , размеры лог-файлов способны быстро увеличиваться. Происходит это в том случае, когда в один и тот же журнал пишут данные, полученные после нескольких сеансов работы с приложением. В результате использование лог-файлов для отладки программ превращается в нетривиальную задачу.

Кроме того, исследование логов ошибок — это сложно, особенно в том случае, если сообщения об ошибках не содержат достаточных сведений о контекстах, в которых происходят ошибки. Когда выполняют команду logging.error(message) , не устанавливая при этом exc_info в True , сложно обнаружить и исследовать первопричину ошибки в том случае, если сообщение об ошибке не слишком информативно.

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

В следующем разделе мы поговорим о том, как интегрировать в Python-проект поддержку Sentry, что позволит упростить процесс отладки кода.

Интеграция Sentry в Python-проект

Установить Sentry Python SDK можно, воспользовавшись менеджером пакетов pip .

pip install sentry-sdk

После установки SDK для настройки мониторинга приложения нужно воспользоваться таким кодом:

sentry_sdk.init( dsn , traces_sample_rate=0.85, )

Как можно видеть — вам, для настройки мониторинга, понадобится ключ dsn . DSN расшифровывается как Data Source Name (имя источника данных). Найти этот ключ можно, перейдя в Your-Project > Settings > Client Keys (DSN) .

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

Логи в интерфейсе Sentry.io

Изучая подробности об ошибке, вы можете увидеть, что Sentry предоставляет подробную информацию о том, где именно произошла ошибка, а так же — об аргументах x и y , работа с которыми привела к появлению исключения.

Подробные сведения об исключении

Продолжая изучение логов, можно увидеть, помимо записей уровня error , записи уровня info . Налаживая мониторинг приложения с использованием Sentry, нужно учитывать, что эта платформа интегрирована с модулем logging . Вспомните — в нашем экспериментальном проекте уровень логирования был установлен в значение info . В результате Sentry записывает все события, уровень которых соответствует info и более высоким уровням, делая это в стиле «навигационной цепочки», что упрощает отслеживание ошибок.

Sentry позволяет фильтровать записи по уровням логирования, таким, как info и error . Это удобнее, чем просмотр больших лог-файлов в поиске потенциальных ошибок и сопутствующих сведений. Это позволяет назначать решению проблем приоритеты, зависящие от серьёзности этих проблем, и, кроме того, позволяет, используя навигационные цепочки, находить источники неполадок.

Фильтрация записей по уровням логирования

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

Сведения о проекте

Код, использованный в данном руководстве, можно найти в этом GitHub-репозитории.

Итоги

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

Когда вы будете работать над своим следующим Python-проектом — не забудьте реализовать в нём механизмы логирования. И можете испытать бесплатную пробную версию Sentry.

О, а приходите к нам работать? �� ��

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

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

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Как быстро добавить логгер в проект на Python

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

Вот короткая версия того, что было в предыдущих частях:

  • логгер — это специальный модуль или библиотека, которая фиксирует определённые события в нашей программе;
  • на что реагировать и как фиксировать — решает программист;
  • для этого он пишет специальную команду типа лог.добавить('Запускаем главный модуль') или лог.ошибка('Файл не найден') ;
  • сам по себе лог ничего не исправляет — он только сообщает о том, что происходит в программе;
  • есть несколько основных уровней логирования — ошибки, предупреждения, информационные сообщения, сообщения отладки и разовые события;
  • эти уровни нужны для того, чтобы фильтровать сообщения по степени важности, например посмотреть только сообщения об ошибках или о штатных срабатываниях;
  • лог можно выводить в консоль, в файл или передавать с помощью сокетов или API в другую программу;
  • когда мы подключили логгер в наш проект с арканоидом на JavaScript, то неожиданно для себя нашли ошибку, которую без логгера мы не замечали.

Теперь поработаем с логгером в проекте на Python и посмотрим, найдём ли мы ещё ошибки в своём коде.

Настраиваем логгер

Python хорош тем, что с ним в комплекте идёт много полезных библиотек, в том числе логгер. Поэтому мы будем использовать встроенный логгер, который подключается так:

С этого момента мы можем использовать логгер в любом месте, просто указав нужный уровень сообщения, например:

logging.debug("Это сообщение для отладки программы") logging.info("Информационное сообщение") logging.warning('Предупреждение — нужно проверить, всё ли в порядке') logging.error("Ошибка! Что-то пошло не так")

Если запустить этот код, то мы увидим только последние два сообщения:

Как быстро добавить логгер в проект на Python

Дело в том, что логгер по умолчанию выводит только сообщения с уровнем warning и выше. Чтобы вывести все сообщения начиная с самого нижнего уровня debug, используем такую команду:

Теперь все сообщения на месте:

Как быстро добавить логгер в проект на Python

Выводим сообщения в файл

По умолчанию логгер пишет всё в консоль, но можно отправлять все сообщения в файл. Для этого в настройках логгера укажем имя файла:

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

Файл появится в той же папке, что и программа, и мы сможем открыть его в любом текстовом редакторе:

Как быстро добавить логгер в проект на Python

Но при этом в консоли мы ничего не увидим — весь вывод ушёл в файл. Чтобы оставить сообщения в консоли и при этом добавлять их в файл, сделаем немного программистского колдунства — вручную настроим потоки вывода и объединим их в одно целое:

# добавляем поток вывода в файл file_log = logging.FileHandler("thecode.log") # и вывод в консоль console_out = logging.StreamHandler() # указываем эти два потока в настройках логгера logging.basicConfig(handlers=(file_log, console_out), level=logging.DEBUG)

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

Добавляем логгер в проект

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

Чтобы поработать с логгером на практике, достаточно любого, даже самого простого проекта на Python. Мы возьмём лёгкую задачу про безумного рекрутера на Python и добавим в неё лог — так будет проще понять, как всё работает. Если освоитесь с этим, то сможете подключить логгер в проект любой сложности — принцип будет точно такой же.

Мы добавим несколько логов

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

Вот готовый код:

# подключаем логгер import logging # добавляем поток вывода в файл file_log = logging.FileHandler("thecode.log") # и вывод в консоль console_out = logging.StreamHandler() # указываем эти два потока в настройках логгера logging.basicConfig(handlers=(file_log, console_out), level=logging.DEBUG) logging.info("Объявляем переменные") # Сколько джуниоров было на старте в старом офисе junior = 1 # Сколько дней ушло на заполнение старого офиса day = 30 # Сделаем диапазон для цикла month = range(1,day) # Каждый день в старом офисе… logging.warning("Входим в цикл удваивания старых джуниоров") for current_day in month: # …удваивалось количество джуниоров junior = junior * 2 logging.debug("Количество старых джуниоров: " + str(junior)) # В новом офисе у нас уже два джуниора на старте new_junior = 2 # Заведём переменную для подсчёта дней в новом офисе new_day = 0 # Пока в новом офисе не станет столько же людей, как и в новом… logging.warning("Входим в цикл удваивания новых джуниоров") while new_junior 

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

Как быстро добавить логгер в проект на Python

Так произошло из-за того, что мы указали уровень DEBUG в настройках логгера. Изменим их так, чтобы логгер нам их не показывал, а всё другое — оставил:

logging.basicConfig(handlers=(file_log, console_out), level=logging.INFO)

Как быстро добавить логгер в проект на Python

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

Любишь Python? Зарабатывай на нём!
Изучите самый модный язык программирования и станьте крутым бэкенд-разработчиком. Старт — бесплатно.

Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём!

Получите ИТ-профессию

В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.

Логирование в Python

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

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

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

Всегда лучше работать через логи, а не выводить на печать через print() .

Библиотека logging в Python

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

«Внимание, красный уровень!»

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

Запустите файл с таким кодом:

import logging logging.debug('123') # Когда нужна отладочная информация logging.info('Сообщение отправлено') # Когда нужна дополнительная информация logging.warning('Большая нагрузка, хелп') # Когда что-то идёт не так, но работает logging.error('Бот не смог отправить сообщение') # Когда что-то сломалось logging.critical('Всё упало! Зовите админа!1!111') # Когда всё совсем плохо 

Логи будут выведены так:

WARNING:root:Большая нагрузка, хелп ERROR:root:Бот не смог отправить сообщение CRITICAL:root:Всё упало! Зовите админа!1!111 

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

Разные уровни логирования нужны для сортировки сообщений по важности. Это облегчает поиск и работу с сообщениями.

DEBUG — уровень отладки: на этом уровне выводится всякая служебная информация: «Произошёл запуск функции», «переменная содержит такое-то значение». Это сообщения о том, что происходит в коде, информация для разработчика.

INFO — информация о текущих событиях: этот уровень применяют, если нужно убедиться, что всё идёт по плану: «Письмо отправлено», «Запись в базе создана».

WARNING — «тревожный звоночек»: проблемы нет, но есть что-то, что может привести к проблеме; что-то, на что следует обратить внимание.

ERROR — это ошибка: что-то работает не так, как нужно. Требуется вмешательство и исправление ошибки.

CRITICAL — случилось что-то совсем критичное: надо всё бросать и бежать к компьютеру; всё сломалось. Не очень часто используется на практике, обычно бывает достаточно ERROR.

Настройка логов

По умолчанию в терминал выводятся только наиболее важные логи, от уровня WARNING и выше: WARNING → ERROR → CRITICAL.

Сообщения с уровнями ниже WARNING, то есть DEBUG и INFO, по умолчанию отключены и никуда не выводятся.

Эти настройки можно изменить, вызвав метод для конфигурации логов basicConfig() и передав в параметр level уровень, с которого нужно фиксировать сообщения:

logging.basicConfig(level=logging.DEBUG) 

Форматирование логов

Без предварительной настройки логи записываются в таком формате:

УРОВЕНЬ ВАЖНОСТИ:текущий пользователь:сообщение 

Этот формат можно изменить: в метод basicConfig() передаётся параметр format , а в нём описывается содержимое лога:

logging.basicConfig(format='%(asctime)s, %(levelname)s, %(name)s, %(message)s') 

asctime — это время события,
levelname — уровень важности,
name — имя логера,
message — текст сообщения.

Для описания атрибутов используется «%-форматирование»: атрибут берётся в скобки, перед скобками ставится символ % , а после скобок указывают тип данных, например:

s — строка (string),
d — число (digit).

Помимо времени и уровня есть и другие полезные атрибуты для форматирования логов:

filename — имя файла, из которого отправлено сообщение в лог;
funcName — имя функции, из которой отправлено сообщение в лог;
lineno — номер строки в том файле, из которого отправлено сообщение в лог.

Сохранение логов в файл

Чтобы сохранять лог-сообщения в файл, нужно передать соответствующие параметры в метод basicConfig() , указав имя файла с расширением .log и режим записи:

logging.basicConfig(filename='main.log', filemode='w') 

Значения параметра filemode :

w — содержимое файла перезаписывается при каждом запуске программы;
x — создать файл и записывать логи в него; если файл с таким именем уже существует — будет ошибка;
a — дописывать новые логи в конец указанного файла.

Ротация логов

Логи со временем растут, занимают всё больше места и, в результате, могут занять всё дисковое пространство. Чтобы контролировать объём логов, можно выставить ограничение на размер этих файлов и на их количество.

Когда размер первого файла достигнет установленного предела — будет создан следующий файл, а когда количество файлов дойдёт до заданного количества — начнёт перезаписываться самый первый файл. И так по кругу: это называется ротацией логов.

Логер — это такая коробка или корзина, в которую Python скидывает лог-сообщения. Логер обрабатывает эти сообщения тем способом, который для него установлен.

import logging from logging.handlers import RotatingFileHandler # здесь мы задаем глобальную конфигурацию для всех логеров logging.basicConfig( level=logging.DEBUG, filename='program.log', format='%(asctime)s, %(levelname)s, %(message)s, %(name)s' ) # а тут настраиваем логгер для текущего файла .py logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) handler = RotatingFileHandler('my_logger.log', maxBytes=50000000, backupCount=5) logger.addHandler(handler) 

Логеров может быть несколько: обычно пишут отдельный логер для каждого пакета. Имя логерам традиционно дают по имени __name__ пакета, для которого он создан. Когда в проекте больше одного пакета — такая структура логирования упрощает работу.

Далее в коде создан handler и добавлен в логер. Handler — это диспетчер логов, он берёт готовые логи, переданные в логер и обрабатывает их нужным образом.

В листинге применён RotatingFileHandler, он управляет ротацией логов: добавляет новые записи в файл, следит за объёмом и количеством лог-файлов. В параметрах RotatingFileHandler указывается максимальный размер одного лог-файла и предельное количество таких файлов. Также в нём указывается путь и имя файлов, так что при использовании RotatingFileHandler не нужно создавать файл через basicConfig .

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

tail -f main.log 

Логи из файла main.log будут выводиться в терминал, отображая самые последние события.

Логирование исключений

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

Самый простой способ логирования исключений:

try: 42 / 0 except Exception as error: logging.error(error, exc_info=True) 

Без параметра exc_info в лог запишется только текст исключения. Существует более компактная запись, с помощью метода logging.exception() :

try: 42 / 0 except Exception: logging.exception() 

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

Похожие записи:
  1. Библиотека python-telegram-bot
  2. Логирование в Python. Задание
  3. Бот в Telegram
  4. Дополнительные возможности ORM

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *