Golang如何基于logrus实现日志暂存、清空与出错时全量打印
实现方案
核心逻辑为自定义logrus Hook拦截全量日志,按循环周期做内存缓冲,无需修改各业务模块已有的日志调用代码,根据单轮循环的执行结果决定是丢弃缓冲还是输出全量日志后退出。
第一步:实现周期缓冲Logrus Hook
该Hook负责拦截所有级别的日志写入内存,不直接输出,同时提供缓冲重置、错误时全量输出的方法,内置互斥锁保证并发安全。
package main import ( "bytes" "fmt" "io" "os" "sync" "github.com/sirupsen/logrus" ) type CycleBufferHook struct { buf *bytes.Buffer formatter logrus.Formatter output io.Writer mu sync.Mutex } func NewCycleBufferHook(formatter logrus.Formatter, output io.Writer) *CycleBufferHook { return &CycleBufferHook{ buf: bytes.NewBuffer(nil), formatter: formatter, output: output, } } // Levels 拦截所有级别的日志 func (h *CycleBufferHook) Levels() []logrus.Level { return logrus.AllLevels } // Fire 日志触发时写入内存缓冲,不直接输出 func (h *CycleBufferHook) Fire(entry *logrus.Entry) error { h.mu.Lock() defer h.mu.Unlock() logLine, err := h.formatter.Format(entry) if err != nil { return err } _, err = h.buf.Write(logLine) return err } // Reset 单轮循环开始时调用,清空上一轮缓冲 func (h *CycleBufferHook) Reset() { h.mu.Lock() defer h.mu.Unlock() h.buf.Reset() } // FlushOnError 循环出错时调用,全量输出缓冲内的日志 func (h *CycleBufferHook) FlushOnError() error { h.mu.Lock() defer h.mu.Unlock() _, err := h.buf.WriteTo(h.output) return err }
第二步:初始化全局Logrus配置
关键配置为将Logrus默认输出设置为丢弃,所有日志仅通过自定义Hook进入缓冲,避免成功循环的日志提前输出。
func initLog() *CycleBufferHook { // 保持原有业务的日志格式配置,可替换为JSONFormatter等自定义格式 formatter := &logrus.TextFormatter{ FullTimestamp: true, TimestampFormat: "2006-01-02 15:04:05", } // 初始化缓冲Hook,输出目标和原有日志配置保持一致,默认输出到标准错误 hook := NewCycleBufferHook(formatter, os.Stderr) // 全局Logrus配置 logrus.SetFormatter(formatter) logrus.SetOutput(io.Discard) // 丢弃默认直接输出的日志 logrus.SetLevel(logrus.TraceLevel) // 放开所有日志级别,避免缓冲时丢日志 logrus.AddHook(hook) return hook }
第三步:改造循环执行逻辑
每轮循环严格执行「重置缓冲→执行业务→结果处理」流程,增加panic捕获,避免异常崩溃时未触发日志输出。
func main() { bufHook := initLog() for { // 新一轮循环开始,清空上一轮缓冲 bufHook.Reset() // 闭包包裹单轮业务逻辑,统一捕获错误与panic runErr := func() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic recovered: %v", r) } }() // 此处写入原有单轮循环的业务逻辑,所有模块调用logrus生成的日志都会自动进入缓冲 // 示例: // logrus.Info("任务启动") // if err := step1.Run(); err != nil { // return fmt.Errorf("步骤1执行失败: %w", err) // } // logrus.Info("步骤1执行完成") // if err := step2.Run(); err != nil { // return fmt.Errorf("步骤2执行失败: %w", err) // } // logrus.Info("步骤2执行完成") return nil }() if runErr != nil { // 记录最终错误到缓冲后,全量输出所有日志 logrus.WithError(runErr).Error("本轮循环执行异常,全量执行日志如下:") if err := bufHook.FlushOnError(); err != nil { fmt.Fprintf(os.Stderr, "输出缓冲日志失败: %v\n", err) } os.Exit(1) } // 循环执行成功,无需额外操作,直接进入下一轮即可 } }
注意事项
- 若单轮循环内使用多协程执行业务,Hook内置的互斥锁可保证日志写入不混乱,但需等本轮所有协程执行完成后再判断执行结果,禁止提前重置缓冲或输出日志。
- 若单轮循环日志量极大,担心内存占用过高,可将
bytes.Buffer替换为临时文件作为缓冲载体,核心逻辑无需改动。 - 若原有日志体系配置了其他Hook(如错误上报、日志审计类Hook),可将对应触发逻辑移至
FlushOnError方法中,仅在循环出错时触发,避免成功循环产生额外操作。
内容的提问来源于stack exchange,提问作者user1940163
相关产品推荐
相关产品推荐

