123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import os
|
|||
|
|
from datetime import datetime
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
|
|||
|
|
# stdlib 级别 → PQLogGUI 标签
|
|||
|
|
_LEVEL_TO_GUI = {
|
|||
|
|
logging.DEBUG: "debug",
|
|||
|
|
logging.INFO: "info",
|
|||
|
|
logging.WARNING: "warning",
|
|||
|
|
logging.ERROR: "error",
|
|||
|
|
logging.CRITICAL: "error",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# PQLogGUI 标签 → stdlib 级别
|
|||
|
|
GUI_LEVEL_TO_LOG = {
|
|||
|
|
"debug": logging.DEBUG,
|
|||
|
|
"info": logging.INFO,
|
|||
|
|
"success": logging.INFO,
|
|||
|
|
"warning": logging.WARNING,
|
|||
|
|
"error": logging.ERROR,
|
|||
|
|
"separator": logging.INFO,
|
|||
|
|
"blank": logging.DEBUG,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 用于在 LogRecord 上做 GUI 来源标记,避免回环
|
|||
|
|
_FROM_GUI_FLAG = "_from_gui"
|
|||
|
|
|
|||
|
|
# 文件日志:头一行元信息,第二行正文,记录之间空行隔开,方便阅读
|
|||
|
|
_FILE_LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s\n%(message)s\n"
|
|||
|
|
_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|||
|
|
|
|||
|
|
_initialized = False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _resolve_log_dir(log_dir: Optional[str]) -> str:
|
|||
|
|
if log_dir:
|
|||
|
|
return log_dir
|
|||
|
|
# 默认放在工作目录下的 log/,方便用户直接打开查看
|
|||
|
|
return os.path.join(os.getcwd(), "log")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def setup_logging(
|
|||
|
|
level: int = logging.INFO,
|
|||
|
|
log_dir: Optional[str] = None,
|
|||
|
|
) -> str:
|
|||
|
|
"""配置全局 logging,返回日志目录。可重复调用,第二次起为空操作。
|
|||
|
|
|
|||
|
|
- 不再向终端输出,避免污染控制台。
|
|||
|
|
- 日志文件按日期命名:``log/YYYY-MM-DD.log``。
|
|||
|
|
"""
|
|||
|
|
global _initialized
|
|||
|
|
root = logging.getLogger()
|
|||
|
|
if _initialized:
|
|||
|
|
return _resolve_log_dir(log_dir)
|
|||
|
|
|
|||
|
|
resolved = _resolve_log_dir(log_dir)
|
|||
|
|
os.makedirs(resolved, exist_ok=True)
|
|||
|
|
|
|||
|
|
file_formatter = logging.Formatter(_FILE_LOG_FORMAT, datefmt=_DATE_FORMAT)
|
|||
|
|
|
|||
|
|
root.setLevel(level)
|
|||
|
|
|
|||
|
|
# 移除既有 handler,避免重复 / basicConfig 默认控制台输出
|
|||
|
|
for handler in list(root.handlers):
|
|||
|
|
root.removeHandler(handler)
|
|||
|
|
|
|||
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|||
|
|
log_file = os.path.join(resolved, f"{today}.log")
|
|||
|
|
file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True)
|
|||
|
|
file_handler.setLevel(logging.DEBUG)
|
|||
|
|
file_handler.setFormatter(file_formatter)
|
|||
|
|
root.addHandler(file_handler)
|
|||
|
|
|
|||
|
|
_initialized = True
|
|||
|
|
logging.getLogger(__name__).info("日志系统初始化完成 dir=%s", resolved)
|
|||
|
|
return resolved
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TkLogHandler(logging.Handler):
|
|||
|
|
"""把 stdlib logging 的记录转发到 ``PQLogGUI`` 面板。
|
|||
|
|
|
|||
|
|
带 ``_from_gui`` 标记的记录会被忽略,避免 GUI → file → GUI 回环。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, log_gui):
|
|||
|
|
super().__init__()
|
|||
|
|
self._log_gui = log_gui
|
|||
|
|
|
|||
|
|
def emit(self, record: logging.LogRecord) -> None: # noqa: D401
|
|||
|
|
if getattr(record, _FROM_GUI_FLAG, False):
|
|||
|
|
return
|
|||
|
|
try:
|
|||
|
|
message = self.format(record)
|
|||
|
|
except Exception:
|
|||
|
|
try:
|
|||
|
|
message = record.getMessage()
|
|||
|
|
except Exception:
|
|||
|
|
return
|
|||
|
|
gui_level = _LEVEL_TO_GUI.get(record.levelno, "info")
|
|||
|
|
try:
|
|||
|
|
# PQLogGUI.log 内部已处理跨线程
|
|||
|
|
self._log_gui.log(message, level=gui_level, _from_logging=True)
|
|||
|
|
except Exception:
|
|||
|
|
self.handleError(record)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def attach_gui_handler(log_gui) -> TkLogHandler:
|
|||
|
|
"""把 ``PQLogGUI`` 注册为 root logger 的 handler。已存在则替换。"""
|
|||
|
|
root = logging.getLogger()
|
|||
|
|
# 移除旧的 TkLogHandler,保证只挂一个
|
|||
|
|
for h in list(root.handlers):
|
|||
|
|
if isinstance(h, TkLogHandler):
|
|||
|
|
root.removeHandler(h)
|
|||
|
|
handler = TkLogHandler(log_gui)
|
|||
|
|
handler.setLevel(logging.INFO)
|
|||
|
|
handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
|
|||
|
|
root.addHandler(handler)
|
|||
|
|
return handler
|