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

封装logrus.Logger函数时如何保留调用处行号前缀?

这个问题我之前也碰到过!原因很简单——你现在启用的log.SetReportCaller(true),只会追踪到直接调用logrus原生方法的那一行,也就是你封装的Info()函数里的log.Info(args...)这行代码,所以日志自然会显示logging.go的行号和封装函数名。

要让日志显示真正调用logging.Info()的业务代码位置,我们需要跳过封装函数这一层调用栈,获取上层的调用者信息。这里有两种可靠的实现方式:

方法一:手动抓取调用者信息(推荐,不依赖logrus内部实现)

这种方法我们不用logrus自带的调用者追踪,而是在封装函数里自己获取业务代码的调用位置,再把这些信息作为自定义字段加到日志里。

修改你的代码如下:

package logging

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"path"
	"runtime"
	"strings"
)

var log *logrus.Logger

func init() {
	log = logrus.New()
	// 关闭logrus自带的caller追踪,我们自己处理
	log.Formatter = &logrus.TextFormatter{
		// 自定义日志格式,把调用者信息放在前面
		Format: func(entry *logrus.Entry) ([]byte, error) {
			// 取出我们提前加好的调用者字段
			caller := entry.Data["caller"].(string)
			funcName := entry.Data["func"].(string)
			// 仿照logrus默认格式拼接日志行
			logLine := fmt.Sprintf(
				"%s[%s]%s %s %s\n",
				strings.ToUpper(entry.Level.String()),
				entry.Time.Format("0000"),
				caller,
				funcName,
				entry.Message,
			)
			return []byte(logLine), nil
		},
	}
}

// 工具函数:跳过封装层,获取真正的调用者帧信息
func getRealCallerFrame() *runtime.Frame {
	// Skip参数说明:
	// 0: runtime.Callers 本身
	// 1: getRealCallerFrame 这个函数
	// 2: logging.Info/Warn 等封装函数
	// 3: 真正调用logging.Info的业务代码
	pcBuffer := make([]uintptr, 10)
	frameCount := runtime.Callers(3, pcBuffer)
	if frameCount == 0 {
		return nil
	}
	frames := runtime.CallersFrames(pcBuffer[:frameCount])
	frame, _ := frames.Next()
	return frame
}

func Info(args ...interface{}) {
	callerFrame := getRealCallerFrame()
	var callerStr, funcStr string
	if callerFrame != nil {
		// 只保留文件名(去掉全路径)和行号
		callerStr = fmt.Sprintf("%s:%d", path.Base(callerFrame.File), callerFrame.Line)
		// 显示完整函数名
		funcStr = fmt.Sprintf("%s()", callerFrame.Function)
	} else {
		// 兜底处理,防止获取失败
		callerStr = "unknown:0"
		funcStr = "unknown()"
	}
	// 添加调用者字段并输出日志
	entry := log.WithFields(logrus.Fields{
		"caller": callerStr,
		"func":   funcStr,
	})
	entry.Info(args...)
}

方法二:修改logrus的CallerPrettyfier跳过封装层

如果你更倾向于用logrus自带的ReportCaller功能,可以在CallerPrettyfier里判断当前帧是不是封装函数,如果是,就往上找一层调用栈:

package logging

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"path"
	"runtime"
	"strings"
)

var log *logrus.Logger

func init() {
	log = logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			// 替换成你实际的包路径,比如"myfolder/logging."
			if strings.HasPrefix(f.Function, "your-module-path/logging.") {
				// 跳过当前封装函数,获取上层调用者
				pcBuffer := make([]uintptr, 2)
				// Skip参数需要根据调用栈调整,这里是4:
				// 0: runtime.Callers, 1: 当前的CallerPrettyfier, 2: logrus内部的caller逻辑, 3: 封装函数, 4: 业务代码调用者
				frameCount := runtime.Callers(4, pcBuffer)
				if frameCount > 0 {
					frames := runtime.CallersFrames(pcBuffer[:frameCount])
					if realFrame, ok := frames.Next(); ok {
						filename := path.Base(realFrame.File)
						return fmt.Sprintf("%s()", realFrame.Function), fmt.Sprintf("%s:%d", filename, realFrame.Line)
					}
				}
				// 获取失败就用当前帧兜底
			}
			// 非封装函数的情况,正常处理
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
}

func Info(args ...interface{}) {
	log.Info(args...)
}

效果验证

当你在业务代码里调用logging.Info("test log")时,日志就会输出你想要的格式:
INFO[0000]myfile.go:39 myfolder/myfile.myfunction() test log

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 22:57:28