You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:36:13