- 删除 CacheDb 结构体中的 debugLog 调用,清理冗余的调试日志代码 - 优化历史表的创建逻辑,简化条件检查 - 更新缓存的获取和设置方法,确保逻辑清晰且高效 - 维护代码整洁性,提升可读性和可维护性
193 lines
5.3 KiB
Go
193 lines
5.3 KiB
Go
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{})
|
||
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进行递归查询, 直到找到非框架层产生的第一个调用.
|
||
// 遍历调用栈,跳过框架层文件,找到应用层代码
|
||
// 使用层数限制确保不会误过滤应用层同名目录
|
||
func findCaller(skip int) string {
|
||
frameworkCount := 0 // 连续框架层计数
|
||
|
||
// 遍历调用栈,找到第一个非框架文件
|
||
for i := 0; i < 20; i++ {
|
||
file, line := getCaller(skip + i)
|
||
if file == "" {
|
||
break
|
||
}
|
||
|
||
if isHoTimeFrameworkFile(file) {
|
||
frameworkCount++
|
||
// 层数限制:如果已经跳过太多层,停止跳过
|
||
if frameworkCount >= maxFrameworkDepth {
|
||
return fmt.Sprintf("%s:%d", file, line)
|
||
}
|
||
continue
|
||
}
|
||
|
||
// 找到非框架文件,返回应用层代码位置
|
||
return fmt.Sprintf("%s:%d", file, line)
|
||
}
|
||
|
||
// 如果找不到应用层,返回最初的调用者
|
||
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
|
||
}
|