В этой заметке мы попробуем выбрать библиотеку для логирования в Python. Логи помогают зафиксировать и понять, что пошло не так в работе вашего микросервиса. Так же в логи часто пишут информационные сообщения. Например: параметры, метрики качества и ход обучения модели. Пример куска лога обучения модели:
logging vs loguru
Библиотека Logging является стандартным решением для логирования в Python. Logging часто критикуем за сложность конфигов, неудобство настроек разного уровня логирования и ротации файлов логов.
Loguru же наоборот, позиционирует себя как максимально простая библиотека для логирования в Python.
Попробуем разробрать плюсы и минусы каждой библиотеки и выбрать лучшее решение для своих задач.
Базовое применение
Сравним код для самого базового логирования. Будем записывать лог в файл.
import logging
logging.basicConfig(filename='logs/logs.log', level=logging.DEBUG)
logging.debug('Error')
logging.info('Information message')
logging.warning('Warning')
Разберем немножко код. basicConfig
— создаёт базовую конфигурацию для нашего логирования, filename
— путь к файлу лога, level
— уровень логирования. При logging.DEBUG
он будет пропускать все записи в лог.
from loguru import logger
logger.add('logs/logs.log', level='DEBUG')
logger.debug('Error')
logger.info('Information message')
logger.warning('Warning')
Думаю тут всё понятно и код выглядит весьма похоже. Но посмотрим на результат в файле и консоли.
DEBUG:root:Error
INFO:root:Information message
WARNING:root:Warning
2021-02-04 17:44:10.914 | DEBUG | main::14 - Error
2021-02-04 17:44:10.915 | INFO | main::15 - Information message
2021-02-04 17:44:10.915 | WARNING | main::16 - Warning
И в консоли и в файле Loguru выглядит поинформативней сразу по умолчанию.
Форматирование
Давайте теперь попробуем сделать форматирование в Logger. Для этого есть метод .setFormatter
Будем выводить время события, тип и само событие как в Loguru.
import logging
import sys
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
fileHandler = logging.FileHandler('logs/logs.log')
fileHandler.setFormatter(logging.Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s'))
logger.addHandler(fileHandler)
streamHandler = logging.StreamHandler(stream=sys.stdout)
streamHandler.setFormatter(logging.Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s'))
logger.addHandler(streamHandler)
logging.debug('Error')
logging.info('Information message')
logging.warning('Warning')
Ох ты ж! Кода стало гораздо больше. Давайте разберем новые классы и методы. Для начала у нас есть класс Handler
. FileHandler
для записи в файл и StreamHandler
для запись в консоль. Затем нужно с помощью метода addHandler
передать их в наш logger. В документации вы найдете еще несколько Handler
.
Теперь разберемся с классом Formatter
. Из названия понятно, что этот класс отвечает за формат записи нашего лога. В нашем примере мы добавили помимо самого сообщения время записи и его тип. Теперь наш лог выглядит так:
[2021-02-04 21:28:28,283: DEBUG] Error
[2021-02-04 21:28:28,283: INFO] Information message
[2021-02-04 21:28:28,283: WARNING] Warning
В Loguru тоже есть форматирование. Делается это так: logger.add('logs/logs.log', level='DEBUG', format="{time} {level} {message}")
Ротация / очистка / архивирование
Часто возникает необходимость ротировать лог — то есть архивировать, очищать или создать новый лог файл с заданной переодичностью. Например это нужно когда логи становятся слишком большими. Снова сравним код.
import logging
import time
from logging.handlers import RotatingFileHandler
def create_rotating_log(path):
logger = logging.getLogger("Rotating Log")
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(path, maxBytes=20, backupCount=5)
logger.addHandler(handler)
for i in range(10):
logger.info("This is test log line %s" % i)
time.sleep(1.5)
if name == "main":
log_file = "test.log"
create_rotating_log(log_file)
Тут используется RotatingFileHandler
. maxBytes
— максимальный размер файла, backupCount
— сколько файлов хранить.
Посмотрим как это можно сделать в Loguru:
logger.add("file_1.log", rotation="500 MB") # Пишет в новый файл после достижения размера лога 500 MB
logger.add("file_2.log", rotation="12:00") # В 12:00 создаёт новый файл
logger.add("file_X.log", retention="10 days") # Очищает наш лог после 10 дней
logger.add("file_Y.log", compression="zip") # Архивирует наши логи
Опять всё выглядит попроще и удобнее.
Обработка исключений
В Logging есть отдельный метод — exception
. Это достаточно удобно и нужно применять его в блоке try except.
import logging
def my_function(x, y, z):
return x / (y * z)
try:
my_function(1, 2, 0)
except ZeroDivisionError:
logging.exception("message")
Лог будет выгледеть так:
ERROR:root:message
Traceback (most recent call last):
File "logs.py", line 5, in
my_function(1, 2, 0)
File "logs.py", line 3, in my_function
return x / (y * z)
ZeroDivisionError: division by zero
В Loguru нам будет нужно использовать декоратор @logger.catch
:
from loguru import logger
@logger.catch
def my_function(x, y, z):
return x / (y * z)
my_function(1, 2, 0)
Вау! Лог выглядит круто. Даже показывает значение переменных:
Заключение
В этой заметке достаточно бегло рассмотрели две библиотеки для логирования в Python. Обе библиотеки имеют еще ряд фишек и возможностей. Рекомендую подробнее почитать в документации. Мне нравится Loguru и он отлично подходит для использования в пайплайнах машинного обучения или для тренировок нейронных сетей. В небольших микросервисах Loguru тоже отлично подходит. Единственный минус использования Loguru, который я нашел это еще одна лишняя зависимость в вашем проекте.
Документация
Loguru — https://github.com/Delgan/loguru
Logging — https://docs.python.org/3/howto/logging.html
Разговоры о Data Science – закрытый канал для общения на тему карьеры, развития профессиональных навыков и применения навыков на работе.