智享技巧屋
第二套高阶模板 · 更大气的阅读体验

Go并发日志记录方法:网站高并发场景下的实用技巧

发布时间:2026-01-07 22:50:54 阅读:49 次

为什么并发日志网站开发中很重要

做网站的人应该都遇到过这种情况:系统一到高峰期,日志要么丢失,要么写入延迟,甚至拖垮整个服务。特别是用 Go 写的后端服务,天生支持高并发,成千上万个 goroutine 同时运行,如果日志处理不当,很容易变成性能瓶颈。

比如你上线了一个促销页面,瞬间涌入大量用户,每个请求都想记一条日志。如果直接用 fmt.Println 或者简单的文件写入,多个协程同时操作 I/O,轻则日志错乱,重则文件锁死、程序卡住。

避免并发写日志的常见坑

最直接但错误的方式是让每个 goroutine 直接往同一个文件写日志:

file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
file.WriteString("[INFO] 用户登录\n")

问题来了:多个协程同时调 file.WriteString,操作系统层面可能会出现写入交错,日志内容混在一起,比如两条日志拼成一条,或者部分内容被覆盖。

使用通道(channel)集中处理日志

一个更稳妥的做法是把日志写入操作收口,通过一个专门的“日志协程”来统一处理。其他协程只负责发送日志消息,不直接操作文件。

可以定义一个日志通道,用来接收所有日志条目:

var logChan = make(chan string, 1000)

func init() {
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
go func() {
for msg := range logChan {
file.WriteString(msg + "\n")
}
}()
}

业务代码里只需要发消息:

logChan <- "[WARN] 支付超时,订单ID: 12345"

这样所有写操作都由单个协程完成,避免了并发冲突,也提高了写入效率。

借助第三方库简化实现

虽然自己实现通道方案能理解原理,但在真实项目中,更推荐用成熟的日志库,比如 zaplogrus,它们内部已经处理好了并发安全的问题。

以 zap 为例,它不仅线程安全,还特别快:

logger, _ := zap.NewProduction()
defer logger.Sync() // 确保日志刷到磁盘

// 多个 goroutine 可以同时调用
logger.Info("用户注册成功",
zap.String("email", "user@example.com"),
zap.Int("userID", 1001))

zap 的设计就是为高并发准备的,结构化日志输出也很适合后续收集分析。

结合文件轮转避免日志过大

光写进一个文件也不行,时间一长文件动辄几个G,查看困难,还可能占满磁盘。可以用 lumberjack 配合 zap 实现自动切分:

writer := &lumberjack.Logger{
Filename: "/var/log/myapp.log",
MaxSize: 10, // 每 10MB 轮转一次
MaxBackups: 5, // 最多保留 5 个旧文件
MaxAge: 7, // 最多保存 7 天
Compress: true,
}

logger := zapcore.AddSync(writer)

这样一来,既保证了并发安全,又解决了运维层面的日志管理问题。

实际搭建网站后端时,别小看日志这一环。选对方法,系统才能稳得住,出问题也更容易排查。