package logger import ( "encoding/json" "fmt" "io" "os" "reflect" "runtime" "strings" "sync" "time" "github.com/sirupsen/logrus" ) const ( reset = "\033[0m" red = "\033[31m" green = "\033[32m" yellow = "\033[33m" blue = "\033[34m" cyan = "\033[36m" white = "\033[97m" ) type colorFormatter struct{} func (f *colorFormatter) 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 = red default: levelColor = white } timestamp := fmt.Sprintf("%s%s%s", levelColor, entry.Time.Format("01-02 15:04:05"), reset) file := "unknown" line := 0 if entry.Caller != nil { file = entry.Caller.File if idx := strings.LastIndex(file, "/"); idx >= 0 { file = file[idx+1:] } line = entry.Caller.Line } title, _ := entry.Data["title"].(string) if title == "" { title = "-" } level := fmt.Sprintf("%s[%s]%s", levelColor, strings.ToUpper(entry.Level.String()), reset) titleStr := fmt.Sprintf("%s「%s」%s", levelColor, title, reset) msg := fmt.Sprintf("%s %s:%d: %s >> %s %s\n", timestamp, file, line, level, titleStr, entry.Message, ) return []byte(msg), nil } var ( instance *logrus.Logger once sync.Once cronInstance *logrus.Logger cronOnce sync.Once ) func GetLogger() *logrus.Logger { once.Do(func() { instance = logrus.New() switch strings.ToLower(os.Getenv("LOG_LEVEL")) { case "debug": instance.SetLevel(logrus.DebugLevel) case "info": instance.SetLevel(logrus.InfoLevel) case "warn": instance.SetLevel(logrus.WarnLevel) case "error": instance.SetLevel(logrus.ErrorLevel) case "fatal": instance.SetLevel(logrus.FatalLevel) default: instance.SetLevel(logrus.InfoLevel) } instance.SetFormatter(&colorFormatter{}) instance.SetReportCaller(true) if os.Getenv("LOG_SAVE") == "true" { logDir := os.Getenv("LOG_SAVE_PATH") if logDir != "" { if err := os.MkdirAll(logDir, 0755); err != nil { instance.Errorf("创建日志目录失败: %v", err) } else { logFile := logDir + "/" + time.Now().Format("20060102") + ".log" f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err == nil { instance.SetOutput(io.MultiWriter(os.Stdout, f)) } } } } }) return instance } func callerSkip() *runtime.Frame { pcs := make([]uintptr, 10) n := runtime.Callers(3, pcs) frames := runtime.CallersFrames(pcs[:n]) for { frame, more := frames.Next() if !strings.Contains(frame.File, "utils/logger/") { return &frame } if !more { break } } return nil } func toString(v interface{}) string { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Struct || (rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Struct) { data, err := json.Marshal(v) if err != nil { return fmt.Sprintf("%v", v) } return string(data) } return fmt.Sprintf("%v", v) } func joinToString(parts ...interface{}) string { strs := make([]string, 0, len(parts)) for _, p := range parts { strs = append(strs, toString(p)) } return strings.Join(strs, " ") } // GetCronLogger 返回定时任务专用 logger,写入独立日志文件 func GetCronLogger() *logrus.Logger { cronOnce.Do(func() { cronInstance = logrus.New() switch strings.ToLower(os.Getenv("LOG_LEVEL")) { case "debug": cronInstance.SetLevel(logrus.DebugLevel) case "info": cronInstance.SetLevel(logrus.InfoLevel) case "warn": cronInstance.SetLevel(logrus.WarnLevel) case "error": cronInstance.SetLevel(logrus.ErrorLevel) default: cronInstance.SetLevel(logrus.InfoLevel) } cronInstance.SetFormatter(&colorFormatter{}) cronInstance.SetReportCaller(true) logDir := os.Getenv("LOG_SAVE_PATH") if logDir == "" { logDir = "./logs" } if err := os.MkdirAll(logDir, 0755); err != nil { cronInstance.SetOutput(os.Stdout) return } logFile := logDir + "/cron_" + time.Now().Format("20060102") + ".log" f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { cronInstance.SetOutput(os.Stdout) return } cronInstance.SetOutput(io.MultiWriter(os.Stdout, f)) }) return cronInstance } func CronDebug(title string, content ...interface{}) { GetCronLogger().WithField("title", title).Debug(joinToString(content...)) } func CronInfo(title string, content ...interface{}) { GetCronLogger().WithField("title", title).Info(joinToString(content...)) } func CronWarn(title string, content ...interface{}) { GetCronLogger().WithField("title", title).Warn(joinToString(content...)) } func CronError(title string, content ...interface{}) { GetCronLogger().WithField("title", title).Error(joinToString(content...)) } func Debug(title string, content ...interface{}) { GetLogger().WithField("title", title).Debug(joinToString(content...)) } func Info(title string, content ...interface{}) { GetLogger().WithField("title", title).Info(joinToString(content...)) } func Warn(title string, content ...interface{}) { GetLogger().WithField("title", title).Warn(joinToString(content...)) } func Error(title string, content ...interface{}) { GetLogger().WithField("title", title).Error(joinToString(content...)) } func Fatal(title string, content ...interface{}) { GetLogger().WithField("title", title).Fatal(joinToString(content...)) }