Go标准库log包并发安全但输出可能交错;自定义io.Writer需自行保证线程安全;高并发场景推荐zap/zerolog等结构化日志库,或采用带panic防护与优雅退出的日志队列。

Go 标准库 log 包本身是并发安全的
只要用的是 log.New 创建的 logger(包括 log.Default() 或 log.Printf 等顶层函数),底层已通过 sync.Mutex 保证写操作串行化。多个 goroutine 同时调用 logger.Println 不会崩溃或丢日志,但——
- 日志行之间可能交错(比如两行内容混在同一行输出)
- 输出目标是
os.Stdout/os.Stderr时,终端显示可能乱序(因底层 write 系统调用非原子) - 若自定义
io.Writer(如写文件、网络、缓冲区),需确保该 writer 自身并发安全
自己封装 os.File 写日志必须加锁
直接用 file.Write 多 goroutine 并发写同一个 *os.File 是不安全的:Go 的 os.File.Write 虽内部有部分同步,但不保证多协程调用时 offset 一致、无覆盖、无截断。常见表现是日志丢失、内容错位、甚至文件末尾出现 \0 字节。
正确做法是用 sync.Mutex 或 sync.RWMutex 包裹写入逻辑:
var mu sync.Mutex
func safeWrite(file *os.File, b []byte) {
mu.Lock()
file.Write(b)
mu.Unlock()
}- 不要依赖
os.File的“看起来能跑”——它不是为高并发写设计的 - 若日志量大,频繁锁竞争会成为瓶颈;此时应考虑日志队列 + 单 writer goroutine 模式
-
os.OpenFile(..., os.O_APPEND)可缓解部分问题,但不能替代锁(POSIX append 模式只保证单次 write 原子,不保证多 goroutine 多次 write 的顺序)
生产环境推荐用结构化日志库(如 zap 或 zerolog)
它们默认支持高并发,且性能远超标准库:zap 的 Logger 实例是并发安全的,内部用无锁环形缓冲 + 单消费者 goroutine 刷盘;zerolog 默认使用 sync.Pool 复用 JSON buffer,避免 GC 压力。
立即学习“go语言免费学习笔记(深入)”;
-
zap.NewProduction()返回的 logger 可直接在任意 goroutine 中调用Info()、Error() - 避免在日志中做昂贵计算(如拼接字符串、调用
time.Now().String()),zap提供Any()和延迟求值字段(zap.Stringer) - 若需滚动文件,搭配
lumberjack.Logger(注意:它本身不是并发安全的,必须包在zap的WriteSyncer中,由 zap 统一控制写入)
自研日志队列要注意 channel 容量和 panic 传播
用 chan string 或 chan *LogEntry 收集日志、后台 goroutine 消费,看似简单,但容易踩坑:
- channel 无缓冲或容量过小 → 日志 goroutine 阻塞,拖慢主业务
- 消费 goroutine panic(如写磁盘失败未 recover)→ 日志彻底停摆,且无提示
- 没设
context.Context控制生命周期 → 程序退出时日志被丢弃
最小可用队列示例(带 panic 捕获与 graceful shutdown):
type LogQueue struct {
ch chan string
done chan struct{}
}
func (q *LogQueue) Start(w io.Writer) {
go func() {
defer func() {
if r := recover(); r != nil {
// 至少打到 stderr,避免完全静默
fmt.Fprintln(os.Stderr, "log consumer panicked:", r)
}
}()
for {
select {
case line := <-q.ch:
w.Write([]byte(line + "\n"))
case <-q.done:
return
}
}
}()
}
func (q *LogQueue) Write(s string) {
select {
case q.ch <- s:
default:
// 丢弃 or fallback to stderr —— 根据业务容忍度选
fmt.Fprintln(os.Stderr, "[LOG-DROP]", s)
}
}
真正难的不是写队列,而是决定什么时候丢日志、丢哪些、要不要落盘备份——这些都得贴着业务 SLA 来定。










