Go多协程写日志必须序列化输出,推荐channel+单goroutine方案:日志项发至带缓冲channel,由唯一后台goroutine顺序写入,确保完整性与逻辑顺序;次选sync.Mutex+bufio.Writer(需小粒度锁+Flush);生产环境优先用Zap/Zerolog等成熟库。
Go 中多协程写日志时,不能直接共用一个 *os.File 或 io.Writer 并发写入,否则会出现内容错乱、覆盖、截断等问题。真正安全且能保持逻辑顺序(非绝对时间顺序)的关键,在于日志事件的序列化输出,而非依赖写入时机。下面从核心问题出发,给出实用、轻量、可落地的方案。
这是最常用也最稳妥的方式:所有日志调用都把日志条目(如结构体或字符串)发到一个有缓冲的 channel,由**唯一一个后台 goroutine 顺序接收并写入文件**。这样天然避免竞争,也保证了日志行的完整性与相对顺序(即谁先发、谁先写)。
for entry := range logChan { writeToFile(entry) }
logChan (注意加超时或非阻塞防止卡死)
select + default 实现丢弃或降级策略如果日志量不大(比如每秒几百条以内),且你希望减少 goroutine 调度开销,可用互斥锁保护带缓冲的 bufio.Writer。关键点是:锁粒度要小,且必须在每次写完后调用 Flush(),否则缓冲区内容可能滞留或跨日志行混粘。
var (mu sync.Mutex; writer *bufio.Writer)
mu.Lock(); writer.WriteString(...); writer.WriteRune('\n
'); writer.Flush(); mu.Unlock()
Flush() 可能返回 error,需检查并处理(如重试或记录错误)生产环境不建议手写日志系统。Zap 和 Zerolog 都原生支持多协程安全写入,并默认采用类似 channel 序列化的模型:
Logger 是并发安全的,底层用 ring buffer + 单 writer goroutinezerolog.New(os.Stderr).With().Timestamp().Logger() 快速启用WriteSyncer(如轮转文件、网络发送),你只需专注日志内容,不用操心同步DisableCaller() 和 DisableStacktrace() 可进一步减少竞争点多协程日志的本质矛盾是:顺序性、性能、可靠性三者不可兼得。需根据场景权衡:
sync.RWMutex + 内存 buffer + 定期刷盘,或使用 WAL 类设计log.SetOutput() 直接设为文件句柄并发写 —— log 包本身不保证写入原子性