现象
日志功能分散在 5 个模块里,是全包复杂度最集中的地方。分层本身(log_parser → log_config → {logging, exp_logging})是合理的,问题全在叶子上:重复实现、loguru 迁移的残留、以及一个已经失效但没人发现的格式化开关。
复杂度排行榜前几名有一半在这里:
| 函数 |
行数 |
分支 |
setup_exp_logger |
147 |
27 |
setup_model_logger |
100 |
8 |
MainModel._setup_logger |
88 |
19 |
setup_integrated_logging |
87 |
6 |
逐条
1. LoggerAdapter.bind(no_format=True) 完全无效
abses/utils/log_config.py:
def _format_message(self, message: str) -> str:
if self._extra.get("no_format"):
return message
return message # ← 两个分支返回同一个东西
abses/utils/logging.py 里四处横幅输出(log_session、setup_logger_info ×2、log_repeat_separator)都调用 logger.bind(no_format=True),本意是让 ASCII 横幅不带时间戳前缀。实际每一行都被打上 [12:00:00][abses][INFO] - 。这是 loguru → 标准库迁移遗留的未修回归,日志文件里能直接看到。
同源残留:LoggerAdapter.add() 永远返回 0 什么都不做(用户以为加了 handler,其实没有),remove(None) 会清掉全部 handler。
要么把 no_format 真正实现(例如切到一个 SIMPLE_FORMAT 的 handler,或用 extra + 自定义 Formatter),要么把这个 API 连同 LoggerAdapter 一起删掉、直接用标准 logging.Logger。
2. exp_logging.py 有大段重复,而且重造了已有的轮子
「入参是 dict 还是 DictConfig」这个分支在同一个函数里逐字重复了 4 次,outpath 解析块逐字重复了 2 次:
if isinstance(cfg, dict):
outpath = cfg.get("outpath")
else:
try:
outpath = OmegaConf.select(cfg, "outpath", default=None)
except Exception:
outpath = None
两个问题:
abses/utils/config.py 已经有 normalize_config() 专门做 dict/DictConfig/None → DictConfig 的归一化,这里没复用。
- 那四个
except Exception 包的是 OmegaConf.select(..., default=...),而它对缺失键根本不抛异常。这四个兜底唯一的作用是掩盖真实的插值错误 / ConfigAttributeError。
抽出一个 _resolve_outpath(cfg) 和一个 _select(cfg, key, default),这个文件能缩掉近一半。
3. 死代码
abses/utils/hydra_logging.py —— generate_hydra_job_logging 全仓唯一命中就是它自己的定义,0% 覆盖率。已在 71a5b34 删除。
configure_root_logger、logging.formatter / FORMAT(后者还是 loguru 的 {time:HH:mm:ss} 花括号语法却挂在 __all__ 里)—— 零调用点。已在 71a5b34 删除。
4. 格式常量三处重复定义
"[%(asctime)s][%(name)s][%(levelname)s] - %(message)s" / "%H:%M:%S" / "INFO" 在 log_config.py、log_parser.py、(已删的)hydra_logging.py 各定义一遍。log_config.py 的注释自己都写着 # Default values (matching log_parser.py)。收敛到一个模块。
5. 作为库,破坏性地重配了根 logger
abses/utils/logging.py 的 setup_model_logger:
for handler in root_logger.handlers[:]:
handler_name = getattr(handler, "name", "") or ""
if "hydra" not in handler_name.lower():
root_logger.removeHandler(handler)
按 handler.name 做子串嗅探来摘除根 logger 上的第三方 handler,再把 ABSESpy 自己的挂上去。叠加 setup_abses_logger 无条件 logger.handlers.clear()、setup_exp_logger 清掉 abses.core 父 logger,整体顺序敏感且无法单独测试。
一个库不应该改根 logger 的配置——那是应用(或 Hydra)的职责。
6. 覆盖率
exp_logging.py 58%,是全包最低的几个之一。
验收标准
现象
日志功能分散在 5 个模块里,是全包复杂度最集中的地方。分层本身(
log_parser→log_config→ {logging,exp_logging})是合理的,问题全在叶子上:重复实现、loguru 迁移的残留、以及一个已经失效但没人发现的格式化开关。复杂度排行榜前几名有一半在这里:
setup_exp_loggersetup_model_loggerMainModel._setup_loggersetup_integrated_logging逐条
1.
LoggerAdapter.bind(no_format=True)完全无效abses/utils/log_config.py:abses/utils/logging.py里四处横幅输出(log_session、setup_logger_info×2、log_repeat_separator)都调用logger.bind(no_format=True),本意是让 ASCII 横幅不带时间戳前缀。实际每一行都被打上[12:00:00][abses][INFO] -。这是 loguru → 标准库迁移遗留的未修回归,日志文件里能直接看到。同源残留:
LoggerAdapter.add()永远返回 0 什么都不做(用户以为加了 handler,其实没有),remove(None)会清掉全部 handler。要么把
no_format真正实现(例如切到一个SIMPLE_FORMAT的 handler,或用extra+ 自定义Formatter),要么把这个 API 连同LoggerAdapter一起删掉、直接用标准logging.Logger。2.
exp_logging.py有大段重复,而且重造了已有的轮子「入参是
dict还是DictConfig」这个分支在同一个函数里逐字重复了 4 次,outpath解析块逐字重复了 2 次:两个问题:
abses/utils/config.py已经有normalize_config()专门做 dict/DictConfig/None → DictConfig 的归一化,这里没复用。except Exception包的是OmegaConf.select(..., default=...),而它对缺失键根本不抛异常。这四个兜底唯一的作用是掩盖真实的插值错误 /ConfigAttributeError。抽出一个
_resolve_outpath(cfg)和一个_select(cfg, key, default),这个文件能缩掉近一半。3. 死代码
abses/utils/hydra_logging.py——generate_hydra_job_logging全仓唯一命中就是它自己的定义,0% 覆盖率。已在 71a5b34 删除。configure_root_logger、logging.formatter/FORMAT(后者还是 loguru 的{time:HH:mm:ss}花括号语法却挂在__all__里)—— 零调用点。已在 71a5b34 删除。4. 格式常量三处重复定义
"[%(asctime)s][%(name)s][%(levelname)s] - %(message)s"/"%H:%M:%S"/"INFO"在log_config.py、log_parser.py、(已删的)hydra_logging.py各定义一遍。log_config.py的注释自己都写着# Default values (matching log_parser.py)。收敛到一个模块。5. 作为库,破坏性地重配了根 logger
abses/utils/logging.py的setup_model_logger:按
handler.name做子串嗅探来摘除根 logger 上的第三方 handler,再把 ABSESpy 自己的挂上去。叠加setup_abses_logger无条件logger.handlers.clear()、setup_exp_logger清掉abses.core父 logger,整体顺序敏感且无法单独测试。一个库不应该改根 logger 的配置——那是应用(或 Hydra)的职责。
6. 覆盖率
exp_logging.py58%,是全包最低的几个之一。验收标准
no_format这个概念被彻底移除)。exp_logging.py里不再有 dict/DictConfig 的重复分支,改为复用normalize_config()。OmegaConf.select(..., default=...)的except Exception。exp_logging.py覆盖率提到 80% 以上。