Работа с логами: модуль logging и настройка вывода
Зачем нужны логи? 🧐
Логи — это хлеб насущный для любого разработчика. Они помогают:
- Отслеживать работу программы в реальном времени 🕵️♂️
- Находить ошибки даже в продакшене 🔍
- Анализировать производительность системы ⚡
Без логов мы как слепые котята в темной комнате — тыкаемся наугад!
Базовый пример: logging за 30 секунд ⏱️
Вот минимальный рабочий пример, который можно добавить в любой проект:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.debug("Это сообщение не появится")
logger.info("Привет, это информационное сообщение!")
logger.warning("А это уже тревожный звоночек")
Вывод:
INFO:__main__: Привет, это информационное сообщение!
WARNING:__main__: А это уже тревожный звоночек
Уровни логирования: от шепота до крика 📢
Модуль logging предлагает 5 уровней важности:
DEBUG— детальная отладочная информация (10)INFO— подтверждение, что всё работает (20)WARNING— что-то пошло не так, но программа работает (30)ERROR— серьёзная проблема, часть функционала сломана (40)CRITICAL— программа не может продолжать работу (50)
logger.log(logging.DEBUG, "Подробности для разработчиков")
logger.log(45, "Можно даже создать свой уровень!") # Между ERROR и CRITICAL
Настройка формата вывода 🎨
Стандартный вывод скучен? Давайте кастомизировать!
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%d-%m-%Y %H:%M:%S",
level=logging.DEBUG
)
logger.info("Теперь сообщения выглядят круче!")
Вывод:
12-01-2024 14:30:45 - __main__ - INFO - Теперь сообщения выглядят круче!
Доступные параметры формата:
%(asctime)s— время события%(levelname)s— уровень важности%(message)s— текст сообщения%(name)s— имя логгера%(filename)s— имя файла, откуда вызван лог
Куда писать логи? Файлы, консоль и не только 📂
Запись в файл
logging.basicConfig(
filename="app.log",
filemode="a", # append (по умолчанию)
level=logging.INFO
)
Одновременно в файл и консоль
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Создаем обработчики
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler("debug.log")
# Добавляем обработчики к логгеру
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.info("Сообщение пойдет в оба места!")
Фильтрация и ротация логов 🌀
Фильтруем сообщения
class NoSpamFilter(logging.Filter):
def filter(self, record):
return "спам" not in record.getMessage()
logger.addFilter(NoSpamFilter())
logger.info("Это нормальное сообщение") # Будет выведено
logger.info("Это сообщение-спам") # Будет проигнорировано
Ротация логов (чтобы не засорять диск)
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
"app.log",
maxBytes=1024*1024, # 1 MB
backupCount=5 # Храним 5 архивных копий
)
logger.addHandler(handler)
Продвинутые техники 🚀
Логирование исключений
try:
1 / 0
except Exception:
logger.exception("Произошла ошибка:") # Автоматически добавит traceback
Вывод:
ERROR:__main__: Произошла ошибка:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
Логирование в многопоточных приложениях
from concurrent.futures import ThreadPoolExecutor
import threading
def worker():
logger.info(f"Поток {threading.get_ident()} начал работу")
with ThreadPoolExecutor(max_workers=3) as executor:
executor.submit(worker)
executor.submit(worker)
Лучшие практики от Данилы Бежина 🏆
- Не изобретайте велосипед — используйте
loggingвместоprint - Разные логгеры для разных модулей —
logger = logging.getLogger(__name__) - Настройка через код, а не конфиги — так понятнее и гибче
- DEBUG только при разработке — в продакшене
INFOили выше - Логируйте контекст — "Не удалось сохранить пользователя ID=42" лучше чем "Ошибка сохранения"
Больше полезных фишек — в плейлисте по логированию на канале Данилы.
Реальный пример: логирование веб-запросов 🌐
from flask import Flask
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# Настройка логирования
handler = RotatingFileHandler('web.log', maxBytes=10000, backupCount=3)
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
@app.route('/')
def home():
app.logger.info('Получен запрос к главной странице')
return "Добро пожаловать!"
if __name__ == '__main__':
app.run()
Теперь все запросы будут аккуратно сохраняться в web.log с деталями!