- Добавлен app/logger.py — loguru с ротацией (консоль, файл, ошибки) - main.py: логи старта/стопа, graceful shutdown (SIGTERM/SIGINT/stop_signal) - Все хендлеры: логирование ключевых действий пользователя - Логи сохраняются в logs/bot_YYYY-MM-DD.log, ротация 30 дней - Ошибки отдельно в logs/errors_*
96 lines
2.6 KiB
Python
96 lines
2.6 KiB
Python
"""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 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
|
|
|
|
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():
|
|
logger.info("Initializing bot...")
|
|
|
|
dp.startup.register(on_startup)
|
|
dp.shutdown.register(on_shutdown)
|
|
|
|
# Handle SIGTERM/SIGINT gracefully
|
|
stop_event = asyncio.Event()
|
|
|
|
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__":
|
|
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)
|