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

Что такое логирование в python

  • автор:

Логирование ошибок

Чтобы перехватывать ошибки, тоже можно использовать логи. Вот как это делается:

try: 0/0 except Exception as err: logging.error(err) 

В логи выведется ошибка:

division by zero 

Чтобы присоединить трейсбек к логу, нужно добавить аргумент exc_info :

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

Теперь ошибка будет с трейсбеком:

division by zero Traceback (most recent call last): File "main.py", line 2, in 0/0 ZeroDivisionError: division by zero 

А чтобы не писать такой код каждый раз, воспользуйтесь методом exception, он сделает то же самое, но короче и проще:

try: 0/0 except Exception as err: logging.exception(err) 

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

Что читать дальше?

Документацию logging и статьи с других сайтов:

  • Логирование в Python
  • Logging in Python (en)

Попробуйте бесплатные уроки по Python

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

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

О логах в Python

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

  • Во-первых, они обычно запущены на сервере, из-за чего до вывода print будет не добраться. А если программу перезапустить, то все сообщения пропадут и вовсе.
  • Во-вторых, чтобы сообщения были полезны, нужно выводить много всего: время, строку в коде, что случилось… Это всё очень засорит код ненужным мусором.

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

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

  • Можно сортировать по степени важности, времени и т. д.
  • Можно выводить не только в терминал. За вас уже реализован вывод в файл, например.
  • Легко понять где, когда и что произошло.

Как пользоваться

Для ведения логов в Python есть библиотека logging :

import logging logging.debug('Сообщение для дебагинга') logging.info('Произошло какое-то событие. Всё идёт по плану.') logging.warning('Предупреждение, что-то могло сломаться') logging.error('Ошибка, что-то сломалось') logging.critical('МЫ В ОГНЕ ЧТО ДЕЛАТЬ. ') 

Здесь мы создаём записи в логах на разных уровнях важности (от debug до critical). При таком использовании будет выглядеть почти как принты:

WARNING:root:Предупреждение, что-то могло сломаться ERROR:root:Ошибка, что-то сломалось CRITICAL:root:МЫ В ОГНЕ ЧТО ДЕЛАТЬ. 

Вопрос, куда делись первые 2 сообщения? Дело в том, что logging автоматически фильтрует для вас логи по степени важности DEBUG , INFO , WARNING , ERROR и CRITICAL .

По умолчанию logging фиксирует только логи уровня WARNING и выше (т.е. ещё ERROR и CRITICAL), а все логи уровнями ниже — игнорирует (DEBUG и INFO). Это можно изменить в настройках логов:

import logging logging.basicConfig(level=logging.DEBUG) logging.debug('Сообщение уровня DEBUG') 

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

Уровни логирования

Рассмотрим каждый вариант отдельно.

DEBUG

DEBUG — это сообщения для отладки, которые вы оставили для себя. Содержимое переменных и всё такое. Самые не важные. Обычные пользователи их не читают, только программисты и системные администраторы.

INFO

INFO — это сообщения о происходящих событиях, не требующих реакции пользователя. Например: отправлено письмо, зарегистрирован пользователь.

WARNING

WARNING — предупреждения о том, что вскоре может привести к неожиданному поведению программы или же к ошибкам в работе модулей. Например: Файл настроек не найден, использую стандартные.

ERROR

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

CRITICAL

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

Что читать дальше?

Обращайтесь к нашим статьям:

  • Форматирование логов
  • Обработчики логов
  • Логирование ошибок

К статьям с других сайтов:

  • Логирование в Python
  • Logging in Python (en)

Или к документации logging

Попробуйте бесплатные уроки по Python

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

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Эффективное логирование в Python

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

Пишем логгер

Создадим файл loggers.py. Для начала импортируем модули и задаём пару значений по умолчанию — директорию для файла с логом и наименование конфигурационного файла, содержащего шаблоны логирования. Его мы опишем следом.

import os import json import logging import logging.config FOLDER_LOG = "log" LOGGING_CONFIG_FILE = 'loggers.json'

Опишем функцию для создания папки с логом: она принимает наименование для папки, но по умолчанию будет называть её «log». Директорию создаём при помощи модуля os и только в том случае, если такой директории ещё не существует.

def create_log_folder(folder=FOLDER_LOG): if not os.path.exists(folder): os.mkdir(folder)

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

