package loger import ( "encoding/json" "fmt" "github.com/sirupsen/logrus" "io" "os" "reflect" "runtime" "strings" "time" ) // ANSI 转义码定义颜色 const ( reset = "\033[0m" red = "\033[31m" green = "\033[32m" yellow = "\033[33m" blue = "\033[34m" magenta = "\033[35m" cyan = "\033[36m" white = "\033[97m" gray = "\033[90m" brightYellow = "\033[93m" ) // 自定义日志格式化器 type customFormatter struct{} // Format 实现 logrus.Formatter 接口 func (f *customFormatter) Format(entry *logrus.Entry) ([]byte, error) { // 根据日志级别选择颜色 var levelColor string switch entry.Level { case logrus.DebugLevel: levelColor = cyan case logrus.InfoLevel: levelColor = green case logrus.WarnLevel: levelColor = yellow case logrus.ErrorLevel: levelColor = red case logrus.FatalLevel, logrus.PanicLevel: levelColor = magenta default: levelColor = white } // 获取时间,格式为 yyyy/mm/dd hh:mm:ss.ms timestamp := fmt.Sprintf("%s%s%s", levelColor, entry.Time.Format("01-02 15:04:05"), reset) // 获取文件名和行号 _, file, line, ok := runtime.Caller(6) // 使用更高的层级 if !ok { file = "unknown" line = 0 } if entry.Caller != nil { // 获取调用者的文件名并只保留文件名部分 file = file[strings.LastIndex(file, "/")+1:] // 仅保留文件名 } // 获取日志标题 title, ok := entry.Data["title"].(string) if !ok { title = "unknown" } // 格式化日志级别,并为其添加颜色 level := fmt.Sprintf("%s[%s]%s", levelColor, strings.ToUpper(entry.Level.String()), reset) title = fmt.Sprintf("%s「%s」%s", levelColor, title, reset) // 格式化输出内容 logMessage := fmt.Sprintf("%s %s:%d: %s >> %s %s\n", timestamp, file, line, level, title, entry.Message, ) return []byte(logMessage), nil } func getLog() *logrus.Logger { logLevel := os.Getenv("LOG_LEVEL") logSave := os.Getenv("LOG_SAVE") logSaveDir := os.Getenv("LOG_SAVE_PATH") log := logrus.New() switch logLevel { case "debug": log.SetLevel(logrus.DebugLevel) case "info": log.SetLevel(logrus.InfoLevel) case "warn": log.SetLevel(logrus.WarnLevel) case "error": log.SetLevel(logrus.ErrorLevel) case "fatal": log.SetLevel(logrus.FatalLevel) } // 自定义日志格式 log.SetFormatter(&customFormatter{}) // 启用调用信息的追踪,这样可以获取到文件名和行号 log.SetReportCaller(true) if logSave == "true" { logFileName := time.Now().Format("20060102") + ".log" logSavePath := logSaveDir + "/" + logFileName // 判断日志文件是否已存在 _, err := os.Stat(logSaveDir) if os.IsNotExist(err) { // 路径不存在,创建目录 if err := os.MkdirAll(logSaveDir, 0755); err != nil { log.Error("日志文件目录创建失败") return log } } logFile, _ := os.OpenFile(logSavePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) multiWriter := io.MultiWriter(os.Stdout, logFile) log.SetOutput(multiWriter) } return log } func toString(v interface{}) string { // 使用反射检查类型 rv := reflect.ValueOf(v) // 处理结构体和结构体指针 if rv.Kind() == reflect.Struct || (rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Struct) { jsonData, err := json.Marshal(v) if err != nil { return fmt.Sprintf("Error marshalling to JSON: %v", err) } return string(jsonData) } // 对于其他类型,使用 fmt.Sprintf return fmt.Sprintf("%v", v) } // isPrintable 检查字符串是否只包含可打印字符 func isPrintable(s string) bool { for _, r := range s { if r < 32 || r > 126 { return false } } return true } // joinToString 将多个参数转换为字符串,并拼接在一起。 func joinToString(parts ...interface{}) string { var strParts []string for _, part := range parts { strParts = append(strParts, toString(part)) } return strings.Join(strParts, "") } func Debug(title string, content ...interface{}) { getLog().WithFields(logrus.Fields{ "title": title, }).Debug(joinToString(content)) } func Info(title string, content ...interface{}) { getLog().WithFields(logrus.Fields{ "title": title, }).Info(joinToString(content)) } func Warn(title string, content ...interface{}) { getLog().WithFields(logrus.Fields{ "title": title, }).Warn(joinToString(content)) } func Error(title string, content ...interface{}) { getLog().WithFields(logrus.Fields{ "title": title, }).Error(joinToString(content)) } func Fatal(title string, content ...interface{}) { getLog().WithFields(logrus.Fields{ "title": title, }).Fatal(joinToString(content)) }