log.print/printf 不适合记录错误,因无时间戳、无换行、无级别区分、不打印调用栈、不展开嵌套错误、不绑定goroutine或位置信息,且并发性能低。

log.Print 和 log.Printf 为什么不适合记录错误
Go 标准库 log 包的 log.Print 和 log.Printf 默认不带时间戳、不自动加换行、也不区分日志级别,容易让错误信息混在普通输出里,排查时根本分不清哪条是错误。更重要的是,它们不会自动打印调用栈——而绝大多数真实错误需要知道「谁、在哪、什么时候」出的问题。
- 错误发生时,仅靠
log.Printf("failed: %v", err)只能拿到err.Error()字符串,丢失上下文 - 如果
err是自定义错误或嵌套错误(比如用fmt.Errorf("wrap: %w", inner)),默认打印不展开底层原因 - 没有内置机制把错误和 goroutine ID、文件名、行号绑定,线上出问题后无法快速定位
用 log.Println + runtime.Caller 拼出带位置的错误日志
想用标准库又不想引入第三方,可以手动补全关键信息。核心是用 runtime.Caller 获取调用点,再拼到日志里:
func logError(err error) {
_, file, line, _ := runtime.Caller(1)
log.Printf("[ERROR] %s:%d — %v", filepath.Base(file), line, err)
}
注意:这里用 Caller(1) 是跳过当前函数帧,拿到上层调用位置;filepath.Base(file) 避免打印冗长绝对路径。但这种方式每次都要手写,且不支持嵌套错误展开。
- 如果
err是fmt.Errorf("read failed: %w", io.EOF),上面代码仍只输出顶层字符串,看不到io.EOF - 所有日志都走
log.Printf,无法按级别过滤(比如开发时关掉 INFO,只留 ERROR) - 并发写日志时默认加锁,吞吐量低,高频错误场景可能成瓶颈
log.New 配合 LstdFlags 和 Lshortfile 能解决什么
标准库其实提供了更可控的方式:log.New 可以定制输出器,配合标志位开启基础元数据:
立即学习“go语言免费学习笔记(深入)”;
logger := log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lshortfile)
logger.Println("connection refused")
输出类似:[ERROR] 2024/05/20 14:22:33 main.go:42: connection refused。相比裸用 log.Printf,它自动加了时间、文件名和行号。
-
log.LstdFlags提供时间戳,log.Lshortfile替代log.Llongfile避免路径过长 - 但依然不识别
error类型——传入logger.Println(err)不会自动调用err.Unwrap()或打印栈 - 无法区分「业务错误」和「panic 级别错误」,所有日志都是同一种输出格式
真正实用的错误日志:用 errors.As / errors.Is + 第三方 logger(如 zerolog)
生产环境建议直接用支持结构化日志和错误展开的库。以 zerolog 为例,它能把错误对象完整序列化,包括嵌套链和栈帧:
import "github.com/rs/zerolog/log"
func handleRequest() {
if err := doSomething(); err != nil {
log.Error().Err(err).Str("action", "process_request").Send()
}
}
输出中会包含 "error":"read timeout" "error_chain":["read timeout","context deadline exceeded"] "error_stack":"..." 这类字段,ELK 或 Loki 可直接解析。
- 必须显式调用
.Err(err)方法,否则只会把err.String()当普通字符串处理 - 如果错误来自
net/http或database/sql,部分底层错误不实现Unwrap(),需手动包装(如fmt.Errorf("db query: %w", err))才能被正确展开 - 不要在
defer里无条件记录recover()错误——这会掩盖本该 panic 的致命问题,应只对明确预期的 recover 场景做日志
最常被忽略的一点:日志中的错误对象是否实现了 error 接口本身不重要,关键是它是否实现了 Unwrap() 或 StackTrace()(如 github.com/pkg/errors),否则任何结构化 logger 都无法还原错误链。










