Golang中如何修改自定义日志包装器以打印实际错误行号

Golang中如何修改自定义日志包装器以打印实际错误行号 使用标准库的日志包时,即使我编写一个包装器,也可以通过 log.Output 获取实际的行号。但我希望使用 logrus,而在 logrus 中似乎没有找到打印实际行号的方法。logrus 打印的是 INFO[0000]logging.go:39 myfolder/logging.Info(),而我想要它打印 INFO[0000]myfile.go:39 myfolder/myfile.myfunction()

这是我的自定义日志记录器代码,我希望它能打印错误发生的实际行号,而不是打印此文件的行号。

package logging

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

var (
	log *logrus.Logger
)

func init() {

	log = logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
	log.Println("hello world")
}

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

// Warn ...
func Warn(args ...interface{}) {
	log.Warn(args...)
}

// Error ...
func Error(args ...interface{}) {
	log.Error(args...)
}

// Fatal
func Fatal(args ...interface{}) {
	log.Fatal(args...)
}

更多关于Golang中如何修改自定义日志包装器以打印实际错误行号的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何修改自定义日志包装器以打印实际错误行号的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在 logrus 中获取实际调用位置的行号,需要调整调用栈的深度。你的代码目前打印的是 logging.go 的行号,因为 logrus 报告的是日志调用发生的位置(即在 logging.Info() 内部)。要获取实际调用日志函数的位置,你需要自定义一个包装器,手动设置调用深度。

以下是修改后的代码,通过 logrus.Entry 和自定义的调用栈深度来打印实际错误行号:

package logging

import (
	"fmt"
	"path"
	"runtime"

	"github.com/sirupsen/logrus"
)

var log *logrus.Logger

func init() {
	log = logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
}

// getCallerFrame 跳过包装器层,获取实际调用者的栈帧
func getCallerFrame() *runtime.Frame {
	// 跳过当前函数(getCallerFrame)和调用它的日志包装函数
	pc := make([]uintptr, 10)
	n := runtime.Callers(0, pc)
	if n == 0 {
		return nil
	}

	frames := runtime.CallersFrames(pc[:n])
	for i := 0; i < 3; i++ { // 跳过 getCallerFrame、日志包装函数和调用者
		frame, more := frames.Next()
		if !more {
			break
		}
		if i == 2 { // 第三个帧是实际调用日志的位置
			return &frame
		}
	}
	return nil
}

// withActualCaller 创建一个带有实际调用者信息的 logrus.Entry
func withActualCaller() *logrus.Entry {
	frame := getCallerFrame()
	if frame == nil {
		return log.WithField("caller", "unknown")
	}
	return log.WithFields(logrus.Fields{
		"caller": fmt.Sprintf("%s:%d %s", path.Base(frame.File), frame.Line, frame.Function),
	})
}

// Info 打印实际调用位置的信息日志
func Info(args ...interface{}) {
	withActualCaller().Info(args...)
}

// Warn 打印实际调用位置的警告日志
func Warn(args ...interface{}) {
	withActualCaller().Warn(args...)
}

// Error 打印实际调用位置的错误日志
func Error(args ...interface{}) {
	withActualCaller().Error(args...)
}

// Fatal 打印实际调用位置的致命错误日志并退出
func Fatal(args ...interface{}) {
	withActualCaller().Fatal(args...)
}

如果你希望保持 logrus 默认的调用者格式,但调整深度,可以直接修改日志记录器的调用栈跳过深度:

package logging

import (
	"fmt"
	"path"
	"runtime"

	"github.com/sirupsen/logrus"
)

var log *logrus.Logger

func init() {
	log = logrus.New()
	log.SetReportCaller(true)
	log.Formatter = &logrus.TextFormatter{
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			filename := path.Base(f.File)
			return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
		},
	}
	// 设置 logrus 跳过包装器层
	log.SetReportCaller(true)
	log.ReportCaller = true
}

// 自定义日志级别函数,通过 runtime.Caller 手动获取调用信息
func logWithCaller(level logrus.Level, args ...interface{}) {
	// 跳过两层:当前函数和调用它的公共函数(如 Info、Error 等)
	pc, file, line, ok := runtime.Caller(2)
	if !ok {
		file = "unknown"
		line = 0
	}

	funcName := "unknown"
	if pc != 0 {
		funcName = runtime.FuncForPC(pc).Name()
	}

	entry := log.WithFields(logrus.Fields{
		"custom_caller": fmt.Sprintf("%s:%d %s", path.Base(file), line, funcName),
	})

	switch level {
	case logrus.InfoLevel:
		entry.Info(args...)
	case logrus.WarnLevel:
		entry.Warn(args...)
	case logrus.ErrorLevel:
		entry.Error(args...)
	case logrus.FatalLevel:
		entry.Fatal(args...)
	}
}

// Info 打印信息日志
func Info(args ...interface{}) {
	logWithCaller(logrus.InfoLevel, args...)
}

// Warn 打印警告日志
func Warn(args ...interface{}) {
	logWithCaller(logrus.WarnLevel, args...)
}

// Error 打印错误日志
func Error(args ...interface{}) {
	logWithCaller(logrus.ErrorLevel, args...)
}

// Fatal 打印致命错误日志并退出
func Fatal(args ...interface{}) {
	logWithCaller(logrus.FatalLevel, args...)
}

这样,当你在其他文件中调用 logging.Info("message") 时,日志会显示实际调用位置的文件名、行号和函数名,而不是 logging.go 中的位置。

回到顶部