
Go 标准库 log.Logger 默认记录的是封装函数所在文件的行号;通过 Logger.Output() 并指定 callDepth=2(或更高)可跳过包装层,精准定位到业务代码的调用位置。
go 标准库 `log.logger` 默认记录的是封装函数所在文件的行号;通过 `logger.output()` 并指定 `calldepth=2`(或更高)可跳过包装层,精准定位到业务代码的调用位置。
在 Go 中封装日志函数(如 logging.Debug())时,一个常见痛点是:日志输出的文件路径和行号总是指向日志封装文件(如 logging.go),而非实际调用日志函数的业务文件(如 main.go)。这是因为 log.Logger.Printf 内部固定使用 callDepth = 2 调用 Output(),而该深度是从 Printf 自身开始计算的——当你在 Debug() 函数中调用 Printf,调用栈变为:main → Debug → Printf → Output,此时 Printf 内部的 Output(2, ...) 实际捕获的是 Debug 函数的位置,而非 main。
正确做法是绕过 Printf,直接调用 Logger.Output() 并显式设置 callDepth。由于我们的调用链是 main → Debug → Output,要让 Output 定位到 main 行,需设 callDepth = 2(即跳过 Output 和 Debug 这两层):
// src/logging/logging.go
package logging
import (
"log"
"os"
)
var Logger *log.Logger
func init() {
// 注意:log.Llongfile 已足够,无需 log.LstdFlags 中的日期/时间(Output 会自动添加)
Logger = log.New(os.Stdout, "[Debug] ", log.Llongfile|log.LstdFlags)
}
func Debug(format string, v ...interface{}) {
// ✅ 正确:跳过 Debug 和 Output 两层,定位到调用 Debug 的位置
msg := format
if len(v) > 0 {
msg = fmt.Sprintf(format, v...)
}
Logger.Output(2, msg) // callDepth = 2
}
⚠️ 注意事项:
- log.Lshortfile / log.Llongfile 必须在初始化 Logger 时启用,否则 Output 不会注入文件信息;
- callDepth 是从 Output 调用点向上数的帧数:1 = Debug 函数自身,2 = main 函数调用处,3 则会越界(返回 ??:0);
- 若后续增加中间封装(如 Debug → wrapDebug → Output),需同步将 callDepth 增至 3;
- 避免在 Debug 中重复调用 Logger.SetPrefix() —— 前缀应在 init() 中一次性设定,频繁修改影响并发安全且无必要。
这样修改后,运行 main.go 将输出预期结果:
[Debug] 2015/12/10 22:20:23 /tmp/log/src/main/main.go:6: in main
精准反映真实调用上下文,大幅提升调试效率。











