
log.Println 默认输出到标准错误(stderr),而 fmt.Println 输出到标准输出(stdout);两者虽均无内部缓冲,但终端对 stdout 和 stderr 的刷新策略与重定向行为不同,导致显示顺序可能与代码执行顺序不一致。
go 中日志与格式化输出顺序错乱的原因解析:`log.println` 默认输出到标准错误(stderr),而 `fmt.println` 输出到标准输出(stdout);两者虽均无内部缓冲,但终端对 stdout 和 stderr 的刷新策略与重定向行为不同,导致显示顺序可能与代码执行顺序不一致。
在 Go 程序中,看似简单的两行输出语句:
log.Println("initial")
fmt.Println(1, 2)却可能产生「反直觉」的输出顺序(例如先打印 1 2,再显示带时间戳的日志行)。这并非因为 log.Println “更慢”,而是源于 I/O 目标流的本质差异:
- ✅
fmt.Println向os.Stdout(标准输出)写入,通常用于常规程序输出; - ✅
log.Println向os.Stderr(标准错误)写入,专为诊断、错误和日志设计。
虽然二者默认均采用行缓冲(line-buffered)或无缓冲(unbuffered)模式(Go 的 log 包底层使用 os.Stderr.Write,不额外加缓冲;fmt.Println 也直接调用 os.Stdout.Write),但关键在于:操作系统和终端对 stdout 与 stderr 的处理逻辑并不保证同步或时序一致。
? 为什么终端会“乱序”?
- 多数终端(如 macOS Terminal、Linux GNOME Terminal、Windows Terminal)会对 stdout 和 stderr 使用独立的缓冲队列与刷新时机;
- 某些环境(尤其 IDE 内置终端或 CI 日志收集器)甚至会对 stderr 进行延迟合并或异步高亮处理;
- 在
go run临时执行场景下,stderr 可能因日志前缀(如2016/12/30 14:22:58)触发稍晚的格式化与写入,加剧视觉错位。
✅ 验证方式(推荐)
运行以下命令可清晰复现差异:
go run main.go 2>&1 | cat -n
该命令将 stderr 重定向至 stdout 并通过 cat 统一流处理,此时输出顺序将严格按代码执行顺序呈现(即先 log 后 fmt)。
⚠️ 注意事项与最佳实践
- ❌ 不要依赖
stdout与stderr的交错输出顺序做逻辑判断; - ✅ 如需严格顺序(如调试时),统一使用同一输出流:
fmt.Println("initial") // 替代 log.Println fmt.Println(1, 2) - ✅ 若必须用
log且需同步效果,可显式刷新(虽非常规,但可行):log.SetOutput(os.Stdout) // 将 log 输出重定向至 stdout log.Println("initial") fmt.Println(1, 2) - ✅ 生产环境中,建议通过结构化日志库(如
zap或zerolog)配合统一日志管道,规避流竞争问题。
总之,这不是 Go 的 bug,而是 Unix I/O 模型的自然体现——stdout 与 stderr 是两条独立通道。理解这一点,就能从容应对各类日志混排场景。


















