feat(logging): добавить систему ротации логов

- Ежедневная ротация в 00:00 с архивацией в tar.gz
  - Разделение по уровням: info.log, warning.log, error.log
  - Отдельный payments.log для платежных операций
  - Отправка архивов в Telegram-канал бекапов
  - Автоочистка архивов старше 7 дней (настраивается)
  - Переключатель LOG_ROTATION_ENABLED (по умолчанию выключен)
This commit is contained in:
gy9vin
2025-12-27 19:02:28 +03:00
parent 3fc9e63653
commit d343a317ee
17 changed files with 704 additions and 44 deletions
+110 -10
View File
@@ -37,6 +37,9 @@ from app.services.contest_rotation_service import contest_rotation_service
from app.services.nalogo_queue_service import nalogo_queue_service
from app.utils.startup_timeline import StartupTimeline
from app.utils.timezone import TimezoneAwareFormatter
from app.utils.log_handlers import LevelFilterHandler, ExcludePaymentFilter
from app.utils.payment_logger import payment_logger, configure_payment_logger
from app.services.log_rotation_service import log_rotation_service
class GracefulExit:
@@ -55,17 +58,85 @@ async def main():
timezone_name=settings.TIMEZONE,
)
file_handler = logging.FileHandler(settings.LOG_FILE, encoding='utf-8')
file_handler.setFormatter(formatter)
log_handlers = []
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
# === Инициализация системы логирования ===
if settings.is_log_rotation_enabled():
# Новая система: разделение по уровням + отдельный лог платежей
await log_rotation_service.initialize()
log_dir = log_rotation_service.current_dir
log_dir.mkdir(parents=True, exist_ok=True)
# 1. Общий лог (bot.log) - все уровни, без платежей
bot_handler = logging.FileHandler(log_dir / "bot.log", encoding='utf-8')
bot_handler.setFormatter(formatter)
bot_handler.addFilter(ExcludePaymentFilter())
log_handlers.append(bot_handler)
# 2. INFO лог - только INFO уровень
info_handler = LevelFilterHandler(
str(log_dir / settings.LOG_INFO_FILE),
min_level=logging.INFO,
max_level=logging.INFO,
)
info_handler.setFormatter(formatter)
info_handler.addFilter(ExcludePaymentFilter())
log_handlers.append(info_handler)
# 3. WARNING лог - WARNING и выше
warning_handler = LevelFilterHandler(
str(log_dir / settings.LOG_WARNING_FILE),
min_level=logging.WARNING,
)
warning_handler.setFormatter(formatter)
warning_handler.addFilter(ExcludePaymentFilter())
log_handlers.append(warning_handler)
# 4. ERROR лог - только ERROR и CRITICAL
error_handler = LevelFilterHandler(
str(log_dir / settings.LOG_ERROR_FILE),
min_level=logging.ERROR,
)
error_handler.setFormatter(formatter)
error_handler.addFilter(ExcludePaymentFilter())
log_handlers.append(error_handler)
# 5. Payment лог - отдельный файл для платежей
payment_handler = logging.FileHandler(
log_dir / settings.LOG_PAYMENTS_FILE,
encoding='utf-8',
)
configure_payment_logger(payment_handler, formatter)
# 6. Консольный вывод
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
log_handlers.append(stream_handler)
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL),
handlers=log_handlers,
)
# Регистрируем хэндлеры для управления при ротации
log_rotation_service.register_handlers(log_handlers)
else:
# Старое поведение: один файл лога
file_handler = logging.FileHandler(settings.LOG_FILE, encoding='utf-8')
file_handler.setFormatter(formatter)
log_handlers.append(file_handler)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
log_handlers.append(stream_handler)
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL),
handlers=log_handlers,
)
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL),
handlers=[file_handler, stream_handler],
)
# Установим более высокий уровень логирования для "мусорных" логов
logging.getLogger("aiohttp.access").setLevel(logging.ERROR)
logging.getLogger("aiohttp.client").setLevel(logging.WARNING)
@@ -74,7 +145,7 @@ async def main():
logging.getLogger("aiogram").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.ERROR)
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
timeline = StartupTimeline(logger, "Bedolaga Remnawave Bot")
timeline.log_banner(
@@ -246,6 +317,28 @@ async def main():
stage.warning(f"Ошибка запуска ротации игр: {e}")
logger.error(f"❌ Ошибка запуска ротации игр: {e}")
if settings.is_log_rotation_enabled():
async with timeline.stage(
"Ротация логов",
"📋",
success_message="Сервис ротации логов готов",
) as stage:
try:
log_rotation_service.set_bot(bot)
await log_rotation_service.start()
status = log_rotation_service.get_status()
stage.log(f"Время ротации: {status.rotation_time}")
stage.log(f"Хранение архивов: {status.keep_days} дней")
if status.send_to_telegram:
stage.log("Отправка в Telegram: включена")
if status.next_rotation:
from datetime import datetime
next_dt = datetime.fromisoformat(status.next_rotation)
stage.log(f"Следующая ротация: {next_dt.strftime('%d.%m.%Y %H:%M')}")
except Exception as e:
stage.warning(f"Ошибка запуска сервиса ротации логов: {e}")
logger.error(f"❌ Ошибка запуска сервиса ротации логов: {e}")
async with timeline.stage(
"Автосинхронизация RemnaWave",
"🔄",
@@ -673,6 +766,13 @@ async def main():
except Exception as e:
logger.error(f"Ошибка остановки ротации игр: {e}")
if settings.is_log_rotation_enabled():
logger.info("ℹ️ Остановка сервиса ротации логов...")
try:
await log_rotation_service.stop()
except Exception as e:
logger.error(f"Ошибка остановки сервиса ротации логов: {e}")
logger.info("ℹ️ Остановка очереди чеков NaloGO...")
try:
await nalogo_queue_service.stop()