Golang slog:程序运行中日志文件被删后如何重建?
问题描述
运行以下Go代码时,若删除日志文件log.txt,程序会继续运行但无报错,且日志不再写入文件。需要找到自动重建日志文件,或通过slog包正确处理该场景的方案(已知可通过syscall.Flock阻止文件删除,优先寻求重建方案):
package main import ( "log/slog" "os" "time" ) func main() { file, _ := os.Create("log.txt") opt := &slog.HandlerOptions{} handler := slog.NewTextHandler(file, opt) //redirecting log output to "log.txt" logger := slog.New(handler) for { logger.Info("info message") time.Sleep(time.Second) } }
解决方案
方案1:自定义可自动重建的Writer包装器
实现一个自定义io.Writer,每次写入前检查日志文件是否存在,若已被删除则重建文件,再执行写入操作。核心是将这个Writer传给slog.TextHandler。
package main import ( "log/slog" "os" "time" ) type recoverableFileWriter struct { path string file *os.File } func newRecoverableFileWriter(path string) (*recoverableFileWriter, error) { f, err := os.Create(path) if err != nil { return nil, err } return &recoverableFileWriter{path: path, file: f}, nil } func (w *recoverableFileWriter) Write(p []byte) (n int, err error) { // 检查文件是否存在 if _, statErr := os.Stat(w.path); os.IsNotExist(statErr) { // 文件已删除,关闭旧描述符并重建 w.file.Close() newFile, err := os.Create(w.path) if err != nil { return 0, err } w.file = newFile } // 执行写入 return w.file.Write(p) } func main() { writer, err := newRecoverableFileWriter("log.txt") if err != nil { panic(err) } defer writer.file.Close() opt := &slog.HandlerOptions{} handler := slog.NewTextHandler(writer, opt) logger := slog.New(handler) for { logger.Info("info message") time.Sleep(time.Second) } }
注:
os.Stat存在极小概率的竞态(检查后文件被删除),但多数场景下足够稳定;若需更严谨,可捕获写入错误后再触发重建。
方案2:后台定时检查+动态替换Handler
启动后台goroutine定期检查日志文件状态,发现文件被删除时,重建文件并替换slog的Handler(通过原子变量存储当前Logger,保证并发安全)。
package main import ( "log/slog" "os" "sync/atomic" "time" ) func main() { logPath := "log.txt" // 初始化日志Handler handler, err := createFileHandler(logPath) if err != nil { panic(err) } var currentLogger atomic.Value currentLogger.Store(slog.New(handler)) // 后台定时检查并重建 go func() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { if _, err := os.Stat(logPath); os.IsNotExist(err) { newHandler, err := createFileHandler(logPath) if err != nil { slog.Error("failed to recreate log handler", "err", err) continue } // 更新Logger并关闭旧文件 currentLogger.Store(slog.New(newHandler)) handler.Close() handler = newHandler } } }() // 日志循环 for { logger := currentLogger.Load().(*slog.Logger) logger.Info("info message") time.Sleep(time.Second) } } func createFileHandler(path string) (*slog.TextHandler, error) { file, err := os.Create(path) if err != nil { return nil, err } return slog.NewTextHandler(file, &slog.HandlerOptions{}), nil }
注:检查间隔内的日志可能丢失,适合对日志完整性要求不极高的场景;若需避免丢失,可结合方案1的写入时检查逻辑。
方案3:捕获写入错误触发重建
自定义slog.Handler包装原生TextHandler,在写入失败时判断是否为文件删除导致的错误,若则重建文件并重试写入,保证日志不丢失。
package main import ( "context" "log/slog" "os" "time" ) type recoverHandler struct { path string handler slog.Handler file *os.File } func newRecoverHandler(path string) (*recoverHandler, error) { file, err := os.Create(path) if err != nil { return nil, err } handler := slog.NewTextHandler(file, &slog.HandlerOptions{}) return &recoverHandler{path: path, handler: handler, file: file}, nil } func (h *recoverHandler) Handle(ctx context.Context, r slog.Record) error { err := h.handler.Handle(ctx, r) if err != nil { // 判断是否为文件不存在错误 if _, statErr := os.Stat(h.path); os.IsNotExist(statErr) { // 重建文件 h.file.Close() newFile, err := os.Create(h.path) if err != nil { return err } h.file = newFile h.handler = slog.NewTextHandler(newFile, &slog.HandlerOptions{}) // 重试写入 return h.handler.Handle(ctx, r) } return err } return nil } // 实现slog.Handler的其他接口方法,委托给底层Handler func (h *recoverHandler) Enabled(ctx context.Context, level slog.Level) bool { return h.handler.Enabled(ctx, level) } func (h *recoverHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return &recoverHandler{ path: h.path, handler: h.handler.WithAttrs(attrs), file: h.file, } } func (h *recoverHandler) WithGroup(name string) slog.Handler { return &recoverHandler{ path: h.path, handler: h.handler.WithGroup(name), file: h.file, } } func main() { handler, err := newRecoverHandler("log.txt") if err != nil { panic(err) } defer handler.file.Close() logger := slog.New(handler) for { logger.Info("info message") time.Sleep(time.Second) } }
通用注意事项
- 所有方案需妥善处理文件关闭逻辑,避免文件描述符泄漏。
- 需确保程序拥有创建日志文件的目录权限。
- 极端竞态场景(如重建后文件立即被删除)可通过多层错误处理覆盖。
内容的提问来源于stack exchange,提问作者Altfaa
相关产品推荐
相关产品推荐

