直接用 log.Printf 记录任务日志易丢数据,因缓冲未刷新、goroutine 被抢占或主程序提前退出;并发写入还可能导致日志错乱。

为什么直接用 log.Printf 记录任务日志容易丢数据
Go 程序中启动 goroutine 执行异步任务时,如果只在任务函数里用 log.Printf 打印日志,进程退出前可能根本看不到输出——因为日志缓冲未刷新、goroutine 已被抢占或主 goroutine 提前结束。更严重的是,多个 goroutine 并发写同一 log.Logger 实例(默认非锁保护)可能导致日志行错乱或截断。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有任务日志必须绑定独立的
*log.Logger实例,且启用log.Lshortfile或log.Lmicroseconds辅助定位 - 避免在 goroutine 中直接调用全局
log.Printf;改用显式传入 logger 实例 - 若任务生命周期短于主程序,需确保 logger 的底层
io.Writer(如文件)已Close(),否则缓冲区内容丢失
如何给每个任务生成带唯一 ID 的结构化日志
单纯打时间戳不够,任务重试、并发执行时无法区分日志归属。推荐在任务启动时生成 trace ID,并作为字段注入 logger,而不是拼接进 message 字符串。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
uuid.NewString()(Go 1.20+)或github.com/google/uuid生成taskID - 用
log.New创建子 logger,配合log.SetPrefix或封装一个带字段的 wrapper(例如logger.With("task_id", taskID)) - 不推荐用
fmt.Sprintf拼接日志字符串:易出格式错误、难解析、无法统一加字段
示例(轻量封装):
type TaskLogger struct {
*log.Logger
taskID string
}
func (t *TaskLogger) Info(msg string) {
t.Printf("[task=%s] %s", t.taskID, msg)
}
任务失败时怎么确保错误堆栈完整记录
常见错误是只记录 err.Error(),丢失调用链和 goroutine 状态。尤其当任务 panic 后被 recover 捕获,仅打印 err 无法还原现场。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- panic 场景下,用
debug.PrintStack()或runtime/debug.Stack()获取完整堆栈,写入日志文件而非标准输出 - 非 panic 错误,优先用
fmt.Errorf("xxx: %w", err)包装,保留原始 error 链;日志中调用fmt.Sprintf("%+v", err)显示栈帧(需第三方库如github.com/pkg/errors或 Go 1.13+ 原生支持) - 避免在 defer 中记录错误日志却忘了检查 error 是否为 nil,导致空日志干扰排查
日志文件滚动与清理怎么做才不踩坑
用 os.OpenFile 直接创建日志文件后不做轮转,很快会撑爆磁盘。但很多轮转库(如 lumberjack)默认不保证原子写入,高并发任务下可能丢 log 行。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 选用
gopkg.in/natefinch/lumberjack.v2,它内部用sync.Mutex保护 write,兼容多 goroutine 安全 - 关键配置必须显式设置:
MaxSize(单位 MB)、MaxBackups、MaxAge(天),否则默认值极不实用 - 不要把日志文件路径写死成相对路径(如
"logs/task.log"),应通过 flag 或环境变量注入,方便容器化部署时挂载卷
典型初始化:
l := &lumberjack.Logger{
Filename: os.Getenv("LOG_PATH"),
MaxSize: 10,
MaxBackups: 5,
MaxAge: 7,
Compress: true,
}
logger := log.New(l, "", log.LstdFlags|log.Lshortfile)
任务日志不是“能打出来就行”,关键是让每条日志可追溯、可关联、可归档。最容易被忽略的是:任务结束前忘记 flush 日志 writer,以及在 defer 里 recover 后没把堆栈写进任务专属 logger 而是打到了全局 log。


















