refactor(logging): 迁移日志记录到 Zerolog 并优化错误处理
- 将日志记录库从 Logrus 替换为 Zerolog,提升性能和灵活性 - 更新各个模块的日志记录方式,确保一致性 - 优化错误处理逻辑,确保在发生错误时能够正确记录并传递错误信息 - 移除不再使用的错误处理字段,简化代码结构 - 更新相关文档以反映新的日志记录和错误处理机制
This commit is contained in:
+429
@@ -0,0 +1,429 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ErrorRecord 错误历史记录条目
|
||||
type ErrorRecord struct {
|
||||
Err error
|
||||
Msg string
|
||||
Time time.Time
|
||||
Caller string
|
||||
}
|
||||
|
||||
// Logger 日志核心结构体,封装 zerolog
|
||||
type Logger struct {
|
||||
zl zerolog.Logger
|
||||
logLevel int
|
||||
errors []ErrorRecord
|
||||
errIdx int // 环形缓冲写入位置
|
||||
errCount int // 实际写入总数(用于判断缓冲是否满)
|
||||
errorsMu sync.RWMutex
|
||||
maxErrors int
|
||||
}
|
||||
|
||||
// NewLogger 创建日志实例
|
||||
// logLevel: 0=仅 error,>=1=全部
|
||||
// logFile: 文件路径模板(如 "logs/20060102.txt"),空则不写文件
|
||||
// maxErrors: 错误历史最大条数,0 则不记录
|
||||
func NewLogger(logLevel int, logFile string, maxErrors int) *Logger {
|
||||
zerolog.CallerMarshalFunc = callerMarshalFunc
|
||||
zerolog.TimeFieldFormat = "2006-01-02 15:04:05"
|
||||
|
||||
var level zerolog.Level
|
||||
if logLevel == 0 {
|
||||
level = zerolog.ErrorLevel
|
||||
} else {
|
||||
level = zerolog.DebugLevel
|
||||
}
|
||||
|
||||
consoleWriter := zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
TimeFormat: "2006-01-02 15:04:05",
|
||||
NoColor: false,
|
||||
FormatLevel: formatLevelColor,
|
||||
FormatCaller: func(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("[%s]", i)
|
||||
},
|
||||
}
|
||||
|
||||
var writers []io.Writer
|
||||
writers = append(writers, consoleWriter)
|
||||
|
||||
if logFile != "" {
|
||||
fw := &TemplateFileWriter{pathTemplate: logFile}
|
||||
writers = append(writers, fw)
|
||||
}
|
||||
|
||||
multi := zerolog.MultiLevelWriter(writers...)
|
||||
zl := zerolog.New(multi).
|
||||
Level(level).
|
||||
With().
|
||||
Timestamp().
|
||||
Caller().
|
||||
Logger()
|
||||
|
||||
l := &Logger{
|
||||
zl: zl,
|
||||
logLevel: logLevel,
|
||||
maxErrors: maxErrors,
|
||||
}
|
||||
if maxErrors > 0 {
|
||||
l.errors = make([]ErrorRecord, maxErrors)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// NewTestLogger 创建用于测试的静默 Logger(输出到 io.Discard)
|
||||
func NewTestLogger() *Logger {
|
||||
zl := zerolog.New(io.Discard).Level(zerolog.Disabled)
|
||||
return &Logger{zl: zl, logLevel: 0}
|
||||
}
|
||||
|
||||
// SetOutput 设置日志输出(用于测试等场景)
|
||||
func (l *Logger) SetOutput(w io.Writer) {
|
||||
if w == io.Discard {
|
||||
l.zl = zerolog.New(w).Level(zerolog.Disabled)
|
||||
return
|
||||
}
|
||||
consoleWriter := zerolog.ConsoleWriter{
|
||||
Out: w,
|
||||
TimeFormat: "2006-01-02 15:04:05",
|
||||
NoColor: true,
|
||||
FormatLevel: formatLevelPlain,
|
||||
FormatCaller: func(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("[%s]", i)
|
||||
},
|
||||
}
|
||||
l.zl = zerolog.New(consoleWriter).
|
||||
With().
|
||||
Timestamp().
|
||||
Caller().
|
||||
Logger()
|
||||
}
|
||||
|
||||
// GetLevel 获取当前日志等级
|
||||
func (l *Logger) GetLevel() int {
|
||||
return l.logLevel
|
||||
}
|
||||
|
||||
// --- 链式调用 API ---
|
||||
// 以下方法兼容两种调用风格:
|
||||
// 无参数:返回 *zerolog.Event 用于链式调用,如 l.Error().Str("k","v").Msg("...")
|
||||
// 有参数:直接拼接并打印日志(logrus 兼容),返回 no-op Event
|
||||
|
||||
func (l *Logger) Debug(args ...interface{}) *zerolog.Event {
|
||||
if len(args) > 0 {
|
||||
l.zl.Debug().Msg(fmt.Sprint(args...))
|
||||
nop := zerolog.Nop()
|
||||
return nop.Debug()
|
||||
}
|
||||
return l.zl.Debug()
|
||||
}
|
||||
|
||||
func (l *Logger) Info(args ...interface{}) *zerolog.Event {
|
||||
if len(args) > 0 {
|
||||
l.zl.Info().Msg(fmt.Sprint(args...))
|
||||
nop := zerolog.Nop()
|
||||
return nop.Info()
|
||||
}
|
||||
return l.zl.Info()
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(args ...interface{}) *zerolog.Event {
|
||||
if len(args) > 0 {
|
||||
l.zl.Warn().Msg(fmt.Sprint(args...))
|
||||
nop := zerolog.Nop()
|
||||
return nop.Warn()
|
||||
}
|
||||
return l.zl.Warn()
|
||||
}
|
||||
|
||||
func (l *Logger) Error(args ...interface{}) *zerolog.Event {
|
||||
if len(args) > 0 {
|
||||
msg := fmt.Sprint(args...)
|
||||
if l.maxErrors > 0 {
|
||||
l.recordError(msg, nil)
|
||||
}
|
||||
l.zl.Error().Msg(msg)
|
||||
nop := zerolog.Nop()
|
||||
return nop.Error()
|
||||
}
|
||||
if l.maxErrors > 0 {
|
||||
l.recordError("", nil)
|
||||
}
|
||||
return l.zl.Error()
|
||||
}
|
||||
|
||||
// RecordError 手动记录一条错误到历史(用于需要指定详情的场景)
|
||||
func (l *Logger) RecordError(err error, msg string, caller string) {
|
||||
if l.maxErrors <= 0 {
|
||||
return
|
||||
}
|
||||
l.errorsMu.Lock()
|
||||
defer l.errorsMu.Unlock()
|
||||
l.errors[l.errIdx] = ErrorRecord{
|
||||
Err: err,
|
||||
Msg: msg,
|
||||
Time: time.Now(),
|
||||
Caller: caller,
|
||||
}
|
||||
l.errIdx = (l.errIdx + 1) % l.maxErrors
|
||||
l.errCount++
|
||||
}
|
||||
|
||||
func (l *Logger) recordError(msg string, err error) {
|
||||
_, file, line, ok := runtime.Caller(2)
|
||||
caller := ""
|
||||
if ok {
|
||||
caller = formatCaller(file, line)
|
||||
}
|
||||
l.RecordError(err, msg, caller)
|
||||
}
|
||||
|
||||
// GetRecentErrors 获取最近 N 条错误(不传则返回全部已存储的)
|
||||
func (l *Logger) GetRecentErrors(n ...int) []ErrorRecord {
|
||||
if l.maxErrors <= 0 {
|
||||
return nil
|
||||
}
|
||||
l.errorsMu.RLock()
|
||||
defer l.errorsMu.RUnlock()
|
||||
|
||||
total := l.errCount
|
||||
if total > l.maxErrors {
|
||||
total = l.maxErrors
|
||||
}
|
||||
if total == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
want := total
|
||||
if len(n) > 0 && n[0] > 0 && n[0] < want {
|
||||
want = n[0]
|
||||
}
|
||||
|
||||
result := make([]ErrorRecord, 0, want)
|
||||
// 从最新的往前读
|
||||
for i := 0; i < want; i++ {
|
||||
idx := (l.errIdx - 1 - i + l.maxErrors) % l.maxErrors
|
||||
if l.errors[idx].Time.IsZero() {
|
||||
break
|
||||
}
|
||||
result = append(result, l.errors[idx])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// --- 格式化快捷方法 ---
|
||||
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
l.zl.Debug().Msgf(format, v...)
|
||||
}
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
l.zl.Info().Msgf(format, v...)
|
||||
}
|
||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||
l.zl.Warn().Msgf(format, v...)
|
||||
}
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
if l.maxErrors > 0 {
|
||||
l.recordError(fmt.Sprintf(format, v...), nil)
|
||||
}
|
||||
l.zl.Error().Msgf(format, v...)
|
||||
}
|
||||
|
||||
// --- 日志级别颜色 ---
|
||||
|
||||
func formatLevelColor(i interface{}) string {
|
||||
level := strings.ToUpper(fmt.Sprintf("%s", i))
|
||||
switch level {
|
||||
case "DEBUG":
|
||||
return fmt.Sprintf("\x1b[36m|%s|\x1b[0m", level) // cyan
|
||||
case "INFO":
|
||||
return fmt.Sprintf("\x1b[32m|%s|\x1b[0m", level) // green
|
||||
case "WARN":
|
||||
return fmt.Sprintf("\x1b[33m|%s|\x1b[0m", level) // yellow
|
||||
case "ERROR":
|
||||
return fmt.Sprintf("\x1b[31m|%s|\x1b[0m", level) // red
|
||||
case "FATAL":
|
||||
return fmt.Sprintf("\x1b[35m|%s|\x1b[0m", level) // magenta
|
||||
default:
|
||||
return fmt.Sprintf("|%s|", level)
|
||||
}
|
||||
}
|
||||
|
||||
func formatLevelPlain(i interface{}) string {
|
||||
return fmt.Sprintf("|%s|", strings.ToUpper(fmt.Sprintf("%s", i)))
|
||||
}
|
||||
|
||||
// --- TemplateFileWriter ---
|
||||
|
||||
// TemplateFileWriter 按时间模板切换文件路径的 Writer
|
||||
type TemplateFileWriter struct {
|
||||
pathTemplate string
|
||||
mu sync.Mutex
|
||||
currentPath string
|
||||
file *os.File
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func (w *TemplateFileWriter) Write(p []byte) (n int, err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
path := time.Now().Format(w.pathTemplate)
|
||||
if path != w.currentPath {
|
||||
if w.writer != nil {
|
||||
_ = w.writer.Flush()
|
||||
}
|
||||
if w.file != nil {
|
||||
_ = w.file.Close()
|
||||
}
|
||||
_ = os.MkdirAll(filepath.Dir(path), 0755)
|
||||
w.file, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.writer = bufio.NewWriterSize(w.file, 4096)
|
||||
w.currentPath = path
|
||||
}
|
||||
n, err = w.writer.Write(p)
|
||||
_ = w.writer.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// --- 调用者智能过滤 ---
|
||||
|
||||
const maxFrameworkDepth = 10
|
||||
|
||||
func callerMarshalFunc(_ uintptr, file string, line int) string {
|
||||
return findCaller(file, line)
|
||||
}
|
||||
|
||||
func findCaller(origFile string, origLine int) string {
|
||||
frameworkCount := 0
|
||||
var lastFrameworkFile string
|
||||
var lastFrameworkLine int
|
||||
var applicationFile string
|
||||
var applicationLine int
|
||||
|
||||
for i := 1; i < 20; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
shortFile := shortenPath(file)
|
||||
|
||||
if isHoTimeFrameworkFile(shortFile) {
|
||||
frameworkCount++
|
||||
lastFrameworkFile = shortFile
|
||||
lastFrameworkLine = line
|
||||
if strings.Contains(shortFile, "application.go") {
|
||||
applicationFile = shortFile
|
||||
applicationLine = line
|
||||
}
|
||||
if frameworkCount >= maxFrameworkDepth {
|
||||
return fmt.Sprintf("%s:%d", shortFile, line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", shortFile, line)
|
||||
}
|
||||
|
||||
if applicationFile != "" {
|
||||
return fmt.Sprintf("%s:%d", applicationFile, applicationLine)
|
||||
}
|
||||
if lastFrameworkFile != "" {
|
||||
return fmt.Sprintf("%s:%d", lastFrameworkFile, lastFrameworkLine)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", shortenPath(origFile), origLine)
|
||||
}
|
||||
|
||||
func formatCaller(file string, line int) string {
|
||||
return fmt.Sprintf("%s:%d", shortenPath(file), line)
|
||||
}
|
||||
|
||||
func shortenPath(file string) string {
|
||||
n := 0
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' || file[i] == '\\' {
|
||||
n++
|
||||
if n >= 2 {
|
||||
return file[i+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func isHoTimeFrameworkFile(file string) bool {
|
||||
// zerolog 内部文件
|
||||
if strings.HasPrefix(file, "zerolog/") || strings.HasPrefix(file, "zerolog@") {
|
||||
return true
|
||||
}
|
||||
// logrus 遗留(vendor 中可能存在)
|
||||
if strings.HasPrefix(file, "logrus/") || strings.HasPrefix(file, "logrus@") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(file, "runtime/") {
|
||||
return true
|
||||
}
|
||||
|
||||
lowerFile := strings.ToLower(file)
|
||||
if strings.Contains(lowerFile, "hotime") {
|
||||
frameworkDirs := []string{"db/", "common/", "code/", "cache/", "log/", "dri/"}
|
||||
for _, dir := range frameworkDirs {
|
||||
if strings.Contains(file, dir) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(file, "application.go") ||
|
||||
strings.HasSuffix(file, "context.go") ||
|
||||
strings.HasSuffix(file, "session.go") ||
|
||||
strings.HasSuffix(file, "const.go") ||
|
||||
strings.HasSuffix(file, "type.go") ||
|
||||
strings.HasSuffix(file, "var.go") ||
|
||||
strings.HasSuffix(file, "mime.go") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
frameworkCoreDirs := []string{"db/", "common/", "code/", "cache/"}
|
||||
for _, dir := range frameworkCoreDirs {
|
||||
if strings.HasPrefix(file, dir) {
|
||||
frameworkFiles := []string{
|
||||
"query.go", "crud.go", "where.go", "builder.go", "db.go",
|
||||
"dialect.go", "aggregate.go", "transaction.go", "identifier.go",
|
||||
"error.go", "func.go", "map.go", "obj.go", "slice.go",
|
||||
"makecode.go", "template.go", "config.go",
|
||||
"cache.go", "cache_db.go", "cache_memory.go", "cache_redis.go",
|
||||
}
|
||||
for _, f := range frameworkFiles {
|
||||
if strings.HasSuffix(file, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
-222
@@ -1,222 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetLog(path string, showCodeLine bool) *log.Logger {
|
||||
|
||||
hook := MyHook{
|
||||
Field: "line",
|
||||
Skip: 5,
|
||||
Path: path, ShowCodeLine: showCodeLine,
|
||||
}
|
||||
|
||||
loger := log.New()
|
||||
loger.SetFormatter(&log.TextFormatter{
|
||||
ForceColors: true,
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
DisableLevelTruncation: true,
|
||||
})
|
||||
loger.AddHook(&hook)
|
||||
return loger
|
||||
}
|
||||
|
||||
// MyHook ...
|
||||
type MyHook struct {
|
||||
Path string //存储日志的位置
|
||||
ShowCodeLine bool //输出代码文件名称和日志行
|
||||
Field string
|
||||
Skip int
|
||||
levels []log.Level
|
||||
}
|
||||
|
||||
// Levels 只定义 error 和 panic 等级的日志,其他日志等级不会触发 hook
|
||||
func (that *MyHook) Levels() []log.Level {
|
||||
return log.AllLevels
|
||||
}
|
||||
|
||||
// Fire 将异常日志写入到指定日志文件中
|
||||
func (that *MyHook) Fire(entry *log.Entry) error {
|
||||
if that.ShowCodeLine {
|
||||
entry.Data[that.Field] = findCaller(that.Skip)
|
||||
}
|
||||
//不需要存储到文件
|
||||
if that.Path == "" {
|
||||
return nil
|
||||
}
|
||||
//存储到文件
|
||||
logFilePath := time.Now().Format(that.Path)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(logFilePath), os.ModeAppend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//os.Create(logFilePath)
|
||||
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bte, _ := entry.Bytes()
|
||||
_, err = f.Write(bte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 最大框架层数限制 - 超过这个层数后不再跳过,防止误过滤应用层
|
||||
const maxFrameworkDepth = 10
|
||||
|
||||
// isHoTimeFrameworkFile 判断是否是 HoTime 框架文件
|
||||
// 更精确的匹配:只有明确属于框架的文件才会被跳过
|
||||
func isHoTimeFrameworkFile(file string) bool {
|
||||
// 1. logrus 日志库内部文件(支持带版本号的路径,如 logrus@v1.8.1/entry.go)
|
||||
if strings.HasPrefix(file, "logrus/") || strings.HasPrefix(file, "logrus@") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 2. Go 运行时文件
|
||||
if strings.HasPrefix(file, "runtime/") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. HoTime 框架核心文件 - 通过包含 "hotime" 或框架特有文件名来识别
|
||||
// 检查路径中是否包含 hotime 框架标识
|
||||
lowerFile := strings.ToLower(file)
|
||||
if strings.Contains(lowerFile, "hotime") {
|
||||
// 是 hotime 框架的一部分,检查是否是核心模块
|
||||
frameworkDirs := []string{"db/", "common/", "code/", "cache/", "log/", "dri/"}
|
||||
for _, dir := range frameworkDirs {
|
||||
if strings.Contains(file, dir) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// 框架核心文件(在 hotime 根目录下的 .go 文件)
|
||||
if strings.HasSuffix(file, "application.go") ||
|
||||
strings.HasSuffix(file, "context.go") ||
|
||||
strings.HasSuffix(file, "session.go") ||
|
||||
strings.HasSuffix(file, "const.go") ||
|
||||
strings.HasSuffix(file, "type.go") ||
|
||||
strings.HasSuffix(file, "var.go") ||
|
||||
strings.HasSuffix(file, "mime.go") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 直接匹配框架核心目录(用于没有完整路径的情况)
|
||||
// 只匹配 "db/xxx.go" 这种在框架核心目录下的文件
|
||||
frameworkCoreDirs := []string{"db/", "common/", "code/", "cache/"}
|
||||
for _, dir := range frameworkCoreDirs {
|
||||
if strings.HasPrefix(file, dir) {
|
||||
// 额外检查:确保不是用户项目中同名目录
|
||||
// 框架文件通常有特定的文件名
|
||||
frameworkFiles := []string{
|
||||
"query.go", "crud.go", "where.go", "builder.go", "db.go",
|
||||
"dialect.go", "aggregate.go", "transaction.go", "identifier.go",
|
||||
"error.go", "func.go", "map.go", "obj.go", "slice.go",
|
||||
"makecode.go", "template.go", "config.go",
|
||||
"cache.go", "cache_db.go", "cache_memory.go", "cache_redis.go",
|
||||
}
|
||||
for _, f := range frameworkFiles {
|
||||
if strings.HasSuffix(file, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 对caller进行递归查询, 直到找到非框架层产生的第一个调用.
|
||||
// 遍历调用栈,跳过框架层文件,找到应用层代码
|
||||
// 使用层数限制确保不会误过滤应用层同名目录
|
||||
// 返回优先级:应用层代码 > application.go > 其他框架文件
|
||||
func findCaller(skip int) string {
|
||||
frameworkCount := 0 // 连续框架层计数
|
||||
var lastFrameworkFile string
|
||||
var lastFrameworkLine int
|
||||
var applicationFile string // 优先记录 application.go 位置
|
||||
var applicationLine int
|
||||
|
||||
// 遍历调用栈,找到第一个非框架文件
|
||||
for i := 0; i < 20; i++ {
|
||||
file, line := getCaller(skip + i)
|
||||
if file == "" {
|
||||
break
|
||||
}
|
||||
|
||||
isFramework := isHoTimeFrameworkFile(file)
|
||||
|
||||
if isFramework {
|
||||
frameworkCount++
|
||||
lastFrameworkFile = file
|
||||
lastFrameworkLine = line
|
||||
// 优先记录 application.go 位置(HoTime 框架入口)
|
||||
if strings.Contains(file, "application.go") {
|
||||
applicationFile = file
|
||||
applicationLine = line
|
||||
}
|
||||
// 层数限制:如果已经跳过太多层,停止跳过
|
||||
if frameworkCount >= maxFrameworkDepth {
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 找到非框架文件,返回应用层代码位置
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
// 如果找不到应用层,返回最后记录的框架文件位置
|
||||
// 优先级:application.go > 其他框架文件 > 第一个调用者
|
||||
// 确保不会返回 logrus 或 runtime 等三方组件位置
|
||||
if applicationFile != "" {
|
||||
return fmt.Sprintf("%s:%d", applicationFile, applicationLine)
|
||||
}
|
||||
|
||||
if lastFrameworkFile != "" {
|
||||
return fmt.Sprintf("%s:%d", lastFrameworkFile, lastFrameworkLine)
|
||||
}
|
||||
|
||||
// 最后的回退:返回第一个调用者
|
||||
file, line := getCaller(skip)
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
// 这里其实可以获取函数名称的: fnName := runtime.FuncForPC(pc).Name()
|
||||
// 但是我觉得有 文件名和行号就够定位问题, 因此忽略了caller返回的第一个值:pc
|
||||
// 在标准库log里面我们可以选择记录文件的全路径或者文件名, 但是在使用过程成并发最合适的,
|
||||
// 因为文件的全路径往往很长, 而文件名在多个包中往往有重复, 因此这里选择多取一层, 取到文件所在的上层目录那层.
|
||||
func getCaller(skip int) (string, int) {
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return "", 0
|
||||
}
|
||||
n := 0
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' || file[i] == '\\' { // 同时处理 / 和 \ 路径分隔符
|
||||
n++
|
||||
if n >= 2 {
|
||||
file = file[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return file, line
|
||||
}
|
||||
Reference in New Issue
Block a user