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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:27:18