def get_logger(name, template='default'): create_log_folder() with open(LOGGING_CONFIG_FILE, "r") as f: dict_config = json.load(f) dict_config["loggers"][name] = dict_config["loggers"][template] logging.config.dictConfig(dict_config) return logging.getLogger(name)

Для удобства опишем ещё одну функцию — получение стандартного лога. Она ничего не принимает и нужна только для инициализации лога с шаблоном default:

def get_default_logger(): create_log_folder() with open(LOGGING_CONFIG_FILE, "r") as f: logging.config.dictConfig(json.load(f)) return logging.getLogger("default")

Описываем конфигурационный файл

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

 < "version": 1, "disable_existing_loggers": false, "formatters": < "default": < "format": "%(asctime)s - %(processName)-10s - %(name)-10s - %(levelname)-8s - %(message)s" >>, "handlers": < "console": < "class": "logging.StreamHandler", "level": "INFO", "formatter": "default" >, "rotating_file": < "class": "logging.handlers.RotatingFileHandler", "level": "DEBUG", "formatter": "default", "filename": "log/main.log", "maxBytes": 10485760, "backupCount": 20 >>, "loggers": < "default": < "handlers": ["console", "rotating_file"], "level": "DEBUG" >> >

Использование логгера

Теперь давайте представим, что вы выгружаете данные по API и складываете их в базу данных на примере нашего материала про транзакции в SQLAlchemy. Рассмотрим заключительную часть кода: добавим строку с инициализацией стандартного логгера и изменим код так, чтобы сначала в лог выводился offset, затем в случае успеха предложение «Successfully inserted data», а в случае ошибки выводилась сама ошибка и предложение: «Error: tried to insert data but got an error».

logger = get_logger('main') offset = 0 subs_count = get_subs_count(group_id) while offset < subs_count: with engine.connect() as conn: transaction = conn.begin() try: logger.info(f"/ ") df = get_subs_info(group_id, offset) df.to_sql('subscribers', con=conn, if_exists='append', index=False) if offset == 10: raise(ValueError("This is a test errror")) transaction.commit() logger.info(f"Successfully inserted data") except Exception as E: transaction.rollback() logger.error(f"Error: tried to insert but got an error: ") time.sleep(1) offset += 10

Теперь во время работы программы будет отображаться такой вывод, который также будет записан в файл main.log папки log в директории проекта. После завершения работы программы можно исследовать логи, посмотреть, на каких offset возникли проблемы, какие данные не удалось вставить и прочитать текст ошибки:

Модуль logging#

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

Самый простой вариант настройки логирования в скрипте, использовать logging.basicConfig:

import logging logging.basicConfig( format='%(threadName)s %(name)s %(levelname)s: %(message)s', level=logging.INFO) 

В таком варианте настройки:

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

Теперь, чтобы вывести log-сообщение в этом скрипте, надо написать так logging.info("тест") .

Пример скрипта с настройкой логирования: (файл logging_basics.py)

from datetime import datetime import logging import netmiko import yaml # эта строка указывает, что лог-сообщения paramiko будут выводиться # только если они уровня WARNING и выше logging.getLogger("paramiko").setLevel(logging.WARNING) logging.basicConfig( format = '%(threadName)s %(name)s %(levelname)s: %(message)s', level=logging.INFO) def send_show(device, show): start_msg = '===> <> Connection: <>' received_msg = ' <> Received: <>' ip = device["ip"] logging.info(start_msg.format(datetime.now().time(), ip)) with netmiko.ConnectHandler(**device) as ssh: ssh.enable() result = ssh.send_command(show) logging.info(received_msg.format(datetime.now().time(), ip)) return result if __name__ == "__main__": with open('devices.yaml') as f: devices = yaml.safe_load(f) for dev in devices: print(send_show(dev, 'sh clock')) 

При выполнении скрипта, вывод будет таким:

$ python logging_basics.py MainThread root INFO: ===> 12:26:12.767168 Connection: 192.168.100.1 MainThread root INFO: 12:26:18.413913 Connection: 192.168.100.2 MainThread root INFO: 12:26:24.095452 Connection: 192.168.100.3 MainThread root INFO: 

В модуле logging еще очень много возможностей. В этом разделе используется только базовый вариант настройки. Узнать больше о возможностях модуля можно в Logging HOWTO

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

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