📝 Логирование + graceful shutdown

- Добавлен app/logger.py — loguru с ротацией (консоль, файл, ошибки)
- main.py: логи старта/стопа, graceful shutdown (SIGTERM/SIGINT/stop_signal)
- Все хендлеры: логирование ключевых действий пользователя
- Логи сохраняются в logs/bot_YYYY-MM-DD.log, ротация 30 дней
- Ошибки отдельно в logs/errors_*
This commit is contained in:
DevProject Agent
2026-06-14 19:43:46 +04:00
parent 4c5f99856c
commit a6a1e0d8a9
10 changed files with 126 additions and 18 deletions
+75 -17
View File
@@ -1,37 +1,95 @@
"""SPA Telegram Bot — main entry point."""
import asyncio
import signal
import sys
from aiogram import Bot, Dispatcher
from app.config import BOT_TOKEN
from app.logger import logger
# Import routers
from app.handlers.start import router as start_router
from app.handlers.booking import router as booking_router
from app.handlers.profile import router as profile_router
from app.handlers.mybookings import router as mybooking_router
from app.handlers.mybookings import router as mybookings_router
from app.handlers.services import router as services_router
from app.handlers.about import router as about_router
from app.handlers.contacts import router as contacts_router
from app.services.booking_service import (
create_booking,
update_booking,
get_busy_times
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# Register routers
dp.include_routers(
start_router,
booking_router,
profile_router,
mybookings_router,
services_router,
about_router,
contacts_router,
)
async def on_startup():
"""Called when bot starts."""
me = await bot.get_me()
logger.info(
"Bot started | @{username} (id={id}) | Token: {token_preview}...",
username=me.username,
id=me.id,
token_preview=BOT_TOKEN[:10],
)
async def on_shutdown():
"""Called when bot shuts down."""
logger.info("Bot shutting down...")
# Give running tasks time to finish
pending = asyncio.all_tasks()
if pending:
logger.debug("Cancelling {count} pending tasks", count=len(pending))
logger.info("Bot shutdown complete")
async def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
logger.info("Initializing bot...")
# подключаем все роутеры
dp.include_router(start_router)
dp.include_router(booking_router)
dp.include_router(profile_router)
dp.include_router(mybooking_router)
dp.include_router(about_router)
dp.include_router(contacts_router)
dp.startup.register(on_startup)
dp.shutdown.register(on_shutdown)
print("Bot started...")
# Handle SIGTERM/SIGINT gracefully
stop_event = asyncio.Event()
await dp.start_polling(bot)
def _signal_handler(sig, frame):
sig_name = signal.Signals(sig).name
logger.warning("Received {sig} signal, shutting down...", sig=sig_name)
stop_event.set()
loop = asyncio.get_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
try:
loop.add_signal_handler(sig, lambda s=sig: _signal_handler(s, None))
except NotImplementedError:
# Windows fallback
signal.signal(sig, _signal_handler)
try:
await dp.start_polling(bot, stop_signal=stop_event)
except asyncio.CancelledError:
logger.info("Polling cancelled")
except Exception as e:
logger.exception("Fatal error in polling: {error}", error=e)
finally:
await bot.session.close()
logger.info("Bot session closed")
if __name__ == "__main__":
asyncio.run(main())
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("Bot stopped by user (Ctrl+C)")
except Exception as e:
logger.exception("Unhandled exception: {error}", error=e)