From b12ad8d671cecb97accf0a378e04957bf2af1acd Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Tue, 1 Oct 2024 10:50:54 -0700 Subject: [PATCH] Added logging, saving log file --- codeaide/logic/chat_handler.py | 19 ++++++----- codeaide/ui/chat_window.py | 25 +++++++++++---- codeaide/utils/api_utils.py | 30 +++++++++++------- codeaide/utils/environment_manager.py | 45 +++++++++++++++++++++------ codeaide/utils/file_handler.py | 28 +++++++++++------ codeaide/utils/general_utils.py | 19 ++++++----- codeaide/utils/logging_config.py | 44 ++++++++++++++++++++++++++ 7 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 codeaide/utils/logging_config.py diff --git a/codeaide/logic/chat_handler.py b/codeaide/logic/chat_handler.py index 483f459..a078ce1 100644 --- a/codeaide/logic/chat_handler.py +++ b/codeaide/logic/chat_handler.py @@ -3,6 +3,7 @@ import sys import re import traceback +import logging from codeaide.utils.api_utils import ( parse_response, send_api_request, @@ -21,6 +22,7 @@ from codeaide.utils.file_handler import FileHandler from codeaide.utils.terminal_manager import TerminalManager from codeaide.utils.general_utils import generate_session_id +from codeaide.utils.logging_config import get_logger class ChatHandler: @@ -37,6 +39,7 @@ def __init__(self): self.session_id = generate_session_id() self.cost_tracker = CostTracker() self.file_handler = FileHandler(session_id=self.session_id) + self.logger = get_logger() self.conversation_history = self.file_handler.load_chat_history() self.env_manager = EnvironmentManager() self.terminal_manager = TerminalManager() @@ -50,7 +53,7 @@ def __init__(self): ]["max_tokens"] self.api_key_valid, self.api_key_message = self.check_api_key() - print(f"New session started with ID: {self.session_id}") + self.logger.info(f"New session started with ID: {self.session_id}") def check_api_key(self): """ @@ -63,7 +66,9 @@ def check_api_key(self): self.api_key_set = self.api_client is not None if not self.api_key_set: + self.logger.warning("API key not set") return False, self.get_api_key_instructions(self.current_provider) + self.logger.info("API key is valid") return True, None def get_api_key_instructions(self, provider): @@ -166,7 +171,7 @@ def process_input(self, user_input): Returns: dict: A response dictionary containing the type and content of the response. """ - print(f"Processing input: {user_input}") + self.logger.info(f"Processing input: {user_input}") try: if not self.api_key_set: return { @@ -190,7 +195,7 @@ def process_input(self, user_input): try: return self.process_ai_response(response) except ValueError as e: - print(f"ValueError: {str(e)}\n") + self.logger.error(f"ValueError: {str(e)}\n") if not self.is_last_attempt(attempt): self.add_error_prompt_to_history(str(e)) else: @@ -484,12 +489,12 @@ def is_task_in_progress(self): return bool(self.conversation_history) def set_model(self, provider, model): - print(f"In set_model: provider: {provider}, model: {model}") + self.logger.info(f"In set_model: provider: {provider}, model: {model}") if provider not in AI_PROVIDERS: - print(f"Invalid provider: {provider}") + self.logger.error(f"Invalid provider: {provider}") return False, None if model not in AI_PROVIDERS[provider]["models"]: - print(f"Invalid model {model} for provider {provider}") + self.logger.error(f"Invalid model {model} for provider {provider}") return False, None self.current_provider = provider @@ -503,7 +508,7 @@ def set_model(self, provider, model): if not api_key_valid: return False, message - print(f"Model {model} for provider {provider} set successfully.") + self.logger.info(f"Model {model} for provider {provider} set successfully.") return True, None def clear_conversation_history(self): diff --git a/codeaide/ui/chat_window.py b/codeaide/ui/chat_window.py index 8016b56..1bd65e6 100644 --- a/codeaide/ui/chat_window.py +++ b/codeaide/ui/chat_window.py @@ -2,6 +2,7 @@ import sys import traceback import time +import logging from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QColor from PyQt5.QtWidgets import ( @@ -37,11 +38,13 @@ DEFAULT_MODEL, MODEL_SWITCH_MESSAGE, ) +from codeaide.utils.logging_config import get_logger class ChatWindow(QMainWindow): def __init__(self, chat_handler): super().__init__() + self.logger = get_logger() self.setWindowTitle("🤖 CodeAIde 🤖") self.setGeometry(0, 0, CHAT_WINDOW_WIDTH, CHAT_WINDOW_HEIGHT) self.chat_handler = chat_handler @@ -64,6 +67,8 @@ def __init__(self, chat_handler): self.timer.start(500) self.timer.timeout.connect(lambda: None) + self.logger.info("Chat window initialized") + def setup_ui(self): central_widget = QWidget(self) self.setCentralWidget(central_widget) @@ -134,6 +139,8 @@ def setup_ui(self): main_layout.addLayout(button_layout) + self.logger.info("Chat window UI initialized") + def eventFilter(self, obj, event): if obj == self.input_text and event.type() == event.KeyPress: if ( @@ -151,6 +158,8 @@ def on_submit(self): if not user_input: return + self.logger.info(f"User input: {user_input}") + # Clear the input field immediately self.input_text.clear() @@ -189,6 +198,8 @@ def add_to_chat(self, sender, message): self.chat_display.append(html_message + "
") self.chat_display.ensureCursorVisible() + self.logger.debug(f"Adding message to chat from {sender}: {message}") + def display_thinking(self): self.add_to_chat("AI", "Thinking... 🤔") @@ -263,6 +274,7 @@ def load_example(self): QMessageBox.information(self, "No Selection", "No example was selected.") def on_exit(self): + self.logger.info("User clicked Exit button, awaiting reply") reply = QMessageBox.question( self, "Quit", @@ -270,6 +282,7 @@ def on_exit(self): QMessageBox.Yes | QMessageBox.No, QMessageBox.No, ) + self.logger.info(f"User clicked Exit button, reply: {reply}") if reply == QMessageBox.Yes: self.close() @@ -291,7 +304,7 @@ def update_model_dropdown(self, provider): if models: self.model_dropdown.setCurrentText(list(models)[0]) else: - print(f"No models available for provider {provider}") + self.logger.info(f"No models available for provider {provider}") def update_chat_handler(self): provider = self.provider_dropdown.currentText() @@ -303,17 +316,17 @@ def update_chat_handler(self): current_version = self.chat_handler.get_latest_version() success, message = self.chat_handler.set_model(provider, model) - print(f"In update_chat_handler: current_version: {current_version}") - print(f"In update_chat_handler, success: {success}") - print(f"In update_chat_handler, message: {message}") + self.logger.info(f"In update_chat_handler: current_version: {current_version}") + self.logger.info(f"In update_chat_handler, success: {success}") + self.logger.info(f"In update_chat_handler, message: {message}") if not success: - print(f"In update_chat_handler, not success") + self.logger.info(f"In update_chat_handler, not success") if message: # This indicates that an API key is required self.waiting_for_api_key = True self.add_to_chat("AI", message) else: - print(f"In update_chat_handler, not success, no message") + self.logger.info(f"In update_chat_handler, not success, no message") self.add_to_chat( "System", f"Failed to set model {model} for provider {provider}. Please check your API key.", diff --git a/codeaide/utils/api_utils.py b/codeaide/utils/api_utils.py index c4b7da6..4166acb 100644 --- a/codeaide/utils/api_utils.py +++ b/codeaide/utils/api_utils.py @@ -6,6 +6,7 @@ from decouple import config, AutoConfig import hjson from anthropic import APIError +import logging from codeaide.utils.constants import ( AI_PROVIDERS, @@ -13,6 +14,9 @@ DEFAULT_PROVIDER, SYSTEM_PROMPT, ) +from codeaide.utils.logging_config import get_logger + +logger = get_logger() class MissingAPIKeyException(Exception): @@ -34,11 +38,13 @@ def get_api_client(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL): api_key_name = AI_PROVIDERS[provider]["api_key_name"] api_key = config(api_key_name, default=None) - print(f"Attempting to get API key for {provider} with key name: {api_key_name}") - print(f"API key found: {'Yes' if api_key else 'No'}") + logger.info( + f"Attempting to get API key for {provider} with key name: {api_key_name}" + ) + logger.info(f"API key found: {'Yes' if api_key else 'No'}") if api_key is None or api_key.strip() == "": - print(f"API key for {provider} is missing or empty") + logger.warning(f"API key for {provider} is missing or empty") return None if provider.lower() == "anthropic": @@ -48,7 +54,7 @@ def get_api_client(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL): else: raise ValueError(f"Unsupported provider: {provider}") except Exception as e: - print(f"Error initializing {provider.capitalize()} API client: {str(e)}") + logger.error(f"Error initializing {provider.capitalize()} API client: {str(e)}") return None @@ -81,13 +87,13 @@ def save_api_key(service, api_key): return True except Exception as e: - print(f"Error saving API key: {str(e)}") + logger.error(f"Error saving API key: {str(e)}") return False def send_api_request(api_client, conversation_history, max_tokens, model, provider): - print(f"Sending API request with model: {model} and max_tokens: {max_tokens}") - print(f"Conversation history: {conversation_history}\n") + logger.info(f"Sending API request with model: {model} and max_tokens: {max_tokens}") + logger.debug(f"Conversation history: {conversation_history}") try: if provider.lower() == "anthropic": @@ -113,11 +119,11 @@ def send_api_request(api_client, conversation_history, max_tokens, model, provid else: raise NotImplementedError(f"API request for {provider} not implemented") - print(f"Received response from {provider}") - print(f"Response object: {response}") + logger.info(f"Received response from {provider}") + logger.debug(f"Response object: {response}") return response except Exception as e: - print(f"Error in API request to {provider}: {str(e)}") + logger.error(f"Error in API request to {provider}: {str(e)}") return None @@ -125,7 +131,7 @@ def parse_response(response, provider): if not response: raise ValueError("Empty or invalid response received") - print(f"Received response: {response}\n") + logger.debug(f"Received response: {response}") if provider.lower() == "anthropic": if not response.content: @@ -182,4 +188,4 @@ def check_api_connection(): if __name__ == "__main__": success, message = check_api_connection() - print(f"Connection {'successful' if success else 'failed'}: {message}") + logger.info(f"Connection {'successful' if success else 'failed'}: {message}") diff --git a/codeaide/utils/environment_manager.py b/codeaide/utils/environment_manager.py index e8ebf29..420b07f 100644 --- a/codeaide/utils/environment_manager.py +++ b/codeaide/utils/environment_manager.py @@ -2,6 +2,9 @@ import subprocess import sys import venv +from codeaide.utils.logging_config import get_logger + +logger = get_logger() class EnvironmentManager: @@ -16,7 +19,10 @@ def __init__(self, env_name="codeaide_env"): def _setup_environment(self): if not os.path.exists(self.env_path): + logger.info(f"Creating new virtual environment at {self.env_path}") venv.create(self.env_path, with_pip=True) + else: + logger.info(f"Using existing virtual environment at {self.env_path}") def _get_installed_packages(self): pip_path = ( @@ -24,12 +30,20 @@ def _get_installed_packages(self): if os.name != "nt" else os.path.join(self.env_path, "Scripts", "pip.exe") ) - result = subprocess.run( - f"{pip_path} freeze", shell=True, check=True, capture_output=True, text=True - ) - self.installed_packages = { - pkg.split("==")[0].lower() for pkg in result.stdout.split("\n") if pkg - } + try: + result = subprocess.run( + f"{pip_path} freeze", + shell=True, + check=True, + capture_output=True, + text=True, + ) + self.installed_packages = { + pkg.split("==")[0].lower() for pkg in result.stdout.split("\n") if pkg + } + logger.info(f"Found {len(self.installed_packages)} installed packages") + except subprocess.CalledProcessError as e: + logger.error(f"Error getting installed packages: {e}") def install_requirements(self, requirements_file): pip_path = ( @@ -44,19 +58,32 @@ def install_requirements(self, requirements_file): if packages_to_install: packages_str = " ".join(packages_to_install) + logger.info(f"Installing new packages: {packages_str}") try: subprocess.run( f"{pip_path} install {packages_str}", shell=True, check=True ) self.installed_packages.update(packages_to_install) + logger.info( + f"Successfully installed {len(packages_to_install)} new packages" + ) return list(packages_to_install) except subprocess.CalledProcessError as e: - print(f"Error installing requirements: {e}") + logger.error(f"Error installing requirements: {e}") return [] + else: + logger.info("No new packages to install") return [] def get_activation_command(self): if os.name == "nt": # Windows - return f"call {os.path.join(self.env_path, 'Scripts', 'activate.bat')}" + activation_path = os.path.join(self.env_path, "Scripts", "activate.bat") else: # Unix-like - return f"source {os.path.join(self.env_path, 'bin', 'activate')}" + activation_path = os.path.join(self.env_path, "bin", "activate") + + logger.info(f"Generated activation command for {os.name} system") + return ( + f"call {activation_path}" + if os.name == "nt" + else f"source {activation_path}" + ) diff --git a/codeaide/utils/file_handler.py b/codeaide/utils/file_handler.py index fb2dce7..0ac1029 100644 --- a/codeaide/utils/file_handler.py +++ b/codeaide/utils/file_handler.py @@ -1,6 +1,8 @@ import os import shutil import json +import logging +from codeaide.utils.logging_config import setup_logger, get_logger class FileHandler: @@ -24,6 +26,10 @@ def __init__(self, base_dir=None, session_id=None): ) self._ensure_output_dirs_exist() + if self.session_dir: + setup_logger(self.session_dir) + self.logger = get_logger() + def _ensure_output_dirs_exist(self): os.makedirs(self.output_dir, exist_ok=True) if self.session_dir: @@ -39,23 +45,23 @@ def save_code(self, code, version, version_description, requirements=[]): ) abs_code_path = os.path.abspath(code_path) abs_req_path = os.path.abspath(requirements_path) - print(f"Attempting to save code to: {abs_code_path}") + self.logger.info(f"Attempting to save code to: {abs_code_path}") try: with open(abs_code_path, "w") as file: file.write(code) - print(f"Code saved successfully to: {abs_code_path}") - print(f"Saving associated requirements to: {abs_req_path}") + self.logger.info(f"Code saved successfully to: {abs_code_path}") + self.logger.info(f"Saving associated requirements to: {abs_req_path}") self.save_requirements(requirements, version) except Exception as e: - print(f"Error saving file: {str(e)}") - print(f"Adding version {version} to versions_dict") + self.logger.error(f"Error saving file: {str(e)}") + self.logger.info(f"Adding version {version} to versions_dict") self.versions_dict[version] = { "version_description": version_description, "requirements": requirements, "code_path": abs_code_path, "requirements_path": abs_req_path, } - print(f"Current versions dict: {self.versions_dict}") + self.logger.debug(f"Current versions dict: {self.versions_dict}") return code_path def save_requirements(self, requirements, version): @@ -94,9 +100,11 @@ def save_chat_history(self, conversation_history): try: with open(self.chat_history_file, "w", encoding="utf-8") as f: json.dump(conversation_history, f, ensure_ascii=False, indent=2) - print(f"Chat history saved successfully to: {self.chat_history_file}") + self.logger.info( + f"Chat history saved successfully to: {self.chat_history_file}" + ) except Exception as e: - print(f"Error saving chat history: {str(e)}") + self.logger.error(f"Error saving chat history: {str(e)}") def load_chat_history(self): if not self.session_dir or not os.path.exists(self.chat_history_file): @@ -106,7 +114,7 @@ def load_chat_history(self): with open(self.chat_history_file, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: - print(f"Error loading chat history: {str(e)}") + self.logger.error(f"Error loading chat history: {str(e)}") return [] def set_session_id(self, session_id): @@ -114,3 +122,5 @@ def set_session_id(self, session_id): self.session_dir = os.path.join(self.output_dir, self.session_id) self.chat_history_file = os.path.join(self.session_dir, "chat_history.json") self._ensure_output_dirs_exist() + setup_logger(self.session_dir) + self.logger = get_logger() diff --git a/codeaide/utils/general_utils.py b/codeaide/utils/general_utils.py index 79039bd..e8fb107 100644 --- a/codeaide/utils/general_utils.py +++ b/codeaide/utils/general_utils.py @@ -1,8 +1,10 @@ import os - import yaml from PyQt5.QtGui import QFont, QColor from datetime import datetime +from codeaide.utils.logging_config import get_logger + +logger = get_logger() # Store the path of the general_utils.py file UTILS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -31,22 +33,22 @@ def get_examples_file_path(): def load_examples(): """Load and return all examples from the YAML file.""" examples_file = get_examples_file_path() - print(f"Attempting to load examples from: {examples_file}") # Debug print + logger.info(f"Attempting to load examples from: {examples_file}") if not os.path.exists(examples_file): - print(f"Examples file not found: {examples_file}") + logger.error(f"Examples file not found: {examples_file}") return [] try: with open(examples_file, "r", encoding="utf-8") as file: data = yaml.safe_load(file) examples = data.get("examples", []) - print(f"Loaded {len(examples)} examples") # Debug print + logger.info(f"Loaded {len(examples)} examples") return examples except yaml.YAMLError as e: - print(f"YAML Error: {str(e)}") + logger.error(f"YAML Error: {str(e)}") return [] except Exception as e: - print(f"Unexpected error: {str(e)}") + logger.error(f"Unexpected error: {str(e)}") return [] @@ -57,6 +59,7 @@ def set_font(font_tuple): elif len(font_tuple) == 3: font_family, font_size, font_style = font_tuple else: + logger.error("Invalid font tuple length") raise ValueError("Font tuple must be of length 2 or 3") qfont = QFont(font_family, font_size) @@ -114,4 +117,6 @@ def generate_session_id(): Generate a unique session ID based on the current timestamp. Format: YYYYMMDD_HHMMSS """ - return datetime.now().strftime("%Y%m%d_%H%M%S") + session_id = datetime.now().strftime("%Y%m%d_%H%M%S") + logger.info(f"Generated new session ID: {session_id}") + return session_id diff --git a/codeaide/utils/logging_config.py b/codeaide/utils/logging_config.py new file mode 100644 index 0000000..4b77956 --- /dev/null +++ b/codeaide/utils/logging_config.py @@ -0,0 +1,44 @@ +import logging +from logging.handlers import RotatingFileHandler +import os + + +def setup_logger(session_dir, level=logging.INFO): + log_dir = os.path.join(session_dir) + os.makedirs(log_dir, exist_ok=True) + + # Set up the log file path + log_file = os.path.join(log_dir, "codeaide.log") + + # Create a formatter + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + + # Set up the file handler with rotation + file_handler = RotatingFileHandler( + log_file, maxBytes=10 * 1024 * 1024, backupCount=5 + ) + file_handler.setFormatter(formatter) + + # Set up the console handler + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + + # Get the root logger + logger = logging.getLogger() + logger.setLevel(level) + + # Remove any existing handlers + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Add the new handlers + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger + + +def get_logger(): + return logging.getLogger()