logging — система логирования Python
Зачем нужен logging
print() подходит для отладки, но в production-коде нужна полноценная система логирования. Модуль logging предоставляет уровни важности, множество обработчиков, форматирование и иерархию логгеров.
import logging
# Basic configuration — quick start
logging.basicConfig(level=logging.INFO)
logging.debug("Отладочная информация") # Not shown (below INFO)
logging.info("Приложение запущено") # Shown
logging.warning("Диск почти полон") # Shown
logging.error("Не удалось подключиться") # Shown
logging.critical("Фатальная ошибка!") # Shown
Уровни логирования
| Уровень | Значение | Когда использовать |
|---|---|---|
DEBUG |
10 | Детальная отладочная информация |
INFO |
20 | Подтверждение нормальной работы |
WARNING |
30 | Что-то неожиданное, но работа продолжается |
ERROR |
40 | Ошибка, часть функциональности недоступна |
CRITICAL |
50 | Критическая ошибка, программа не может продолжить |
getLogger() — именованные логгеры
import logging
# Create a named logger (convention: use __name__)
logger = logging.getLogger(__name__)
# Logger hierarchy: 'myapp.auth' is child of 'myapp'
auth_logger = logging.getLogger("myapp.auth")
db_logger = logging.getLogger("myapp.database")
def process_order(order_id: int) -> None:
logger.info("Обработка заказа #%d", order_id)
try:
logger.debug("Шаг 1: валидация заказа #%d", order_id)
logger.info("Заказ #%d успешно обработан", order_id)
except Exception:
logger.exception("Ошибка обработки заказа #%d", order_id)
# IMPORTANT: use lazy formatting (% style), NOT f-strings
# GOOD: logger.info("User %s logged in", username)
# BAD: logger.info(f"User {username} logged in")
# Why: lazy formatting only formats if the message is actually logged
Handlers — обработчики
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Console output (INFO and above)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# File output (DEBUG and above)
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
# Rotating file (max 5MB, keep 3 backups)
rotating_handler = RotatingFileHandler(
"app_rotating.log",
maxBytes=5 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
# Add handlers to logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# Now messages go to multiple destinations
logger.debug("Отладка — только в файл")
logger.info("Информация — консоль + файл")
logger.warning("Предупреждение — везде")
Formatters — форматирование
import logging
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Apply formatter to handler
console = logging.StreamHandler()
console.setFormatter(formatter)
logger = logging.getLogger("myapp")
logger.addHandler(console)
logger.setLevel(logging.DEBUG)
logger.info("Приложение запущено")
# Output: 2026-03-30 14:30:00 | INFO | myapp | Приложение запущено
# Available format attributes:
# %(asctime)s — Time %(levelname)s — Level
# %(name)s — Logger name %(message)s — Message
# %(filename)s — Source file %(lineno)d — Line number
# %(funcName)s — Function %(process)d — Process ID
Собственный JSON-форматтер
import logging
import json
from datetime import datetime, timezone
class JSONFormatter(logging.Formatter):
"""Format log records as JSON."""
def format(self, record: logging.LogRecord) -> str:
log_data = {
"timestamp": datetime.fromtimestamp(
record.created, tz=timezone.utc,
).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"line": record.lineno,
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
# Usage
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger("myapp")
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.info("Пользователь вошёл")
# {"timestamp": "2026-03-30T11:30:00+00:00", "level": "INFO", ...}
Конфигурация через dictConfig
import logging
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard",
"stream": "ext://sys.stdout",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "standard",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf-8",
},
},
"loggers": {
"myapp": {
"level": "DEBUG",
"handlers": ["console", "file"],
"propagate": False,
},
},
"root": {
"level": "WARNING",
"handlers": ["console"],
},
}
logging.config.dictConfig(LOGGING_CONFIG)
app_logger = logging.getLogger("myapp")
app_logger.info("Приложение запущено")
Контекстная информация
import logging
# LoggerAdapter — reusable extra context
class RequestLogger(logging.LoggerAdapter):
"""Logger that adds request context."""
def process(self, msg, kwargs):
request_id = self.extra.get("request_id", "N/A")
return f"[req:{request_id}] {msg}", kwargs
base_logger = logging.getLogger("myapp.api")
request_logger = RequestLogger(base_logger, {"request_id": "abc-123"})
request_logger.info("Обработка запроса")
# [req:abc-123] Обработка запроса
Практический пример: настройка для приложения
import logging
import logging.config
import os
def setup_logging() -> None:
"""Configure application logging from environment."""
level = os.getenv("LOG_LEVEL", "INFO")
log_dir = os.getenv("LOG_DIR", "logs")
json_format = os.getenv("LOG_JSON", "").lower() == "true"
os.makedirs(log_dir, exist_ok=True)
fmt = (
'{"time":"%(asctime)s","level":"%(levelname)s","msg":"%(message)s"}'
if json_format
else "%(asctime)s [%(levelname)-8s] %(name)s: %(message)s"
)
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {"default": {"format": fmt, "datefmt": "%Y-%m-%d %H:%M:%S"}},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "default", "level": level},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "default",
"filename": f"{log_dir}/app.log",
"maxBytes": 10_485_760,
"backupCount": 5,
"encoding": "utf-8",
},
},
"root": {"level": level, "handlers": ["console", "file"]},
})
# Initialize at startup
setup_logging()
logger = logging.getLogger(__name__)
logger.info("Модуль инициализирован")