Лекция 1. Логирование (Logging)¶
Python предлагает мощную библиотеку логирования в стандартной библиотеке — модуль logging. Многие программисты используют print() для отладки, но logging даёт куда больше возможностей: уровни сообщений, форматирование, конфигурацию через файл/словарь, ротацию файлов, разделение по модулям.
Уровни логирования¶
В порядке возрастания «серьёзности»:
| Уровень | Назначение |
|---|---|
DEBUG |
Подробная отладочная информация — обычно отключена в production. |
INFO |
Подтверждение, что всё идёт по плану. |
WARNING |
Что-то неожиданное, но программа работает. |
ERROR |
Из-за более серьёзной проблемы программа не смогла выполнить какую-то функцию. |
CRITICAL |
Серьёзная ошибка — программа может не выполнять основные функции. |
Самый простой логгер¶
import logging
logging.basicConfig(filename="sample.log", level=logging.INFO)
logging.debug("DEBUG-сообщение — не попадёт в лог (уровень INFO выше)")
logging.info("INFO-сообщение")
logging.error("Что-то пошло не так")
Результат в sample.log:
По умолчанию логи добавляются в файл. Для перезаписи укажите
filemode="w".Часть
rootозначает, что сообщение пришло от корневого логгера. БезbasicConfigлоги выводятся на консоль (stderr).
Логирование исключения¶
import logging
log = logging.getLogger("ex")
try:
1 / 0
except ZeroDivisionError:
log.exception("Деление на ноль!")
# exception() = error() + traceback в лог
Логирование из нескольких модулей¶
Хорошая практика — для каждого модуля иметь свой логгер с именем модуля:
# main.py
import logging
import otherMod
def main():
logging.basicConfig(filename="app.log", level=logging.INFO)
logging.info("Program started")
otherMod.add(7, 8)
logging.info("Done!")
if __name__ == "__main__":
main()
# otherMod.py
import logging
log = logging.getLogger(__name__)
def add(x, y):
log.info("added %s and %s to get %s", x, y, x + y)
return x + y
Чем больше модулей пишут в лог, тем важнее понимать, кто записал сообщение.
Форматирование лога¶
Чтобы в логе была понятная информация (время, имя логгера, уровень, сообщение) — настроим Formatter:
import logging
log = logging.getLogger("exampleApp")
log.setLevel(logging.INFO)
fh = logging.FileHandler("app.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
fh.setFormatter(formatter)
log.addHandler(fh)
log.info("Program started")
Результат:
Полный список атрибутов LogRecord — в официальной документации Python.
Конфигурация: код, файл или словарь¶
Существует три способа сконфигурировать логирование:
1. Через код (показано выше)¶
Гибко, но конфигурация смешивается с кодом приложения.
2. Через INI-файл (logging.config.fileConfig)¶
# logging.conf
[loggers]
keys=root,exampleApp
[handlers]
keys=fileHandler,consoleHandler
[formatters]
keys=myFormatter
[logger_root]
level=WARNING
handlers=consoleHandler
[logger_exampleApp]
level=INFO
handlers=fileHandler
qualname=exampleApp
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=myFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
formatter=myFormatter
args=("app.log",)
[formatter_myFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
import logging
import logging.config
logging.config.fileConfig("logging.conf")
log = logging.getLogger("exampleApp")
log.info("Program started")
3. Через словарь (logging.config.dictConfig) — рекомендуется¶
import logging
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
},
"handlers": {
"file": {
"class": "logging.FileHandler",
"formatter": "default",
"filename": "app.log",
},
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
},
},
"loggers": {
"exampleApp": {
"level": "INFO",
"handlers": ["file", "console"],
},
},
}
logging.config.dictConfig(LOGGING_CONFIG)
log = logging.getLogger("exampleApp")
log.info("Program started")
Словарную конфигурацию удобно хранить в YAML/JSON и подключать в зависимости от окружения (dev/staging/production).
Полезные приёмы¶
Ротация лог-файлов¶
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler("app.log", maxBytes=10_000_000, backupCount=5)
# создаёт app.log, app.log.1, app.log.2, ...
Также есть TimedRotatingFileHandler — ротация по времени (день, час).
Структурированное логирование (JSON)¶
Для production-сервисов часто пишут логи в JSON — их легко парсить ELK/Loki/Splunk:
import json
import logging
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
return json.dumps({
"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
})
Существуют готовые библиотеки: python-json-logger, structlog, loguru (последний — современная альтернатива стандартному logging).
Передача параметров — % vs format¶
В log.info("user=%s", user) строка форматируется только если сообщение реально попадёт в лог. Это эффективнее, чем log.info(f"user={user}") (последняя всегда форматирует).
Логирование в Go (log/slog)¶
Начиная с Go 1.21 в стандартную библиотеку добавлен log/slog — структурированное логирование:
package main
import (
"log/slog"
"os"
)
func main() {
// JSON-handler для production
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger.Info("запуск", "version", "1.0", "env", "production")
logger.Error("сбой подключения", "err", "connection refused", "retry", 3)
// С контекстом — обогащение
log := logger.With("request_id", "abc-123")
log.Info("обработка запроса")
log.Warn("медленный запрос", "duration_ms", 1500)
}
Вывод:
{"time":"2026-06-01T10:30:00Z","level":"INFO","msg":"запуск","version":"1.0","env":"production"}
{"time":"2026-06-01T10:30:01Z","level":"ERROR","msg":"сбой подключения","err":"connection refused","retry":3}
{"time":"2026-06-01T10:30:02Z","level":"INFO","msg":"обработка запроса","request_id":"abc-123"}
Уровни в slog: Debug, Info, Warn, Error. По умолчанию текстовый вывод — slog.NewTextHandler(...); для production — JSON.
Старый пакет log тоже доступен, но для нового кода предпочтителен log/slog.
Что должно быть в логах¶
- Время (с миллисекундами и временной зоной).
- Уровень.
- Имя логгера (модуль / компонент).
- Сообщение — короткое и обобщённое (а не
"User john@example.com tried to log in at 10:30"— лучшеmsg="login attempt"+user="john@example.com"). - Контекстные поля — request_id, user_id, версия приложения и т. д.
- При ошибках — стек-трейс (
logger.exception(...)в Python,slog.Error(..., "err", err)в Go).
Что НЕ должно быть в логах¶
- Пароли, токены, ключи — никогда.
- Персональные данные (PII) — без необходимости и без согласия пользователя.
- Тело больших HTTP-запросов целиком — пишите только статус, размер и ключевые поля.
- Кредитки, медицинские данные — регулируется законами (PCI DSS, HIPAA, GDPR).
Контрольные вопросы¶
- Какие уровни логирования есть в Python? Какой использовать по умолчанию в production?
- Зачем создавать отдельный логгер для каждого модуля?
- В чём отличие
logging.basicConfigотlogging.config.dictConfig? - Что такое handler, formatter, logger?
- Что такое структурированное логирование? В чём его преимущество для production-сервисов?
- Чем
log/slogв Go удобнее «классического» пакетаlog? - Что никогда не следует писать в логи?