diff --git a/.gitignore b/.gitignore index 9b53de8b0..4938272a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# application files and logs +/generated_cv +/data_folder/secrets.yaml +/log/* + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -159,6 +164,3 @@ venv.bak/ # Mono Auto Generated Files mono_crash.* - -/generated_cv -data_folder/secrets.yaml diff --git a/app_config.py b/app_config.py index b0a4389f3..ce5daeea5 100644 --- a/app_config.py +++ b/app_config.py @@ -9,5 +9,7 @@ - "CRITICAL" """ MINIMUM_LOG_LEVEL = "DEBUG" +LOG_TO_FILE = True +LOG_TO_CONSOLE = True MINIMUM_WAIT_TIME = 60 diff --git a/main.py b/main.py index e71334aa2..1ee97dcd5 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ from src.utils import chrome_browser_options from src.job_application_profile import JobApplicationProfile -from loguru import logger +from src.logging import logger # Suppress stderr only during specific operations original_stderr = sys.stderr diff --git a/src/ai_hawk/authenticator.py b/src/ai_hawk/authenticator.py index d47fbf622..9a88f9d12 100644 --- a/src/ai_hawk/authenticator.py +++ b/src/ai_hawk/authenticator.py @@ -7,7 +7,7 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -from loguru import logger +from src.logging import logger def get_authenticator(driver, platform): if platform == 'linkedin': diff --git a/src/ai_hawk/bot_facade.py b/src/ai_hawk/bot_facade.py index 091a46d6b..1952a5108 100644 --- a/src/ai_hawk/bot_facade.py +++ b/src/ai_hawk/bot_facade.py @@ -1,4 +1,4 @@ -from loguru import logger +from src.logging import logger class AIHawkBotState: diff --git a/src/ai_hawk/job_manager.py b/src/ai_hawk/job_manager.py index 04b3c6ac5..eea30a039 100644 --- a/src/ai_hawk/job_manager.py +++ b/src/ai_hawk/job_manager.py @@ -14,8 +14,8 @@ import src.utils as utils from app_config import MINIMUM_WAIT_TIME from src.job import Job +from src.logging import logger -from loguru import logger import urllib.parse diff --git a/src/ai_hawk/linkedIn_easy_applier.py b/src/ai_hawk/linkedIn_easy_applier.py index 46bcefa49..13afac364 100644 --- a/src/ai_hawk/linkedIn_easy_applier.py +++ b/src/ai_hawk/linkedIn_easy_applier.py @@ -20,7 +20,7 @@ from selenium.webdriver.support.ui import Select, WebDriverWait import src.utils as utils -from loguru import logger +from src.logging import logger class AIHawkEasyApplier: @@ -176,7 +176,6 @@ def _find_easy_apply_button(self, job: Any) -> WebElement: ] while attempt < 2: - self.check_for_premium_redirect(job) self._scroll_page() @@ -185,12 +184,10 @@ def _find_easy_apply_button(self, job: Any) -> WebElement: logger.debug(f"Attempting search using {method['description']}") if method.get('find_elements'): - buttons = self.driver.find_elements(By.XPATH, method['xpath']) if buttons: for index, button in enumerate(buttons): try: - WebDriverWait(self.driver, 10).until(EC.visibility_of(button)) WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable(button)) logger.debug(f"Found 'Easy Apply' button {index + 1}, attempting to click") @@ -200,7 +197,6 @@ def _find_easy_apply_button(self, job: Any) -> WebElement: else: raise TimeoutException("No 'Easy Apply' buttons found") else: - button = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.XPATH, method['xpath'])) ) diff --git a/src/ai_hawk/llm/llm_manager.py b/src/ai_hawk/llm/llm_manager.py index 2e06edc76..fdd82ee50 100644 --- a/src/ai_hawk/llm/llm_manager.py +++ b/src/ai_hawk/llm/llm_manager.py @@ -19,7 +19,7 @@ from langchain_core.prompts import ChatPromptTemplate import src.strings as strings -from loguru import logger +from src.logging import logger load_dotenv() diff --git a/src/job.py b/src/job.py index ff72d4702..3bcd9937a 100644 --- a/src/job.py +++ b/src/job.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from loguru import logger +from src.logging import logger @dataclass diff --git a/src/job_application_profile.py b/src/job_application_profile.py index 5b9ea8e94..8a74bdb7e 100644 --- a/src/job_application_profile.py +++ b/src/job_application_profile.py @@ -2,7 +2,7 @@ import yaml -from loguru import logger +from src.logging import logger @dataclass diff --git a/src/logging.py b/src/logging.py new file mode 100644 index 000000000..e03e3c869 --- /dev/null +++ b/src/logging.py @@ -0,0 +1,46 @@ +import os +import random +import sys +import time +from selenium import webdriver +from loguru import logger +from app_config import MINIMUM_LOG_LEVEL, LOG_TO_FILE, LOG_TO_CONSOLE + +from selenium.webdriver.remote.remote_connection import LOGGER as selenium_logger +selenium_logger.setLevel(MINIMUM_LOG_LEVEL) + +log_file = "log/app.log" + +# Ensure the log directory exists +os.makedirs(os.path.dirname(log_file), exist_ok=True) + +# Remove default logger +logger.remove() + +# Configure Loguru logger +config = { + "handlers": [] +} + +# Add file logger if LOG_TO_FILE is True +if LOG_TO_FILE: + config["handlers"].append({ + "sink": log_file, + "level": MINIMUM_LOG_LEVEL, + "rotation": "10 MB", + "retention": "1 week", + "compression": "zip", + "format": "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}" + }) + +# Add console logger if LOG_TO_CONSOLE is True +if LOG_TO_CONSOLE: + config["handlers"].append({ + "sink": sys.stderr, + "level": MINIMUM_LOG_LEVEL, + "format": "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}" + }) + +# Configure Loguru with the new settings +logger.configure(**config) + diff --git a/src/utils.py b/src/utils.py index a14089d1c..8d5ca6cb0 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,24 +1,8 @@ -import logging import os -import random -import sys import time - +import random from selenium import webdriver -from loguru import logger - -from app_config import MINIMUM_LOG_LEVEL - -log_file = "app_log.log" - - -if MINIMUM_LOG_LEVEL in ["DEBUG", "TRACE", "INFO", "WARNING", "ERROR", "CRITICAL"]: - logger.remove() - logger.add(sys.stderr, level=MINIMUM_LOG_LEVEL) -else: - logger.warning(f"Invalid log level: {MINIMUM_LOG_LEVEL}. Defaulting to DEBUG.") - logger.remove() - logger.add(sys.stderr, level="DEBUG") +from src.logging import logger chromeProfilePath = os.path.join(os.getcwd(), "chrome_profile", "linkedin_profile") @@ -33,7 +17,6 @@ def ensure_chrome_profile(): logger.debug(f"Created Chrome profile directory: {chromeProfilePath}") return chromeProfilePath - def is_scrollable(element): scroll_height = element.get_attribute("scrollHeight") client_height = element.get_attribute("clientHeight") @@ -41,7 +24,6 @@ def is_scrollable(element): logger.debug(f"Element scrollable check: scrollHeight={scroll_height}, clientHeight={client_height}, scrollable={scrollable}") return scrollable - def scroll_slow(driver, scrollable_element, start=0, end=3600, step=300, reverse=False): logger.debug(f"Starting slow scroll: start={start}, end={end}, step={step}, reverse={reverse}") @@ -110,7 +92,6 @@ def scroll_slow(driver, scrollable_element, start=0, end=3600, step=300, reverse except Exception as e: logger.error(f"Exception occurred during scrolling: {e}") - def chrome_browser_options(): logger.debug("Setting Chrome browser options") ensure_chrome_profile() @@ -153,14 +134,12 @@ def chrome_browser_options(): return options - def printred(text): red = "\033[91m" reset = "\033[0m" logger.debug("Printing text in red: %s", text) print(f"{red}{text}{reset}") - def printyellow(text): yellow = "\033[93m" reset = "\033[0m" diff --git a/tests/test_aihawk_job_manager.py b/tests/test_aihawk_job_manager.py index 84b552b15..56722c35d 100644 --- a/tests/test_aihawk_job_manager.py +++ b/tests/test_aihawk_job_manager.py @@ -5,7 +5,7 @@ import pytest from ai_hawk.job_manager import AIHawkJobManager from selenium.common.exceptions import NoSuchElementException -from loguru import logger +from src.logging import logger @pytest.fixture diff --git a/tests/test_utils.py b/tests/test_utils.py index 621be61be..ba8701ba4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,7 +9,7 @@ # Mocking logging to avoid actual file writing @pytest.fixture(autouse=True) def mock_logger(mocker): - mocker.patch("src.utils.logger") + mocker.patch("src.logging.logger") # Test ensure_chrome_profile function def test_ensure_chrome_profile(mocker):