Files
hotime/.cursor/plans/fix_caller_detection_v2_2c0111aa.plan.md
T
hoteas 3430bdec19 enhance(tests): 改进测试用例记录与覆盖率报告
- 在 ApiCase 中添加失败原因记录,提升测试结果的可读性
- 更新 CoverageReport 结构,增加总用例、通过用例和失败用例计数
- 优化 PrintCoverage 方法,支持详细输出未通过接口及其失败原因
- 更新 Swagger 生成逻辑,包含失败原因字段,增强调试信息
- 改进 SQL 错误计数逻辑,确保在测试模式下准确记录 SQL 错误
2026-04-13 06:45:16 +08:00

5.0 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
Fix caller detection v2 重新设计 findCaller 的调用者过滤逻辑:(1) 删除 maxFrameworkDepth 提前退出 (2) 收窄 isHoTimeFrameworkFile 定义为仅跳过日志/DB执行管线,让框架业务文件(code/makecode.go、application.go、context.go 等)作为有意义的调用者返回。
id content status
rewrite-findcaller 重写 findCaller:删除 maxFrameworkDepth、applicationFile 跟踪,简化为跳过基础设施 + 返回第一个业务文件 completed
id content status
rewrite-framework-check 重命名 isHoTimeFrameworkFile 为 isInfrastructureFile,收窄到只匹配 log/db/cache/common/dri,移除 code/ 和 application.go 等 completed
false

修复调用者智能识别(v2

根因分析

用户看到 [db/crud.go:149],实际调用者是 code/makecode.go。两个问题叠加:

问题 A: maxFrameworkDepth = 10 过低。zerolog 内部帧(~5) + log/logger.go(1) + db/query.go(3-4) + db/crud.go(1) 轻松达到 10,触发提前返回,根本没走到 code/makecode.go

问题 B: 即使去掉 maxFrameworkDepthcode/makecode.goframeworkCoreDirs 中被 "code/" + "makecode.go" 命中,也会被跳过。最终回退到 applicationFileapplication.go)或 lastFrameworkFile,都不是用户想要的。

设计思路

用户原话:"应用层触发的就打印应用层的,如果是框架层的业务造成的就打印框架层对应位置的,但肯定不是 log/logger.go"。

核心区分:

  • 基础设施文件(应跳过):日志管线(log/、zerolog/)、DB 执行管线(db/*)、缓存中间件(cache/*)、Go runtime、通用工具(common/*)、数据库驱动(dri/*
  • 业务文件(应返回):code/makecode.goapplication.gocontext.gosession.go、所有应用层代码

跳过基础设施,返回第一个业务文件。无论这个业务文件是应用层还是框架层。

具体修改

1. log/logger.go -- findCaller 简化

  • 删除 maxFrameworkDepth 常量(第 315 行)和 frameworkCount >= maxFrameworkDepth 提前退出(第 343-345 行)。i < 20 循环上限已足够保护。
  • 删除 applicationFile / applicationLine 跟踪及其回退逻辑(不再需要 application.go 特殊处理)。
  • 删除 frameworkCount 计数器(不再需要)。
  • 简化后的 findCaller
func findCaller(origFile string, origLine int) string {
    var lastInfraFile string
    var lastInfraLine int

    for i := 1; i < 20; i++ {
        _, file, line, ok := runtime.Caller(i)
        if !ok {
            break
        }
        shortFile := shortenPath(file)

        if isInfrastructureFile(shortFile) {
            lastInfraFile = shortFile
            lastInfraLine = line
            continue
        }

        return fmt.Sprintf("%s:%d", shortFile, line)
    }

    if lastInfraFile != "" {
        return fmt.Sprintf("%s:%d", lastInfraFile, lastInfraLine)
    }
    return fmt.Sprintf("%s:%d", shortenPath(origFile), origLine)
}

2. log/logger.go -- 重命名 + 收窄 isHoTimeFrameworkFile

重命名为 isInfrastructureFile,仅匹配日志/DB/缓存/通用工具等"管道"文件:

func isInfrastructureFile(file string) bool {
    // 第三方日志库
    if strings.HasPrefix(file, "zerolog/") || strings.HasPrefix(file, "zerolog@") {
        return true
    }
    if strings.HasPrefix(file, "logrus/") || strings.HasPrefix(file, "logrus@") {
        return true
    }
    if strings.HasPrefix(file, "runtime/") {
        return true
    }
    // HoTime 日志包
    if strings.HasPrefix(file, "log/") {
        return true
    }

    // DB 执行管线(db/ 下全部文件)
    infraPrefixes := []string{"db/", "cache/", "common/", "dri/"}
    for _, prefix := range infraPrefixes {
        if strings.HasPrefix(file, prefix) {
            return true
        }
    }

    // 兼容路径含 hotime 的场景(module 全路径未被 shortenPath 完全截断时)
    lowerFile := strings.ToLower(file)
    if strings.Contains(lowerFile, "hotime") {
        infraDirs := []string{"db/", "cache/", "common/", "log/", "dri/"}
        for _, dir := range infraDirs {
            if strings.Contains(file, dir) {
                return true
            }
        }
    }

    return false
}

关键变更:

  • "code/" 从所有列表中移除 -- code/makecode.go 不再被跳过
  • application.gocontext.gosession.go 等单文件匹配全部移除 -- 框架业务文件不再被跳过
  • frameworkCoreDirs 中的 "code/" 及其文件名(makecode.go、template.go、config.go)移除

修复后效果

调用链 显示结果
app/user.go -> db.GetMap -> logSQL app/user.go:XX
code/makecode.go -> db.GetList -> logSQL code/makecode.go:XX
application.go SetConfig -> db -> logSQL application.go:XX
context.go session -> db -> logSQL context.go:XX