import os import sys from pathlib import Path from datetime import datetime, timedelta import logging from logging.handlers import RotatingFileHandler import inspect try: import win32gui import win32con WIN32_AVAILABLE = True except ImportError: WIN32_AVAILABLE = False from PubLibrary.InifileHelper import IniRead # ===================== 新增:获取 EXE 同级目录 ===================== def get_app_root(): """获取 runDecoder.exe 所在的真实根目录(兼容 onefile / standalone)""" if getattr(sys, 'frozen', False): # Nuitka / PyInstaller 打包后走这里 app_path = sys.executable else: # 本地源码运行时,取当前脚本目录 app_path = os.path.abspath(__file__) return os.path.dirname(app_path) # 程序根目录(exe 同级) APP_ROOT = Path(get_app_root()) # 日志文件夹名:exe 同级下 logs 目录 DEFAULT_LOG_DIR = APP_ROOT / "logs" # ===================== 读取 [algo_log] 配置 ===================== # 文件日志 FILE_LOG_ENABLE = IniRead("algo_log", "file_log_enable", "true").lower() == "true" FILE_LOG_LEVEL = IniRead("algo_log", "file_log_level", "DEBUG").upper() # 优先级:配置文件 > 默认exe同级logs CFG_LOG_PATH = IniRead("algo_log", "log_path", "").strip() if CFG_LOG_PATH == "exe": LOG_DIR = DEFAULT_LOG_DIR else: LOG_DIR = Path(CFG_LOG_PATH) LOG_RETENTION_DAYS = int(IniRead("algo_log", "retention_days", 3)) # 控制台日志 + 黑框控制 CONSOLE_ENABLE = IniRead("algo_log", "console_enable", "true").lower() == "true" CONSOLE_SHOW_WINDOW = IniRead("algo_log", "console_show_window", "true").lower() == "true" CONSOLE_LOG_LEVEL = IniRead("algo_log", "console_log_level", "INFO").upper() # ===================== 全局常量与缓存 ===================== log_once_cache = set() logger_cache = {} LOG_FILE_PREFIX = 'algo_log_' # 确保日志目录存在 LOG_DIR.mkdir(parents=True, exist_ok=True) LOG_DIR_STR = str(LOG_DIR) + "\\" # 日志格式 LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' DATE_FORMAT = '%Y-%m-%d %H:%M:%S' # 日志级别映射 LEVEL_MAP = { "DEBUG": logging.DEBUG, "INFO": logging.INFO, "WARNING": logging.WARNING, "ERROR": logging.ERROR, "FATAL": logging.FATAL } FILE_LOG_LEVEL_INT = LEVEL_MAP.get(FILE_LOG_LEVEL, logging.INFO) CONSOLE_LOG_LEVEL_INT = LEVEL_MAP.get(CONSOLE_LOG_LEVEL, logging.INFO) # ===================== Windows 控制台黑框显示/隐藏 ===================== def control_console_window(): if not sys.platform.startswith("win") or not WIN32_AVAILABLE: return try: hwnd = win32gui.GetForegroundWindow() if CONSOLE_SHOW_WINDOW: win32gui.ShowWindow(hwnd, win32con.SW_SHOW) else: win32gui.ShowWindow(hwnd, win32con.SW_HIDE) except Exception: pass control_console_window() # ===================== 清理过期日志 ===================== def clean_old_logs(): try: if not LOG_DIR.exists(): return expire_date = datetime.now() - timedelta(days=LOG_RETENTION_DAYS) for filename in os.listdir(LOG_DIR): if not (filename.startswith(LOG_FILE_PREFIX) and filename.endswith('.log')): continue date_str = filename[len(LOG_FILE_PREFIX):-4] try: file_date = datetime.strptime(date_str, '%Y-%m-%d') if file_date < expire_date: file_path = LOG_DIR / filename os.remove(file_path) except ValueError: continue except Exception: pass # ===================== 初始化日志器 ===================== def init_module_logger(logger_name): if logger_name in logger_cache: return logger_cache[logger_name] clean_old_logs() logger = logging.getLogger(logger_name) logger.setLevel(logging.DEBUG) if logger.handlers: logger_cache[logger_name] = logger return logger formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT) # 文件日志 if FILE_LOG_ENABLE: current_date = datetime.now().strftime("%Y-%m-%d") log_file = LOG_DIR / f"{LOG_FILE_PREFIX}{current_date}.log" file_handler = RotatingFileHandler( log_file, maxBytes=10 * 1024 * 1024, backupCount=10, encoding='utf-8' ) file_handler.setFormatter(formatter) file_handler.setLevel(FILE_LOG_LEVEL_INT) logger.addHandler(file_handler) # 控制台日志 if CONSOLE_ENABLE: console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(formatter) console_handler.setLevel(CONSOLE_LOG_LEVEL_INT) logger.addHandler(console_handler) logger_cache[logger_name] = logger return logger # ===================== 对外日志入口函数 ===================== def algo_log(content, level="INFO", record_once=False): frame = inspect.currentframe() if frame: frame = frame.f_back.f_back file_name = os.path.basename(frame.f_code.co_filename) if frame else "unknown" logger = init_module_logger(file_name) if record_once: log_key = f"{level.upper()}_{content}" if log_key in log_once_cache: return log_once_cache.add(log_key) level_upper = level.upper() log_func_map = { "DEBUG": logger.debug, "INFO": logger.info, "WARNING": logger.warning, "ERROR": logger.error, "FATAL": logger.fatal } log_func = log_func_map.get(level_upper, logger.info) log_func(content)