From 8bf781b67d9592dfdd28aaed76b9d275f3a1276f Mon Sep 17 00:00:00 2001 From: Factorino73 Date: Thu, 2 Jan 2025 05:54:43 +0400 Subject: [PATCH] feat: add logging --- main.py | 4 ++ src/bot/telegram_userbot.py | 20 +++++++- src/integrations/gigachat_api_client.py | 28 +++++++++-- src/utils/logging.py | 62 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/utils/logging.py diff --git a/main.py b/main.py index 3960f3a..adb1c97 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ from src.integrations.gigachat_api_client import GigaChatClient from src.bot.telegram_userbot import TelegramUserBot +from src.utils.logging import setup_logging from src.core.configuration import config @@ -7,6 +8,9 @@ def main() -> None: """ Entry point for starting the Telegram user bot. """ + # Configure logging + setup_logging() + # Load API credentials and configuration api_id: str = config.API_ID api_hash: str = config.API_HASH diff --git a/src/bot/telegram_userbot.py b/src/bot/telegram_userbot.py index 9ba16b7..fe69355 100644 --- a/src/bot/telegram_userbot.py +++ b/src/bot/telegram_userbot.py @@ -1,3 +1,5 @@ +import logging +from logging import Logger from typing import Optional from pyrogram import filters @@ -27,6 +29,9 @@ class TelegramUserBot: api_hash (str): The API hash for the Telegram application. gigachat_client (GigaChatClient): An instance of GigaChatClient for handling AI responses. """ + # Configure logging + self.logger: Logger = logging.getLogger(__name__) + self.app: Client = Client(session_name, api_id=api_id, api_hash=api_hash) self.gigachat_client: GigaChatClient = gigachat_client self.register_handlers() @@ -35,6 +40,7 @@ class TelegramUserBot: """ Registers the message handlers for the bot. """ + self.logger.debug("Registering handlers.") self.app.on_message(filters.command("ai") & filters.text)(self.handle_ai_command) async def handle_ai_command(self, client: Client, message: Message) -> None: @@ -45,14 +51,18 @@ class TelegramUserBot: client (Client): The Pyrogram client instance. message (Message): The incoming Telegram message. """ + self.logger.info(f"Received /ai command from chat_id={message.chat.id}") + # Extract the command argument command_arg: Optional[str] = " ".join(message.text.split()[1:]) if not command_arg: + self.logger.warning(f"No argument provided for /ai command by chat_id={message.chat.id}") await message.reply_text("Please provide a message after /ai.") return # Send an initial message indicating processing + self.logger.debug(f"Processing request for chat_id={message.chat.id}") processing_message: Message = await message.reply_text(f"{self.gigachat_client.model_name} is processing your request...") try: @@ -61,6 +71,7 @@ class TelegramUserBot: # Get a response from GigaChat response: str = self.gigachat_client.get_response(str(message.chat.id), command_arg) + self.logger.debug(f"Received response for chat_id={message.chat.id}") # Stop typing animation await client.send_chat_action(message.chat.id, ChatAction.CANCEL) @@ -68,6 +79,9 @@ class TelegramUserBot: # Edit the processing message with the generated response await processing_message.edit_text(response) except Exception as e: + # Log the exception details + self.logger.error(f"Error processing /ai command for chat_id={message.chat.id}: {e}", exc_info=True) + # Stop typing animation in case of an error await client.send_chat_action(message.chat.id, ChatAction.CANCEL) @@ -78,5 +92,9 @@ class TelegramUserBot: """ Starts the bot. """ + self.logger.info("Bot is starting.") print("Bot is running.") - self.app.run() + try: + self.app.run() + except Exception as e: + self.logger.critical(f"Failed to start the bot: {e}", exc_info=True) diff --git a/src/integrations/gigachat_api_client.py b/src/integrations/gigachat_api_client.py index bf40a4a..7ebf670 100644 --- a/src/integrations/gigachat_api_client.py +++ b/src/integrations/gigachat_api_client.py @@ -1,3 +1,5 @@ +import logging +from logging import Logger from typing import Dict from langchain_core.runnables.history import RunnableWithMessageHistory @@ -18,8 +20,13 @@ class GigaChatClient: api_token (str): The API token for authenticating with the GigaChat API. model_name (str): The GigaChat model to use. Defaults to "GigaChat". """ + # Configure logging + self.logger: Logger = logging.getLogger(__name__) + self.api_token: str = api_token self.model_name: str = model_name + self.logger.info(f"Initialize GigaChat client Using model: {self.model_name}") + self.llm: GigaChat = self._create_llm(model_name) self.store: Dict[str, InMemoryChatMessageHistory] = {} self.conversation = RunnableWithMessageHistory(self.llm, self.get_session_history) @@ -34,6 +41,7 @@ class GigaChatClient: Returns: GigaChat: Configured GigaChat instance. """ + self.logger.debug(f"Creating GigaChat LLM with model: {model_name}") return GigaChat( credentials=self.api_token, scope="GIGACHAT_API_PERS", @@ -53,7 +61,10 @@ class GigaChatClient: InMemoryChatMessageHistory: The chat history for the session. """ if session_id not in self.store: + self.logger.debug(f"Creating new session history for session_id: {session_id}") self.store[session_id] = InMemoryChatMessageHistory() + else: + self.logger.debug(f"Retrieving existing session history for session_id: {session_id}") return self.store[session_id] def set_model(self, model_name: str) -> None: @@ -63,6 +74,7 @@ class GigaChatClient: Args: model_name (str): The new GigaChat model to use. """ + self.logger.info(f"Switching model to: {model_name}") self.llm = self._create_llm(model_name) self.conversation = RunnableWithMessageHistory(self.llm, self.get_session_history) @@ -77,8 +89,14 @@ class GigaChatClient: Returns: str: The response text. """ - response = self.conversation.invoke( - input=text, - config={"configurable": {"session_id": session_id}}, - ) - return response.content + self.logger.info(f"Generating response for session_id: {session_id}") + try: + response = self.conversation.invoke( + input=text, + config={"configurable": {"session_id": session_id}}, + ) + self.logger.debug(f"Response for session_id {session_id}") + return response.content + except Exception as e: + self.logger.error(f"Error while getting response for session_id: {session_id}. Error: {e}", exc_info=True) + raise diff --git a/src/utils/logging.py b/src/utils/logging.py new file mode 100644 index 0000000..1e2fd29 --- /dev/null +++ b/src/utils/logging.py @@ -0,0 +1,62 @@ +import logging +import logging.config +from datetime import datetime + + +def setup_logging(output_to_console=False) -> None: + """ + Configures the logging system. + + This function sets up logging with optional output to the console and ensures + log files are rotated daily. It creates a detailed logging format and retains + log files for a week. + + Args: + output_to_console (bool): If True, log messages will also be printed to the console. + Defaults to False. + """ + # Define the default handlers to use. Always logs to a file. + handlers: list[str] = ['file'] + if output_to_console: + # Add console logging if requested + handlers.append('console') + + # Generate the log file name with the current date + log_filename: str = f'logs/log-{datetime.now().strftime("%Y-%m-%d")}.log' + + # Configure the logging settings using a dictionary + logging.config.dictConfig({ + 'version': 1, # Logging configuration version + 'disable_existing_loggers': True, # Deny other loggers to remain active + 'formatters': { + 'detailed': { # Define a detailed logging format + 'format': ( + '%(asctime)s | %(levelname)-8s | ' + '%(filename)s.%(funcName)s, line %(lineno)d: ' + '%(message)s' + ), + 'datefmt': '%Y-%m-%d %H:%M:%S' # Timestamp format + }, + }, + 'handlers': { + # Console handler outputs log messages to the console + 'console': { + 'class': 'logging.StreamHandler', # Standard output stream + 'formatter': 'detailed', # Use the detailed formatter + }, + # File handler writes log messages to a file, rotating daily + 'file': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': log_filename, # Log file path + 'when': 'midnight', # Rotate log files at midnight + 'interval': 1, # Rotate daily + 'backupCount': 7, # Keep up to 7 old log files + 'formatter': 'detailed', # Use the detailed formatter + }, + }, + # Define the root logger configuration + 'root': { + 'handlers': handlers, # Handlers to use (console, file, or both) + 'level': 'DEBUG', # Log level (DEBUG logs all levels) + }, + })