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

Golang Zerolog:Caller能否输出被包装HTTP处理函数的文件及行号?

问题描述

我用通用Handle函数包装HTTP Handler来复用错误处理逻辑,但Zerolog的Caller输出总是显示包装函数(handler.go里的Handle方法)的文件和行号。我希望能打印返回错误的被包装函数(比如endpoints.go里userHandler返回错误的那行)的对应行号,不确定调用栈里有没有相关信息,请问能不能通过当前实现做到?还是必须改成直接在HTTP Handler里记录日志?


相关代码

handler.go

func (h *Handler) Handle(fn appHandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if e := fn(h.app, w, r); e != nil {
            if e.Context != nil {
                logger := e.Context.Caller().Logger() // 此处打印的是当前文件及行号
                logger.Error().Stack().Err(e.Error).Msg(e.LogMessage)
            } else {
                log.Error().Err(e.Error).Msg(e.LogMessage)
            }

            if e.HttpCode != 0 {
                http.Error(w, e.HttpMessage, e.HttpCode)
            } else {
                http.Error(w, e.HttpMessage, http.StatusInternalServerError)
            }
        }
    }
}

endpoints.go

func init() {
    router.Post("/user", handler.Handle(userHandler))
}

func userHandler(app *App, w http.ResponseWriter, r *http.Request) *AppError {
    userUid := r.Context().Value(authentication.UserUid).(string)
    logger := log.With().Str("userUid", userUid)

    // 业务逻辑处理
    user, err := database.FindUser(userUid)
    if err != nil {
        return &AppError{Error: err, Context: &logger} // 希望此处的文件及行号被打印
    }

    // 其他操作

    return nil // 成功返回
}

解决方案

不需要直接在HTTP Handler内记录日志,以下两种可靠方式可实现需求:

方法一:在返回AppError时预先注入Caller信息

问题根源是你在包装函数内调用e.Context.Caller(),导致Caller指向包装函数的位置。只需在被包装Handler返回错误时,提前给logger添加Caller信息即可:

  1. 修改userHandler的错误返回逻辑:
if err != nil {
    // 在此处调用Caller(),让Caller指向当前错误产生的行
    return &AppError{Error: err, Context: logger.Caller()}
}
  1. 修改Handle方法内的日志初始化代码,移除多余的Caller()调用:
if e.Context != nil {
    logger := e.Context.Logger()
    logger.Error().Stack().Err(e.Error).Msg(e.LogMessage)
}

此时Zerolog的Caller字段会显示userHandler中返回错误的行号,因为Caller()是在错误产生的位置执行的。

方法二:给AppError新增Caller字段,手动记录调用位置

若不想提前修改logger结构,可给AppError新增字段存储错误位置:

  1. 更新AppError结构体:
type AppError struct {
    Error       error
    Context     *zerolog.Context
    Caller      string // 新增:存储错误产生的文件及行号
    HttpCode    int
    HttpMessage string
    LogMessage  string
}
  1. 在返回错误时,用runtime.Caller()获取当前位置:
import (
    "fmt"
    "runtime"
)

// ...

if err != nil {
    _, file, line, ok := runtime.Caller(0)
    caller := ""
    if ok {
        caller = fmt.Sprintf("%s:%d", file, line)
    }
    return &AppError{Error: err, Context: &logger, Caller: caller}
}
  1. 在包装函数的日志中添加该Caller字段:
if e.Context != nil {
    logger := e.Context.Logger()
    logger.Error().Stack().Err(e.Error).Str("caller", e.Caller).Msg(e.LogMessage)
}

这种方式灵活性更高,可自定义Caller格式,且不依赖Zerolog的内置Caller机制。

不推荐的方案:用CallerSkip跳过栈帧

你也可以尝试在包装函数中使用CallerWithSkip(1)跳过当前栈帧:

logger := e.Context.CallerWithSkip(1).Logger()

但该方案依赖固定的栈帧层数,后续代码结构变更(如新增中间层)会导致Caller指向错误位置,可靠性远不如前两种方法。


内容的提问来源于stack exchange,提问作者Galen Howlett

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:27:10