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):