📝 Логирование + 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:
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from app.logger import logger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
DB_DIR = Path(__file__).resolve().parent.parent.parent / "data"
|
DB_DIR = Path(__file__).resolve().parent.parent.parent / "data"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
from app.logger import logger
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from app.keyboards.menu import menu, cancel_menu
|
|||||||
from app.keyboards.calendar import get_dates_keyboard
|
from app.keyboards.calendar import get_dates_keyboard
|
||||||
from app.keyboards.time_slots import get_time_keyboard
|
from app.keyboards.time_slots import get_time_keyboard
|
||||||
from app.keyboards.confirm_booking import get_confirm_booking_keyboard
|
from app.keyboards.confirm_booking import get_confirm_booking_keyboard
|
||||||
|
from app.logger import logger
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ async def choose_service(
|
|||||||
services = SERVICES
|
services = SERVICES
|
||||||
|
|
||||||
service_name = services[callback.data]
|
service_name = services[callback.data]
|
||||||
|
logger.debug("User {uid} selected service: {srv}", uid=callback.from_user.id, srv=service_name)
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
service=service_name
|
service=service_name
|
||||||
@@ -84,6 +86,7 @@ async def choose_master(
|
|||||||
masters = MASTERS
|
masters = MASTERS
|
||||||
|
|
||||||
master_name = masters[callback.data]
|
master_name = masters[callback.data]
|
||||||
|
logger.debug("User {uid} selected master: {m}", uid=callback.from_user.id, m=master_name)
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
master=master_name
|
master=master_name
|
||||||
@@ -118,6 +121,7 @@ async def choose_date(
|
|||||||
await state.update_data(
|
await state.update_data(
|
||||||
date=selected_date
|
date=selected_date
|
||||||
)
|
)
|
||||||
|
logger.debug("User {uid} selected date: {d}", uid=callback.from_user.id, d=selected_date)
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
|
|
||||||
@@ -156,6 +160,7 @@ async def choose_time(
|
|||||||
await state.update_data(
|
await state.update_data(
|
||||||
time=selected_time
|
time=selected_time
|
||||||
)
|
)
|
||||||
|
logger.debug("User {uid} selected time: {t}", uid=callback.from_user.id, t=selected_time)
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
from app.logger import logger
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from app.services.booking_service import (
|
|||||||
cancel_booking,
|
cancel_booking,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from app.logger import logger
|
||||||
from app.keyboards.my_booking import (
|
from app.keyboards.my_booking import (
|
||||||
get_booking_actions
|
get_booking_actions
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from aiogram.types import Message
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from app.db.memory import get_user
|
from app.db.memory import get_user
|
||||||
|
from app.logger import logger
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from aiogram import Router
|
from aiogram import Router
|
||||||
router = Router()
|
router = Router()
|
||||||
|
from app.logger import logger
|
||||||
@@ -4,6 +4,7 @@ from aiogram.types import Message
|
|||||||
|
|
||||||
from app.keyboards.menu import menu
|
from app.keyboards.menu import menu
|
||||||
from app.db.memory import get_user, set_user
|
from app.db.memory import get_user, set_user
|
||||||
|
from app.logger import logger
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"""Logging configuration for SPA Telegram Bot."""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
LOG_DIR = Path(__file__).resolve().parent.parent / "logs"
|
||||||
|
LOG_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Remove default handler
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
# Console handler (INFO and above)
|
||||||
|
logger.add(
|
||||||
|
sys.stdout,
|
||||||
|
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
||||||
|
level="INFO",
|
||||||
|
colorize=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# File handler with rotation (DEBUG and above, keep 30 days)
|
||||||
|
logger.add(
|
||||||
|
LOG_DIR / "bot_{time:YYYY-MM-DD}.log",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||||
|
level="DEBUG",
|
||||||
|
rotation="1 day",
|
||||||
|
retention="30 days",
|
||||||
|
compression="gz",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Error-only file
|
||||||
|
logger.add(
|
||||||
|
LOG_DIR / "errors_{time:YYYY-MM-DD}.log",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||||
|
level="ERROR",
|
||||||
|
rotation="1 day",
|
||||||
|
retention="90 days",
|
||||||
|
compression="gz",
|
||||||
|
)
|
||||||
+75
-17
@@ -1,37 +1,95 @@
|
|||||||
|
"""SPA Telegram Bot — main entry point."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
|
|
||||||
from app.config import BOT_TOKEN
|
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.start import router as start_router
|
||||||
from app.handlers.booking import router as booking_router
|
from app.handlers.booking import router as booking_router
|
||||||
from app.handlers.profile import router as profile_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.about import router as about_router
|
||||||
from app.handlers.contacts import router as contacts_router
|
from app.handlers.contacts import router as contacts_router
|
||||||
from app.services.booking_service import (
|
|
||||||
create_booking,
|
bot = Bot(token=BOT_TOKEN)
|
||||||
update_booking,
|
dp = Dispatcher()
|
||||||
get_busy_times
|
|
||||||
|
# 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():
|
async def main():
|
||||||
bot = Bot(token=BOT_TOKEN)
|
logger.info("Initializing bot...")
|
||||||
dp = Dispatcher()
|
|
||||||
|
|
||||||
# подключаем все роутеры
|
dp.startup.register(on_startup)
|
||||||
dp.include_router(start_router)
|
dp.shutdown.register(on_shutdown)
|
||||||
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)
|
|
||||||
|
|
||||||
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__":
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user