172 lines
5.5 KiB
Python
172 lines
5.5 KiB
Python
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) |