Files
pqAutomationApp/app/logging_setup.py
2026-04-29 16:43:31 +08:00

123 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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