From 2f99a93212ec08babc5cb29642ff021fe5d6c185 Mon Sep 17 00:00:00 2001 From: taynpg Date: Mon, 23 Mar 2026 11:47:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E8=87=AAnested-logrus-format?= =?UTF-8?q?ter=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- golang/logUse/ansi/formatter.go | 243 ++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 golang/logUse/ansi/formatter.go diff --git a/golang/logUse/ansi/formatter.go b/golang/logUse/ansi/formatter.go new file mode 100644 index 0000000..8276331 --- /dev/null +++ b/golang/logUse/ansi/formatter.go @@ -0,0 +1,243 @@ +package ansi + +// 改自 github.com/antonfisher/nested-logrus-formatter v1.3.1 +import ( + "bytes" + "fmt" + "runtime" + "sort" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +// Formatter - logrus formatter, implements logrus.Formatter +type Formatter struct { + // FieldsOrder - default: fields sorted alphabetically + FieldsOrder []string + + // TimestampFormat - default: time.StampMilli = "Jan _2 15:04:05.000" + TimestampFormat string + + // HideKeys - show [fieldValue] instead of [fieldKey:fieldValue] + HideKeys bool + + // NoColors - disable colors + NoColors bool + + // NoFieldsColors - apply colors only to the level, default is level + fields + NoFieldsColors bool + + // NoFieldsSpace - no space between fields + NoFieldsSpace bool + + // ShowFullLevel - show a full level [WARNING] instead of [WARN] + ShowFullLevel bool + + // NoUppercaseLevel - no upper case for level value + NoUppercaseLevel bool + + // TrimMessages - trim whitespaces on messages + TrimMessages bool + + // CallerFirst - print caller info first + CallerFirst bool + + // HighlightMessage - highlight message with level color + HighlightMessage bool + + // CustomCallerFormatter - set custom formatter for caller info + CustomCallerFormatter func(*runtime.Frame) string +} + +// Format an log entry +func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { + levelColor := getColorByLevel(entry.Level) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = time.StampMilli + } + + // output buffer + b := &bytes.Buffer{} + + // write time + b.WriteString(entry.Time.Format(timestampFormat)) + + // write level + var level string + if f.NoUppercaseLevel { + level = entry.Level.String() + } else { + level = strings.ToUpper(entry.Level.String()) + } + + if f.CallerFirst { + f.writeCaller(b, entry) + } + + hasLevelColor := false + if !f.NoColors { + fmt.Fprintf(b, "\x1b[%dm", levelColor) + hasLevelColor = true + } + + b.WriteString(" [") + if f.ShowFullLevel { + b.WriteString(level) + } else { + b.WriteString(level[:4]) + } + b.WriteString("]") + + if !f.NoFieldsSpace { + b.WriteString(" ") + } + + // 重置颜色逻辑 + if hasLevelColor { + // 如果设置了 NoFieldsColors 且 不开启高亮消息,则在字段前重置颜色 + if f.NoFieldsColors && !f.HighlightMessage { + b.WriteString("\x1b[0m") + hasLevelColor = false + } + // 如果设置了 NoFieldsColors 且 开启高亮消息,则保持颜色 + // 如果没设置 NoFieldsColors,则保持颜色用于字段 + } + + // write fields + if f.FieldsOrder == nil { + f.writeFields(b, entry) + } else { + f.writeOrderedFields(b, entry) + } + + if f.NoFieldsSpace { + b.WriteString(" ") + } + + // 写入消息前的颜色处理 + if hasLevelColor && !f.HighlightMessage { + // 如果还有颜色但不需要高亮消息,先重置颜色 + b.WriteString("\x1b[0m") + hasLevelColor = false + } else if f.HighlightMessage && !hasLevelColor && !f.NoColors { + // 如果需要高亮消息但当前没有颜色,设置颜色 + fmt.Fprintf(b, "\x1b[%dm", levelColor) + hasLevelColor = true + } + + // write message + var message string + if f.TrimMessages { + message = strings.TrimSpace(entry.Message) + } else { + message = entry.Message + } + b.WriteString(message) + + // 重置颜色 + if hasLevelColor && !f.NoColors { + b.WriteString("\x1b[0m") + } + + if !f.CallerFirst { + f.writeCaller(b, entry) + } + + b.WriteByte('\n') + + return b.Bytes(), nil +} + +func (f *Formatter) writeCaller(b *bytes.Buffer, entry *logrus.Entry) { + if entry.HasCaller() { + if f.CustomCallerFormatter != nil { + fmt.Fprintf(b, "%s", f.CustomCallerFormatter(entry.Caller)) + } else { + fmt.Fprintf( + b, + " (%s:%d %s)", + entry.Caller.File, + entry.Caller.Line, + entry.Caller.Function, + ) + } + } +} + +func (f *Formatter) writeFields(b *bytes.Buffer, entry *logrus.Entry) { + if len(entry.Data) != 0 { + fields := make([]string, 0, len(entry.Data)) + for field := range entry.Data { + fields = append(fields, field) + } + + sort.Strings(fields) + + for _, field := range fields { + f.writeField(b, entry, field) + } + } +} + +func (f *Formatter) writeOrderedFields(b *bytes.Buffer, entry *logrus.Entry) { + length := len(entry.Data) + foundFieldsMap := map[string]bool{} + for _, field := range f.FieldsOrder { + if _, ok := entry.Data[field]; ok { + foundFieldsMap[field] = true + length-- + f.writeField(b, entry, field) + } + } + + if length > 0 { + notFoundFields := make([]string, 0, length) + for field := range entry.Data { + if foundFieldsMap[field] == false { + notFoundFields = append(notFoundFields, field) + } + } + + sort.Strings(notFoundFields) + + for _, field := range notFoundFields { + f.writeField(b, entry, field) + } + } +} + +func (f *Formatter) writeField(b *bytes.Buffer, entry *logrus.Entry, field string) { + if f.HideKeys { + fmt.Fprintf(b, "[%v]", entry.Data[field]) + } else { + fmt.Fprintf(b, "[%s:%v]", field, entry.Data[field]) + } + + if !f.NoFieldsSpace { + b.WriteString(" ") + } +} + +const ( + colorRed = 31 + colorYellow = 33 + colorBlue = 36 + colorGray = 37 +) + +func getColorByLevel(level logrus.Level) int { + switch level { + case logrus.DebugLevel, logrus.TraceLevel: + return colorGray + case logrus.WarnLevel: + return colorYellow + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + return colorRed + default: + return colorBlue + } +}