log.SetOutput 重定向到文件后日志不刷盘,因 *os.File 默认缓冲且 log 不自动 Flush;需用 os.OpenFile 显式打开并追加,或手动 file.Sync();log.Logger 并发安全但日志行序可能乱序。
默认 log.Logger 写入的是 os.Stderr,直接用 log.SetOutput 指向文件句柄时,内容可能滞留在缓冲区,尤其进程退出前看不到最新日志。根本原因是底层 *os.File 默认带缓冲,且 log 包不自动调用 Flush。
os.OpenFile 显式打开文件,传入 os.O_CREATE | os.O_WRONLY | os.O_APPEND
os.Stdout 或 os.Stderr 的副本——它们不可靠且不支持追加bufio.Writer 并手动 Flush(),但更简单做法是关闭缓冲:file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
log.SetOutput(file)(注意:os.File 在 Linux/macOS 下 write 系统调用本身不缓冲,但 Go 运行时对小写入可能合并;真正保险的做法是每条日志后 file.Sync(),仅在关键场景用)log.Logger 是并发安全的,内部用 mutex 保护输出逻辑,所以多个 goroutine 直接调用 log.Print 不会损坏文件内容。但要注意:
fmt.Fprintf(file, ...) 替代 log——那完全不加锁,必然错乱zap 或 zerolog,原生 log 不支持上下文注入原生 log 包通过 log.SetFlags 控制前缀,但仅支持固定选项,无法动态插入函数名或行号。常用组合是:
log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile → 输出类似 2025/05/22 14:23:11.123 main.go:27:
Lshortfile 开销略大(每次调用需 runtime.Caller),高吞吐场景慎用func Logf(format string, v ...interface{}) {
_, file, line, _ := runtime.Caller(1)
filename := filepath.Base(file)
log.Printf("[%s:%d] "+format, append([]interface{}{filename, line}, v...)...)
}log.Fatal 等价于 log.Print() + os.Exit(1),而 os.Exit 不触发 defer、不刷新文件缓冲、不执行 runtime.GC。如果日志刚写入内核缓冲还没落盘,就永远消失了。
log.Fatal,改用 log.Println(...); os.Exit(1),并在 exit 前加 file.Sync()
Fatal 方法,确保 flush
log.Fatal 无妨;生产服务中关键错误日志,务必先 log.Printf,再显式 file.Sync(),最后 os.Exit
Close 导致 fd 耗尽,或没 Sync 导致日志“消失”。