diff --git a/.gitignore b/.gitignore index 2995de2..ca30bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ conversations.pkl *.jpg *.png *.jpeg -.vscode/ \ No newline at end of file +.vscode/ +data/actors +examples.ini \ No newline at end of file diff --git a/docker/chatbot/Dockerfile b/docker/chatbot/Dockerfile index 0509a05..1f49083 100644 --- a/docker/chatbot/Dockerfile +++ b/docker/chatbot/Dockerfile @@ -28,7 +28,7 @@ RUN poetry config virtualenvs.create false \ COPY src/ ./src/ # Create directories for bot persistence -RUN mkdir -p /app/data +RUN mkdir -p /app/data/actors # Create non-root user RUN groupadd -r botuser && useradd -r -g botuser botuser diff --git a/src/core/logging.py b/src/core/logging.py index ffc4b05..99f69b1 100644 --- a/src/core/logging.py +++ b/src/core/logging.py @@ -2,6 +2,7 @@ import logging import sys from logging.handlers import RotatingFileHandler from typing import Sequence +from threading import Lock from core.config import settings @@ -23,11 +24,50 @@ class ExtendedLogger(logging.Logger): else: formatted = ", ".join(items) self.info(f"{message}: {formatted}") + + +class ActorLogger(logging.Logger): + def __init__(self): + self.__loggers: dict[str, logging.Logger] = {} + self.lock = Lock() + + def log(self, actor_id: int, level: int, msg: str): + logger = self._get_or_create_logger(actor_id) + logger.log(level, msg) + + def info(self, actor_id: int, msg: str): + self.log(actor_id, logging.INFO, msg) + + def error(self, actor_id: int, msg: str): + self.log(actor_id, logging.ERROR, msg) + + def warning(self, actor_id: int, msg: str): + self.log(actor_id, logging.WARNING, msg) + + def _get_or_create_logger(self, actor_id: str): + with self.lock: + if actor_id not in self.__loggers: + logger = logging.getLogger(f"actor_{actor_id}") + logger.setLevel(logging.INFO) + handler = RotatingFileHandler( + filename=f"/app/data/actors/actor_{actor_id}.log", + maxBytes=5 * 1024 * 1024, + backupCount=5, + encoding='utf-8' + ) + formatter = logging.Formatter( + fmt="%(asctime)s %(levelname)s [%(name)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + self.__loggers[actor_id] = logger + return self.__loggers[actor_id] def setup_logging(): """ Инициализирует логирование для всего приложения. - Конфигурирует консольный и файловый логгеры с ротацией. + Конфигурирует консольный логгер. """ # Используем наш кастомный класс логгера @@ -52,18 +92,6 @@ def setup_logging(): # Собираем хэндлеры handlers = [console_handler] - # Файловый хэндлер с ротацией (если указан путь в настройках) - if hasattr(settings, 'log_file_path') and settings.log_file_path: - file_handler = RotatingFileHandler( - filename=settings.log_file_path, - maxBytes=10 * 1024 * 1024, # 10 MB - backupCount=5, - encoding="utf-8" - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - handlers.append(file_handler) - # Конфигурируем корневой логгер root_logger = logging.getLogger() root_logger.setLevel(log_level) diff --git a/src/docbot/handlers/patients/consultation_handler.py b/src/docbot/handlers/patients/consultation_handler.py index ea46f57..0e3ec8b 100644 --- a/src/docbot/handlers/patients/consultation_handler.py +++ b/src/docbot/handlers/patients/consultation_handler.py @@ -20,7 +20,7 @@ from docbot.services.doctors_service import ( get_doctors_payment_link, get_doctor_by_code ) from docbot.services.session_service import create_session -from core.logging import logger +from core.logging import logger, ActorLogger from core.exceptions import DatabaseError from core.enums.dialog_helpers import ConfirmationMessage from core.utils import is_phone_correct, make_a_payment_link @@ -34,6 +34,7 @@ ENTER_DOCTOR_NUMBER = 5 ENTER_CONSULTATION_DATE = 6 PAY_CONSULTATION = 7 ERROR_PHONE_NUMBER = 8 +actor_logger = ActorLogger() STOPPING = 99 @@ -78,14 +79,14 @@ async def accept_personal_data_agreement(update: Update, context: ContextTypes.D try: registered = await get_patient_by_telegram_id(user_id) except DatabaseError as e: - logger.error(f"Database error while fetching patient by Telegram ID {user_id}: {e}") + actor_logger.error(user_id, f"Database error while fetching patient by Telegram ID {user_id}: {e}") await update.message.reply_text( "❌ Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже." ) return ConversationHandler.END - logger.info(f"User {user_id} initiated consultation process.") - logger.info(f"User exists? {registered}") + actor_logger.info(user_id, f"User {user_id} initiated consultation process.") + actor_logger.info(user_id, f"User exists? {registered}") if registered: await update.message.reply_text( @@ -113,7 +114,7 @@ async def receive_patient_aceptance(update: Update, context: ContextTypes.DEFAUL try: await create_patient(telegram_id=user_id, terms_acceptance=True) # Создаем пациента в БД except DatabaseError as e: - logger.error(f"Failed to create patient for user {user_id}: {e}") + actor_logger.error(user_id, f"Failed to create patient for user {user_id}: {e}") await update.callback_query.edit_message_text( text="❌ Произошла ошибка при создании вашей записи. Пожалуйста, попробуйте позже." ) @@ -167,20 +168,20 @@ async def enter_patient_phone(update: Update, context: ContextTypes.DEFAULT_TYPE if patient: has_phone = await is_user_has_phone(user_id) - logger.info(f"User {user_id} has phone: {has_phone}") + actor_logger.info(user_id, f"User {user_id} has phone: {has_phone}") if update.callback_query.data == 'back': - logger.info(f"User {user_id} requested correction of their phone number") + actor_logger.info(user_id, f"User {user_id} requested correction of their phone number") if not has_phone: - logger.info(f"Ask user {user_id} enter a phone number.") + actor_logger.info(user_id, f"Ask user {user_id} enter a phone number.") await update.callback_query.message.reply_text( text="Пожалуйста, введите ваш номер мобильного телефона для записи на консультацию:\n" "Без знака +, вида 79991112233" ) return ENTER_PATIENT_PHONE else: - logger.info(f"Ask user {user_id} enter a doctor's id.") + actor_logger.info(user_id, f"Ask user {user_id} enter a doctor's id.") await update.callback_query.edit_message_text( text="Введите серийный номер врача, к которому вы хотите записаться на консультацию:", parse_mode="Markdown" @@ -196,7 +197,7 @@ async def receive_patient_phone(update: Update, context: ContextTypes.DEFAULT_TY # Например, вызов сервиса для обновления информации о пациенте # await update_patient_phone(telegram_id=user_id, phone=phone) - logger.info((f"Checking user's phone {is_phone_correct(phone=phone)}")) + actor_logger.info(user_id, f"Checking user's phone {is_phone_correct(phone=phone)}") if not is_phone_correct(phone=phone): keyboard = [ [ @@ -213,7 +214,7 @@ async def receive_patient_phone(update: Update, context: ContextTypes.DEFAULT_TY ) return ERROR_PHONE_NUMBER - logger.info((f"receive_patient_phone User {user_id} provided phone: {phone}")) + actor_logger.info(user_id, f"receive_patient_phone User {user_id} provided phone: {phone}") context.user_data['phone'] = phone keyboard = [ @@ -244,7 +245,7 @@ async def receive_doctor_number(update: Update, context: ContextTypes.DEFAULT_TY if not await is_user_has_phone(user_id): patient_phone = context.user_data['phone'] await update_patient_phone(telegram_id=user_id, phone=patient_phone) - logger.info((f"Saved patient's phone number {patient_phone}")) + actor_logger.info(user_id, f"Saved patient's phone number {patient_phone}") await update.message.reply_text( text=f"Вы ввели серийный номер врача: {doctor_number}\n" @@ -252,7 +253,7 @@ async def receive_doctor_number(update: Update, context: ContextTypes.DEFAULT_TY parse_mode="Markdown" ) else: - logger.info(f"User {user_id} requested correction of their consultation date") + actor_logger.info(user_id, f"User {user_id} requested correction of their consultation date") await update.effective_message.reply_text( text="Введите дату и время консультации в формате ДД.ММ.ГГ ЧЧ:ММ, например, 01.01.23 12:00.", parse_mode="Markdown" @@ -299,7 +300,7 @@ async def pay_consultation(update: Update, context: ContextTypes.DEFAULT_TYPE) - patient = await get_patient_by_telegram_id(user_id) doctor = await get_doctor_by_code(context.user_data['doctor_number']) except DatabaseError as e: - logger.error(f"Database error during payment process for user {user_id}: {e}") + actor_logger.error(user_id, f"Database error during payment process for user {user_id}: {e}") await update.callback_query.message.reply_text( "❌ Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже." ) @@ -316,7 +317,7 @@ async def pay_consultation(update: Update, context: ContextTypes.DEFAULT_TYPE) - doctor=doctor ) except DatabaseError as e: - logger.error(f"Failed to create session for user {user_id}: {e}") + actor_logger.error(user_id, f"Failed to create session for user {user_id}: {e}") await update.callback_query.message.reply_text( "❌ Произошла ошибка при создании записи на консультацию. Пожалуйста, попробуйте позже." )