Выбираем логирование в Python: logging vs loguru

В этой заметке мы попробуем выбрать библиотеку для логирования в 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
Результат Loguru

И в консоли и в файле 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)

Вау! Лог выглядит круто. Даже показывает значение переменных:

Loguru обработка исключений
Loguru обработка исключений

Заключение

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

Документация

Loguru — https://github.com/Delgan/loguru

Logging — https://docs.python.org/3/howto/logging.html

Мой курс анализ медицинских изображений в Python

Share it

Если вам понравилась заметка - подписывайтесь на мой канал в телеграме https://t.me/renat_alimbekov или вы можете поддержать меня Become a Patron!


Интересные записи в этой рубрике: