Forráskód Böngészése

集成Selenium标准原子方法 && 集成高精炼Logger日志记录器 && 集成相关debug工具

BrainZhang 1 hónapja
szülő
commit
c441a2ec35

BIN
Base/WebUI/Driver/msedgedriver.exe


+ 87 - 0
Base/WebUI/Tools/Driver.py

@@ -0,0 +1,87 @@
+from selenium import webdriver
+from selenium.webdriver.chrome.service import Service as ChromeService
+from selenium.webdriver.firefox.service import Service as FirefoxService
+from selenium.webdriver.edge.service import Service as EdgeService
+from selenium.webdriver.edge.options import Options as EdgeOptions
+import os
+from Config.UIConfig import Config
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+class DriverSelector:
+    def __init__(self, driver_path=ROOT_DIR+Config.EDGE_DRIVER):
+        """
+        初始化驱动器选择器,默认选择Edge
+        :param driver_path: 默认的驱动地址
+        """
+        self.driver_path = driver_path
+        self.driver = None
+        # 检查驱动文件是否存在
+        if not os.path.exists(self.driver_path):
+            raise FileNotFoundError(f"驱动程序未找到: {self.driver_path}")
+
+    def create_chrome_driver(self, options=None):
+        if options is None:
+            options = webdriver.ChromeOptions()
+            options.add_argument('--start-maximized')
+            options.add_argument('--disable-notifications')
+
+        service = ChromeService(executable_path=self.driver_path)
+        self.driver = webdriver.Chrome(service=service, options=options)
+        return self.driver
+
+    def create_firefox_driver(self, options=None):
+        if options is None:
+            options = webdriver.FirefoxOptions()
+            options.add_argument('--start-maximized')
+
+        service = FirefoxService(executable_path=self.driver_path)
+        self.driver = webdriver.Firefox(service=service, options=options)
+        return self.driver
+
+    def create_edge_driver(self, options=None):
+        if options is None:
+            print(self.driver_path)
+            options = EdgeOptions()
+            options.add_argument('--start-maximized')
+            options.add_argument('--disable-notifications')
+            # 添加使用Chromium版本的Edge标志
+            options.use_chromium = True
+
+        service = EdgeService(executable_path=self.driver_path)
+        self.driver = webdriver.Edge(service=service, options=options)
+        return self.driver
+
+    def close_driver(self):
+        if self.driver:
+            self.driver.quit()
+            self.driver = None
+
+    def get_driver_info(self):
+        if self.driver:
+            return {
+                "browser_name": self.driver.name,
+                "browser_version": self.driver.capabilities['browserVersion'],
+                "driver_path": self.driver_path
+            }
+        return None
+
+
+# 示例
+if __name__ == "__main__":
+    # 创建驱动选择器实例
+    selector = DriverSelector()
+    try:
+        # 创建Edge驱动
+        driver = selector.create_edge_driver()
+        # 打开网页
+        driver.get("https://www.baidu.com")
+        print("当前页面标题:", driver.title)
+        # 获取驱动信息
+        info = selector.get_driver_info()
+        print("浏览器信息:", info)
+    except Exception as e:
+        print(f"发生错误: {e}")
+    finally:
+        # 关闭驱动
+        if 'selector' in locals():
+            selector.close_driver()

+ 0 - 0
Base/WebUI/Tools/__init__.py


+ 471 - 0
Base/WebUI/UI/Base.py

@@ -0,0 +1,471 @@
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.action_chains import ActionChains
+from selenium.webdriver.common.keys import Keys
+from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotVisibleException
+import time
+
+
+class SeleniumBase:
+    def __init__(self, driver):
+        """
+        初始化Selenium基础操作类
+        :param driver: WebDriver实例
+        """
+        self.driver = driver
+        self.wait = WebDriverWait(self.driver, 10)  # 默认等待10秒
+
+    def set_implicit_wait(self, seconds):
+        """设置隐式等待时间"""
+        self.driver.implicitly_wait(seconds)
+
+    def set_explicit_wait(self, seconds):
+        """设置显式等待时间"""
+        self.wait = WebDriverWait(self.driver, seconds)
+
+    # ==================== 浏览器操作 ====================
+    def open_url(self, url):
+        """打开指定URL"""
+        self.driver.get(url)
+
+    def get_current_url(self):
+        """获取当前URL"""
+        return self.driver.current_url
+
+    def get_page_title(self):
+        """获取页面标题"""
+        return self.driver.title
+
+    def refresh_page(self):
+        """刷新页面"""
+        self.driver.refresh()
+
+    def navigate_forward(self):
+        """浏览器前进"""
+        self.driver.forward()
+
+    def navigate_back(self):
+        """浏览器后退"""
+        self.driver.back()
+
+    def maximize_window(self):
+        """最大化窗口"""
+        self.driver.maximize_window()
+
+    def set_window_size(self, width, height):
+        """设置窗口大小"""
+        self.driver.set_window_size(width, height)
+
+    def get_window_size(self):
+        """获取窗口大小"""
+        return self.driver.get_window_size()
+
+    def switch_to_frame(self, frame_reference):
+        """切换到指定frame"""
+        self.driver.switch_to.frame(frame_reference)
+
+    def switch_to_default_content(self):
+        """切换回默认内容"""
+        self.driver.switch_to.default_content()
+
+    def switch_to_window(self, window_handle):
+        """切换到指定窗口"""
+        self.driver.switch_to.window(window_handle)
+
+    def switch_to_newest_window(self):
+        """切换到最新打开的窗口"""
+        handles = self.driver.window_handles
+        self.driver.switch_to.window(handles[-1])
+        return handles[-1]
+
+    def close_current_window(self):
+        """关闭当前窗口"""
+        self.driver.close()
+
+    def get_all_window_handles(self):
+        """获取所有窗口句柄"""
+        return self.driver.window_handles
+
+    def get_current_window_handle(self):
+        """获取当前窗口句柄"""
+        return self.driver.current_window_handle
+
+    def execute_script(self, script, *args):
+        """执行JavaScript脚本"""
+        return self.driver.execute_script(script, *args)
+
+    def scroll_to_element(self, element):
+        """滚动到指定元素"""
+        self.execute_script("arguments[0].scrollIntoView();", element)
+
+    def scroll_to_bottom(self):
+        """滚动到页面底部"""
+        self.execute_script("window.scrollTo(0, document.body.scrollHeight);")
+
+    def scroll_to_top(self):
+        """滚动到页面顶部"""
+        self.execute_script("window.scrollTo(0, 0);")
+
+    def take_screenshot(self, filename):
+        """截取屏幕截图"""
+        self.driver.save_screenshot(filename)
+
+    # ==================== 元素定位 ====================
+    def find_element(self, by, value, timeout=None):
+        """
+        查找元素(带等待)
+        :param by: 定位方式 (By.ID, By.XPATH, By.CSS_SELECTOR, etc.)
+        :param value: 定位值
+        :param timeout: 超时时间,默认使用类初始化时的等待时间
+        :return: WebElement
+        """
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        try:
+            return wait.until(EC.presence_of_element_located((by, value)))
+        except TimeoutException:
+            raise NoSuchElementException(f"元素未找到: {by}={value}")
+
+    def find_elements(self, by, value, timeout=None):
+        """
+        查找多个元素(带等待)
+        :param by: 定位方式
+        :param value: 定位值
+        :param timeout: 超时时间
+        :return: 元素列表
+        """
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        try:
+            return wait.until(EC.presence_of_all_elements_located((by, value)))
+        except TimeoutException:
+            return []
+
+    def find_visible_element(self, by, value, timeout=None):
+        """
+        查找可见元素
+        :param by: 定位方式
+        :param value: 定位值
+        :param timeout: 超时时间
+        :return: WebElement
+        """
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        try:
+            return wait.until(EC.visibility_of_element_located((by, value)))
+        except TimeoutException:
+            raise ElementNotVisibleException(f"元素不可见: {by}={value}")
+
+    def find_clickable_element(self, by, value, timeout=None):
+        """
+        查找可点击元素
+        :param by: 定位方式
+        :param value: 定位值
+        :param timeout: 超时时间
+        :return: WebElement
+        """
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        try:
+            return wait.until(EC.element_to_be_clickable((by, value)))
+        except TimeoutException:
+            raise ElementNotVisibleException(f"元素不可点击: {by}={value}")
+
+    # 快捷定位方法
+    def by_id(self, id_value, timeout=None):
+        """通过ID定位元素"""
+        return self.find_element(By.ID, id_value, timeout)
+
+    def by_name(self, name_value, timeout=None):
+        """通过Name定位元素"""
+        return self.find_element(By.NAME, name_value, timeout)
+
+    def by_xpath(self, xpath_value, timeout=None):
+        """通过XPath定位元素"""
+        return self.find_element(By.XPATH, xpath_value, timeout)
+
+    def by_css(self, css_value, timeout=None):
+        """通过CSS选择器定位元素"""
+        return self.find_element(By.CSS_SELECTOR, css_value, timeout)
+
+    def by_class(self, class_value, timeout=None):
+        """通过Class定位元素"""
+        return self.find_element(By.CLASS_NAME, class_value, timeout)
+
+    def by_link_text(self, link_text, timeout=None):
+        """通过链接文本定位元素"""
+        return self.find_element(By.LINK_TEXT, link_text, timeout)
+
+    def by_partial_link_text(self, partial_link_text, timeout=None):
+        """通过部分链接文本定位元素"""
+        return self.find_element(By.PARTIAL_LINK_TEXT, partial_link_text, timeout)
+
+    def by_tag(self, tag_name, timeout=None):
+        """通过标签名定位元素"""
+        return self.find_element(By.TAG_NAME, tag_name, timeout)
+
+    # ==================== 元素操作 ====================
+    def click(self, by, value, timeout=None):
+        """点击元素"""
+        element = self.find_clickable_element(by, value, timeout)
+        element.click()
+
+    def input_text(self, by, value, text, timeout=None):
+        """输入文本"""
+        element = self.find_element(by, value, timeout)
+        element.clear()
+        element.send_keys(text)
+
+    def clear_text(self, by, value, timeout=None):
+        """清除文本"""
+        element = self.find_element(by, value, timeout)
+        element.clear()
+
+    def get_text(self, by, value, timeout=None):
+        """获取元素文本"""
+        element = self.find_element(by, value, timeout)
+        return element.text
+
+    def get_attribute(self, by, value, attribute_name, timeout=None):
+        """获取元素属性"""
+        element = self.find_element(by, value, timeout)
+        return element.get_attribute(attribute_name)
+
+    def is_displayed(self, by, value, timeout=None):
+        """检查元素是否显示"""
+        try:
+            element = self.find_visible_element(by, value, timeout)
+            return element.is_displayed()
+        except (NoSuchElementException, ElementNotVisibleException):
+            return False
+
+    def is_enabled(self, by, value, timeout=None):
+        """检查元素是否启用"""
+        try:
+            element = self.find_element(by, value, timeout)
+            return element.is_enabled()
+        except NoSuchElementException:
+            return False
+
+    def is_selected(self, by, value, timeout=None):
+        """检查元素是否被选中"""
+        try:
+            element = self.find_element(by, value, timeout)
+            return element.is_selected()
+        except NoSuchElementException:
+            return False
+
+    def select_dropdown_by_value(self, by, value, option_value, timeout=None):
+        """通过值选择下拉选项"""
+        from selenium.webdriver.support.ui import Select
+        element = self.find_element(by, value, timeout)
+        select = Select(element)
+        select.select_by_value(option_value)
+
+    def select_dropdown_by_text(self, by, value, option_text, timeout=None):
+        """通过文本选择下拉选项"""
+        from selenium.webdriver.support.ui import Select
+        element = self.find_element(by, value, timeout)
+        select = Select(element)
+        select.select_by_visible_text(option_text)
+
+    def select_dropdown_by_index(self, by, value, index, timeout=None):
+        """通过索引选择下拉选项"""
+        from selenium.webdriver.support.ui import Select
+        element = self.find_element(by, value, timeout)
+        select = Select(element)
+        select.select_by_index(index)
+
+    def get_dropdown_options(self, by, value, timeout=None):
+        """获取下拉选项"""
+        from selenium.webdriver.support.ui import Select
+        element = self.find_element(by, value, timeout)
+        select = Select(element)
+        return [option.text for option in select.options]
+
+    def hover(self, by, value, timeout=None):
+        """鼠标悬停"""
+        element = self.find_element(by, value, timeout)
+        ActionChains(self.driver).move_to_element(element).perform()
+
+    def double_click(self, by, value, timeout=None):
+        """双击元素"""
+        element = self.find_element(by, value, timeout)
+        ActionChains(self.driver).double_click(element).perform()
+
+    def right_click(self, by, value, timeout=None):
+        """右键点击元素"""
+        element = self.find_element(by, value, timeout)
+        ActionChains(self.driver).context_click(element).perform()
+
+    def drag_and_drop(self, source_by, source_value, target_by, target_value, timeout=None):
+        """拖放元素"""
+        source_element = self.find_element(source_by, source_value, timeout)
+        target_element = self.find_element(target_by, target_value, timeout)
+        ActionChains(self.driver).drag_and_drop(source_element, target_element).perform()
+
+    def press_key(self, by, value, key, timeout=None):
+        """按下键盘按键"""
+        element = self.find_element(by, value, timeout)
+        element.send_keys(key)
+
+    def press_enter(self, by, value, timeout=None):
+        """按下回车键"""
+        self.press_key(by, value, Keys.ENTER, timeout)
+
+    def press_tab(self, by, value, timeout=None):
+        """按下Tab键"""
+        self.press_key(by, value, Keys.TAB, timeout)
+
+    def press_escape(self, by, value, timeout=None):
+        """按下Esc键"""
+        self.press_key(by, value, Keys.ESCAPE, timeout)
+
+    # ==================== 等待方法 ====================
+    def wait_for_element_present(self, by, value, timeout=None):
+        """等待元素出现"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.presence_of_element_located((by, value)))
+
+    def wait_for_element_visible(self, by, value, timeout=None):
+        """等待元素可见"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.visibility_of_element_located((by, value)))
+
+    def wait_for_element_invisible(self, by, value, timeout=None):
+        """等待元素不可见"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.invisibility_of_element_located((by, value)))
+
+    def wait_for_element_clickable(self, by, value, timeout=None):
+        """等待元素可点击"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.element_to_be_clickable((by, value)))
+
+    def wait_for_text_present(self, by, value, text, timeout=None):
+        """等待元素包含特定文本"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.text_to_be_present_in_element((by, value), text))
+
+    def wait_for_title_contains(self, text, timeout=None):
+        """等待标题包含特定文本"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.title_contains(text))
+
+    def wait_for_title_is(self, text, timeout=None):
+        """等待标题等于特定文本"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.title_is(text))
+
+    def wait_for_alert_present(self, timeout=None):
+        """等待警告框出现"""
+        wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
+        return wait.until(EC.alert_is_present())
+
+    # ==================== 弹窗处理 ====================
+    def accept_alert(self, timeout=None):
+        """接受警告框"""
+        alert = self.wait_for_alert_present(timeout)
+        alert.accept()
+
+    def dismiss_alert(self, timeout=None):
+        """取消警告框"""
+        alert = self.wait_for_alert_present(timeout)
+        alert.dismiss()
+
+    def get_alert_text(self, timeout=None):
+        """获取警告框文本"""
+        alert = self.wait_for_alert_present(timeout)
+        return alert.text
+
+    def input_alert_text(self, text, timeout=None):
+        """向警告框输入文本"""
+        alert = self.wait_for_alert_present(timeout)
+        alert.send_keys(text)
+        alert.accept()
+
+    # ==================== Cookie操作 ====================
+    def get_all_cookies(self):
+        """获取所有Cookie"""
+        return self.driver.get_cookies()
+
+    def get_cookie_by_name(self, name):
+        """根据名称获取Cookie"""
+        return self.driver.get_cookie(name)
+
+    def add_cookie(self, cookie_dict):
+        """添加Cookie"""
+        self.driver.add_cookie(cookie_dict)
+
+    def delete_cookie(self, name):
+        """删除指定Cookie"""
+        self.driver.delete_cookie(name)
+
+    def delete_all_cookies(self):
+        """删除所有Cookie"""
+        self.driver.delete_all_cookies()
+
+    # ==================== JS执行 ====================
+    def js_click(self, by, value, timeout=None):
+        """通过JS点击元素"""
+        element = self.find_element(by, value, timeout)
+        self.execute_script("arguments[0].click();", element)
+
+    def js_input_text(self, by, value, text, timeout=None):
+        """通过JS输入文本"""
+        element = self.find_element(by, value, timeout)
+        self.execute_script("arguments[0].value = arguments[1];", element, text)
+
+    def js_set_attribute(self, by, value, attr_name, attr_value, timeout=None):
+        """通过JS设置元素属性"""
+        element = self.find_element(by, value, timeout)
+        self.execute_script(f"arguments[0].setAttribute('{attr_name}', '{attr_value}');", element)
+
+    def js_remove_attribute(self, by, value, attr_name, timeout=None):
+        """通过JS移除元素属性"""
+        element = self.find_element(by, value, timeout)
+        self.execute_script(f"arguments[0].removeAttribute('{attr_name}');", element)
+
+    def js_scroll_to_element(self, by, value, timeout=None):
+        """通过JS滚动到元素"""
+        element = self.find_element(by, value, timeout)
+        self.execute_script("arguments[0].scrollIntoView(true);", element)
+
+    # ==================== 其他实用方法 ====================
+    def switch_to_active_element(self):
+        """切换到当前活动元素"""
+        return self.driver.switch_to.active_element
+
+    def get_page_source(self):
+        """获取页面源码"""
+        return self.driver.page_source
+
+    def wait_for_page_load(self, timeout=30):
+        """等待页面完全加载"""
+        old_page = self.driver.find_element(By.TAG_NAME, 'html')
+        yield
+        WebDriverWait(self.driver, timeout).until(
+            EC.staleness_of(old_page)
+        )
+
+    def is_alert_present(self, timeout=5):
+        """检查警告框是否存在"""
+        try:
+            WebDriverWait(self.driver, timeout).until(EC.alert_is_present())
+            return True
+        except TimeoutException:
+            return False
+
+    def wait_for_ajax_complete(self, timeout=30):
+        """等待AJAX请求完成"""
+        js_condition = "return jQuery.active == 0"
+        WebDriverWait(self.driver, timeout).until(
+            lambda driver: self.execute_script(js_condition)
+        )
+
+    def highlight_element(self, by, value, timeout=None, duration=3):
+        """高亮显示元素(用于调试)"""
+        element = self.find_element(by, value, timeout)
+        original_style = element.get_attribute("style")
+        self.execute_script("arguments[0].setAttribute('style', arguments[1]);",
+                            element, "border: 2px solid red; border-style: dashed;")
+        time.sleep(duration)
+        self.execute_script("arguments[0].setAttribute('style', arguments[1]);",
+                            element, original_style)

+ 0 - 0
Base/WebUI/UI/__init__.py


+ 5 - 0
Config/Config.py

@@ -0,0 +1,5 @@
+import logging
+
+class Config(object):
+    log_level = logging.DEBUG
+    log_format = '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'

+ 7 - 0
Config/UIConfig.py

@@ -0,0 +1,7 @@
+class Config:
+    # 驱动器路径 , 为了适配Windows使用的路径方法,暂时不去简化配置方法
+    EDGE_DRIVER = "\\Base\\WebUI\\Driver\\msedgedriver.exe"
+    CHROME_DRIVER = "\\Base\\WebUI\\Driver\\"
+    FOXFIRE_DRIVER = "\\Base\\WebUI\\Driver\\"
+    #测试项目地址
+    BASE_UII = "http://115.190.47.211:3000/"

+ 57 - 0
Utils/Debug.py

@@ -0,0 +1,57 @@
+"""
+    作用 : 用于debug调试相关的函数,不用于最终的记录
+"""
+import logging
+import time
+from functools import wraps
+
+# 配置日志
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger('FunctionLogger')
+
+
+def log_function_call(level=logging.INFO):
+    """
+    函数日志记录装饰器
+
+    参数:
+        level: 日志级别,默认为INFO
+    """
+
+    def decorator(func):
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            # 记录函数开始执行
+            logger.log(level, f"开始执行函数: {func.__name__}")
+            logger.log(level, f"参数: args={args}, kwargs={kwargs}")
+
+            # 记录执行时间
+            start_time = time.time()
+
+            try:
+                # 执行函数
+                result = func(*args, **kwargs)
+                # 记录执行结果
+                end_time = time.time()
+                execution_time = end_time - start_time
+
+                logger.log(level, f"函数 {func.__name__} 执行成功")
+                logger.log(level, f"返回值: {result}")
+                logger.log(level, f"执行时间: {execution_time:.4f} 秒")
+
+                return result
+            except Exception as e:
+                # 记录异常信息
+                end_time = time.time()
+                execution_time = end_time - start_time
+
+                logger.error(f"函数 {func.__name__} 执行失败: {str(e)}")
+                logger.error(f"执行时间: {execution_time:.4f} 秒")
+                raise  # 重新抛出异常
+
+        return wrapper
+
+    return decorator

+ 144 - 0
Utils/Log.py

@@ -0,0 +1,144 @@
+import logging
+import sys
+import traceback
+from datetime import datetime
+from pathlib import Path
+from Config.Config import Config
+
+
+class Logger:
+    def __init__(self, log_path: str = None, log_name: str = None):
+        """
+        初始化日志记录器
+        Args:
+            log_path: 日志存储路径,默认为项目根目录下的Log文件夹
+            log_name: 日志文件名,默认为当前日期时间
+        """
+        # 设置默认日志路径为项目根目录下的Log文件夹
+        if log_path is None:
+            project_root = self._find_project_root()
+            log_path = project_root / "Log"
+        # 创建日志目录(如果不存在)
+        self.log_path = Path(log_path)
+        self.log_path.mkdir(parents=True, exist_ok=True)
+        # 设置日志文件名
+        if log_name is None:
+            log_name = f"log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
+        self.log_file = self.log_path / log_name
+        # 配置日志记录器
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(Config.log_level)
+        # 清除现有处理器(避免重复记录)
+        self.logger.handlers.clear()
+        # 创建格式化器
+        formatter = logging.Formatter(Config.log_format)
+        # 文件处理器(记录所有级别日志)
+        file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
+        file_handler.setLevel(logging.DEBUG)
+        file_handler.setFormatter(formatter)
+        self.logger.addHandler(file_handler)
+        # 控制台处理器(仅记录INFO及以上级别)
+        console_handler = logging.StreamHandler()
+        console_handler.setLevel(logging.INFO)
+        console_handler.setFormatter(formatter)
+        self.logger.addHandler(console_handler)
+        # 设置异常钩子,捕获未处理的异常
+        sys.excepthook = self._handle_exception
+        # 重定向标准输出和标准错误
+        self._redirect_stdout_stderr()
+
+    def _find_project_root(self):
+        """查找项目根目录"""
+        # 查找包含.git、.project、setup.py或requirements.txt等标记文件的目录
+        current_dir = Path(__file__).resolve().parent
+        # 向上查找直到找到项目根目录标记
+        for parent in [current_dir] + list(current_dir.parents):
+            if any((parent / marker).exists() for marker in
+                   ['.git', '.project', 'setup.py', 'requirements.txt', 'pyproject.toml']):
+                return parent
+        # 如果找不到标记文件,则使用当前工作目录
+        return Path.cwd()
+
+    def _redirect_stdout_stderr(self):
+        """
+        重定向标准输出和标准错误到日志
+        :return:
+        """
+        class StreamToLogger:
+            def __init__(self, logger, level):
+                self.logger = logger
+                self.level = level
+                self.linebuf = ''
+
+            def write(self, buf):
+                for line in buf.rstrip().splitlines():
+                    if line.strip():  # 忽略空行
+                        self.logger.log(self.level, line.rstrip())
+
+            def flush(self):
+                pass
+        # 重定向标准输出到INFO级别
+        sys.stdout = StreamToLogger(self.logger, logging.INFO)
+        # 重定向标准错误到ERROR级别
+        sys.stderr = StreamToLogger(self.logger, logging.ERROR)
+
+    def _handle_exception(self, exc_type, exc_value, exc_traceback):
+        """处理未捕获的异常"""
+        # 忽略KeyboardInterrupt,以便控制台可以正常退出
+        if issubclass(exc_type, KeyboardInterrupt):
+            sys.__excepthook__(exc_type, exc_value, exc_traceback)
+            return
+        self.logger.error(
+            "未捕获的异常:",
+            exc_info=(exc_type, exc_value, exc_traceback)
+        )
+
+    def debug(self, msg):
+        self.logger.debug(msg)
+
+    def info(self, msg):
+        self.logger.info(msg)
+
+    def warning(self, msg):
+        self.logger.warning(msg)
+
+    def error(self, msg):
+        self.logger.error(msg)
+
+    def critical(self, msg):
+        self.logger.critical(msg)
+
+    def exception(self, msg, exc_info=True):
+        """记录异常信息,包括堆栈跟踪"""
+        self.logger.exception(msg, exc_info=exc_info)
+
+    def log_exception(self, e, context=""):
+        """记录异常及其上下文信息"""
+        error_msg = f"{context}: {type(e).__name__}: {str(e)}"
+        self.error(error_msg)
+        self.error("堆栈跟踪:\n" + "".join(traceback.format_tb(e.__traceback__)))
+
+
+# 使用示例
+if __name__ == "__main__":
+    # 创建日志记录器实例(使用默认路径)
+    logger = Logger(log_name="my_app.log")
+    # 记录不同级别的日志
+    logger.debug("这是一条调试信息")
+    logger.info("程序启动成功")
+    logger.warning("磁盘空间不足")
+    # 模拟一个异常
+    try:
+        result = 1 / 0
+    except Exception as e:
+        logger.log_exception(e, "除法运算时发生错误")
+    # 模拟控制台输出
+    print("这是一条普通的控制台输出")
+    # 模拟控制台错误输出
+    sys.stderr.write("这是一条错误输出\n")
+    # 模拟未捕获的异常
+    def cause_exception():
+        raise ValueError("这是一个未捕获的异常")
+    # 调用会抛出异常的函数
+    cause_exception()
+    logger.info("程序执行完成")