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

配置GIN+zerolog:错误日志写入文件,其余日志输出至标准输出

Gin + Zerolog 实现按级别分流日志(Error写文件,其他输出到Stdout)

你的代码问题出在中间件的判断逻辑上:l.GetLevel()获取的是Logger实例的全局级别(你设置的是InfoLevel),而不是当前日志调用的级别(比如调用Error()时的ErrorLevel)。所以这个if条件永远不会成立,所有日志都只会输出到stdout,达不到Error级日志写文件的效果。

下面给出两种可行的解决方案:

方案一:使用Zerolog Hook实现级别分流(推荐)

Hook是Zerolog提供的扩展机制,可以在日志事件触发时自定义处理逻辑,我们可以用它根据级别分流输出。

1. 定义LevelSplitHook

import (
    "io"
    "github.com/rs/zerolog"
)

type LevelSplitHook struct {
    stdoutWriter io.Writer
    fileWriter   io.Writer
}

// Run 实现zerolog.Hook接口,处理日志事件
func (h *LevelSplitHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
    // 根据级别选择输出目标
    targetWriter := h.stdoutWriter
    if level == zerolog.ErrorLevel {
        targetWriter = h.fileWriter
    }

    // 基于目标writer创建新的日志事件,保留原事件的字段
    newLogger := zerolog.New(targetWriter).With().
        Timestamp().
        Caller().
        Logger()
    newEvent := newLogger.Event(level)
    // 复制原事件的所有字段
    e.Struct().MarshalZerologObject(newEvent)
    // 写入日志消息
    newEvent.Msg(msg)

    // 丢弃原事件,避免重复输出
    e.Discard()
}

2. 在Gin中间件中配置Hook

func main() {
    zerolog.TimeFieldFormat = "02-01-2006 15:04:05"
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
    zerolog.SetGlobalLevel(zerolog.InfoLevel)

    // 打开错误日志文件,注意处理文件打开错误
    errorFile, err := os.OpenFile("error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer errorFile.Close()

    // 初始化分流Hook
    splitHook := &LevelSplitHook{
        stdoutWriter: os.Stdout,
        fileWriter:   errorFile,
    }

    r := gin.Default()
    r.Use(logger.SetLogger(
        logger.WithLogger(func(ctx *gin.Context, l zerolog.Logger) zerolog.Logger {
            // 给logger添加Hook,原输出设置为Discard(由Hook处理实际输出)
            return l.Hook(splitHook).With().Caller().Logger()
        }),
    ))

    // 你的路由配置...
}

3. 路由函数中正常调用日志

func GetTasks(ctx *gin.Context, db *sql.DB, log *zerolog.Logger) {
    _, err := db.Prepare("SELECT * FROM tasks ORDER BY id ASC")
    log.Error().Err(errors.New("halo....")).Msg("数据库预处理错误")
    // 这条日志会写入error.log

    log.Info().Msg("获取任务列表请求")
    // 这条日志会输出到stdout
}

方案二:自定义Writer实现级别分流

通过自定义io.Writer,解析日志的JSON内容判断级别,再决定输出目标。这种方式更简单,但依赖Zerolog的JSON格式。

1. 定义LevelSplitWriter

import (
    "io"
    "encoding/json"
)

type LevelSplitWriter struct {
    stdout io.Writer
    file   io.Writer
}

func (w *LevelSplitWriter) Write(p []byte) (n int, err error) {
    // 解析日志JSON,提取level字段
    var logData map[string]interface{}
    if err := json.Unmarshal(p, &logData); err == nil {
        if level, ok := logData["level"].(string); ok && level == "error" {
            return w.file.Write(p)
        }
    }
    // 默认输出到stdout
    return w.stdout.Write(p)
}

2. 在Gin中间件中配置Writer

func main() {
    zerolog.TimeFieldFormat = "02-01-2006 15:04:05"
    zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
    zerolog.SetGlobalLevel(zerolog.InfoLevel)

    errorFile, err := os.OpenFile("error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer errorFile.Close()

    splitWriter := &LevelSplitWriter{
        stdout: os.Stdout,
        file:   errorFile,
    }

    r := gin.Default()
    r.Use(logger.SetLogger(
        logger.WithLogger(func(ctx *gin.Context, l zerolog.Logger) zerolog.Logger {
            return l.Output(splitWriter).With().Caller().Logger()
        }),
    ))

    // 你的路由配置...
}

内容的提问来源于stack exchange,提问作者Lutfi Khoirudin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:43:17