

新闻资讯
技术学院Go标准log包默认缓冲写入,需显式Sync()或用自动刷新Writer;轮转推荐rotatelogs;并发写需线程安全io.Writer;JSON日志应换用zerolog/zap;须预检路径权限与磁盘空间。
log 包写入文件时,为什么日志没刷到磁盘?Go 标准库的 log 默认使用带缓冲的 io.Writer,如果直接传入 os.File 而不显式调用 Sync(),程序崩溃或提前退出时容易丢失最后几条日志。
os.O_CREATE | os.O_WRONLY | os.O_APPEND 打开文件,避免覆盖历史日志file.Sync()(尤其在关键错误日志后)Writer,比如用 bufio.NewWriterSize(file, 4096) 并在每次 Write() 后 Flush()
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
logger := log.New(file, "[INFO] ", log.LstdFlags|log.Lshortfile)
// 记录后立即刷盘
logger.Println("startup completed")
file.Sync()
fsnotify 和 rotatelogs 怎么选?标准 log 包不支持自动切分,必须借助第三方。常见方案有两个:自己监听 SIGHUP 或文件变更,或用成熟轮转库。
lestrrat-go/file-rotatelogs 是目前最稳定的选项,支持按时间/大小切分、保留份数、压缩归档fsnotify 适合已有信号处理机制的项目(如监听 SIGHUP 后手动 Reopen()),但需自行管理文件句柄和并发安全too many open files 错误rl, _ := rotatelogs.New(
"logs/app.%Y%m%d.log",
rotatelogs.WithMaxAge(7*24*time.Hour),
rotatelogs.WithRotationCount(30),
)
logger := log.New(rl, "", log.LstdFlags)
log.Logger 本身不是并发安全的——它的 Output() 方法内部不加锁,多个 goroutine 直接调用会竞争写入缓冲区。
sync.Mutex 包一层 Write(),但高并发下会成为瓶颈log.SetOutput() 替换为线程安全的 io.Writer,例如 lumberjack.Logger 或自定义带 channel 的 writerfmt.Sprintf),应让 log.Printf 自己格式化,减少临时内存分配var mu sync.Mutex
logger := log.New(os.Stdout, "", log.LstdFlags)
// 不要这样:
go func() { mu.Lock(); defer mu.Unlock(); logger.Println("msg") }()
// 更好:用 lumberjack 或直接设为 sync.Once + buffered channel writer
log 包不支持结构化,怎么办?标准 log 只能输出纯文本,JSON 字段必须手动构造,且无法统一控制字段名(如 level、timestamp)。硬编码易出错,也不利于后续解析。
立即学习“go语言免费学习笔记(深入)”;
zerolog 或 zap 替代——它们原生支持结构化日志、字段类型校验、采样和 hookJSONLogger 类型,实现 io.Writer 接口,在 Write() 中解析每行、补全 J
SON 字段再写入time.Now().UTC().Format(time.RFC3339Nano) 比 log.Lmicroseconds 更适配 ESlogger := zerolog.New(os.Stderr).With().Timestamp().Logger()
logger.Info().Str("component", "api").Int("status", 200).Msg("request completed")
日志路径权限、磁盘满、时区配置这些看似外围的问题,往往比逻辑错误更难定位。写入前先 os.Stat() 检查目录可写性,轮转时用 syscall.Getpid() 做文件名防冲突,比事后排查快得多。