diff options
Diffstat (limited to 'vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go')
-rw-r--r-- | vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go b/vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go new file mode 100644 index 00000000..7cb807da --- /dev/null +++ b/vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go @@ -0,0 +1,382 @@ +package prefixed + +import ( + "bytes" + "fmt" + "io" + "os" + "regexp" + "sort" + "strings" + "sync" + "time" + + "github.com/mgutz/ansi" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" +) + +const defaultTimestampFormat = time.RFC3339 + +var ( + baseTimestamp time.Time = time.Now() + defaultColorScheme *ColorScheme = &ColorScheme{ + InfoLevelStyle: "green", + WarnLevelStyle: "yellow", + ErrorLevelStyle: "red", + FatalLevelStyle: "red", + PanicLevelStyle: "red", + DebugLevelStyle: "blue", + PrefixStyle: "cyan", + TimestampStyle: "black+h", + } + noColorsColorScheme *compiledColorScheme = &compiledColorScheme{ + InfoLevelColor: ansi.ColorFunc(""), + WarnLevelColor: ansi.ColorFunc(""), + ErrorLevelColor: ansi.ColorFunc(""), + FatalLevelColor: ansi.ColorFunc(""), + PanicLevelColor: ansi.ColorFunc(""), + DebugLevelColor: ansi.ColorFunc(""), + PrefixColor: ansi.ColorFunc(""), + TimestampColor: ansi.ColorFunc(""), + } + defaultCompiledColorScheme *compiledColorScheme = compileColorScheme(defaultColorScheme) +) + +func miniTS() int { + return int(time.Since(baseTimestamp) / time.Second) +} + +type ColorScheme struct { + InfoLevelStyle string + WarnLevelStyle string + ErrorLevelStyle string + FatalLevelStyle string + PanicLevelStyle string + DebugLevelStyle string + PrefixStyle string + TimestampStyle string +} + +type compiledColorScheme struct { + InfoLevelColor func(string) string + WarnLevelColor func(string) string + ErrorLevelColor func(string) string + FatalLevelColor func(string) string + PanicLevelColor func(string) string + DebugLevelColor func(string) string + PrefixColor func(string) string + TimestampColor func(string) string +} + +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. For a TTY colors are enabled by default. + DisableColors bool + + // Force formatted layout, even for non-TTY output. + ForceFormatting bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Disable the conversion of the log levels to uppercase + DisableUppercase bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // Timestamp format to use for display when a full timestamp is printed. + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool + + // Wrap empty fields in quotes if true. + QuoteEmptyFields bool + + // Can be set to the override the default quoting character " + // with something else. For example: ', or `. + QuoteCharacter string + + // Pad msg field with spaces on the right for display. + // The value for this parameter will be the size of padding. + // Its default value is zero, which means no padding will be applied for msg. + SpacePadding int + + // Pad prefix field with spaces on the right for display. + // The value for this parameter will be the size of padding. + // Its default value is zero, which means no padding will be applied for prefix. + PrefixPadding int + + // Color scheme to use. + colorScheme *compiledColorScheme + + // Whether the logger's out is to a terminal. + isTerminal bool + + sync.Once +} + +func getCompiledColor(main string, fallback string) func(string) string { + var style string + if main != "" { + style = main + } else { + style = fallback + } + return ansi.ColorFunc(style) +} + +func compileColorScheme(s *ColorScheme) *compiledColorScheme { + return &compiledColorScheme{ + InfoLevelColor: getCompiledColor(s.InfoLevelStyle, defaultColorScheme.InfoLevelStyle), + WarnLevelColor: getCompiledColor(s.WarnLevelStyle, defaultColorScheme.WarnLevelStyle), + ErrorLevelColor: getCompiledColor(s.ErrorLevelStyle, defaultColorScheme.ErrorLevelStyle), + FatalLevelColor: getCompiledColor(s.FatalLevelStyle, defaultColorScheme.FatalLevelStyle), + PanicLevelColor: getCompiledColor(s.PanicLevelStyle, defaultColorScheme.PanicLevelStyle), + DebugLevelColor: getCompiledColor(s.DebugLevelStyle, defaultColorScheme.DebugLevelStyle), + PrefixColor: getCompiledColor(s.PrefixStyle, defaultColorScheme.PrefixStyle), + TimestampColor: getCompiledColor(s.TimestampStyle, defaultColorScheme.TimestampStyle), + } +} + +func (f *TextFormatter) init(entry *logrus.Entry) { + if len(f.QuoteCharacter) == 0 { + f.QuoteCharacter = "\"" + } + if entry.Logger != nil { + f.isTerminal = f.checkIfTerminal(entry.Logger.Out) + } +} + +func (f *TextFormatter) checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + return terminal.IsTerminal(int(v.Fd())) + default: + return false + } +} + +func (f *TextFormatter) SetColorScheme(colorScheme *ColorScheme) { + f.colorScheme = compileColorScheme(colorScheme) +} + +func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var b *bytes.Buffer + var keys []string = make([]string, 0, len(entry.Data)) + for k := range entry.Data { + keys = append(keys, k) + } + lastKeyIdx := len(keys) - 1 + + if !f.DisableSorting { + sort.Strings(keys) + } + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + prefixFieldClashes(entry.Data) + + f.Do(func() { f.init(entry) }) + + isFormatted := f.ForceFormatting || f.isTerminal + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + if isFormatted { + isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors + var colorScheme *compiledColorScheme + if isColored { + if f.colorScheme == nil { + colorScheme = defaultCompiledColorScheme + } else { + colorScheme = f.colorScheme + } + } else { + colorScheme = noColorsColorScheme + } + f.printColored(b, entry, keys, timestampFormat, colorScheme) + } else { + if !f.DisableTimestamp { + f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat), true) + } + f.appendKeyValue(b, "level", entry.Level.String(), true) + if entry.Message != "" { + f.appendKeyValue(b, "msg", entry.Message, lastKeyIdx >= 0) + } + for i, key := range keys { + f.appendKeyValue(b, key, entry.Data[key], lastKeyIdx != i) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string, colorScheme *compiledColorScheme) { + var levelColor func(string) string + var levelText string + switch entry.Level { + case logrus.InfoLevel: + levelColor = colorScheme.InfoLevelColor + case logrus.WarnLevel: + levelColor = colorScheme.WarnLevelColor + case logrus.ErrorLevel: + levelColor = colorScheme.ErrorLevelColor + case logrus.FatalLevel: + levelColor = colorScheme.FatalLevelColor + case logrus.PanicLevel: + levelColor = colorScheme.PanicLevelColor + default: + levelColor = colorScheme.DebugLevelColor + } + + if entry.Level != logrus.WarnLevel { + levelText = entry.Level.String() + } else { + levelText = "warn" + } + + if !f.DisableUppercase { + levelText = strings.ToUpper(levelText) + } + + level := levelColor(fmt.Sprintf("%5s", levelText)) + prefix := "" + message := entry.Message + + adjustedPrefixPadding := f.PrefixPadding //compensate for ANSI color sequences + + if prefixValue, ok := entry.Data["prefix"]; ok { + rawPrefixLength := len(prefixValue.(string)) + prefix = colorScheme.PrefixColor(" " + prefixValue.(string) + ":") + adjustedPrefixPadding = f.PrefixPadding + (len(prefix) - rawPrefixLength - 1) + } else { + prefixValue, trimmedMsg := extractPrefix(entry.Message) + rawPrefixLength := len(prefixValue) + if len(prefixValue) > 0 { + prefix = colorScheme.PrefixColor(" " + prefixValue + ":") + message = trimmedMsg + } + adjustedPrefixPadding = f.PrefixPadding + (len(prefix) - rawPrefixLength - 1) + } + + prefixFormat := "%s" + if f.PrefixPadding != 0 { + prefixFormat = fmt.Sprintf("%%-%ds", adjustedPrefixPadding) + } + + messageFormat := "%s" + if f.SpacePadding != 0 { + messageFormat = fmt.Sprintf("%%-%ds", f.SpacePadding) + } + + if f.DisableTimestamp { + fmt.Fprintf(b, "%s"+prefixFormat+" "+messageFormat, level, prefix, message) + } else { + var timestamp string + if !f.FullTimestamp { + timestamp = fmt.Sprintf("[%04d]", miniTS()) + } else { + timestamp = fmt.Sprintf("[%s]", entry.Time.Format(timestampFormat)) + } + fmt.Fprintf(b, "%s %s"+prefixFormat+" "+messageFormat, colorScheme.TimestampColor(timestamp), level, prefix, message) + } + for _, k := range keys { + if k != "prefix" { + v := entry.Data[k] + fmt.Fprintf(b, " %s=%+v", levelColor(k), v) + } + } +} + +func (f *TextFormatter) needsQuoting(text string) bool { + if f.QuoteEmptyFields && len(text) == 0 { + return true + } + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.') { + return true + } + } + return false +} + +func extractPrefix(msg string) (string, string) { + prefix := "" + regex := regexp.MustCompile("^\\[(.*?)\\]") + if regex.MatchString(msg) { + match := regex.FindString(msg) + prefix, msg = match[1:len(match)-1], strings.TrimSpace(msg[len(match):]) + } + return prefix, msg +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}, appendSpace bool) { + b.WriteString(key) + b.WriteByte('=') + f.appendValue(b, value) + + if appendSpace { + b.WriteByte(' ') + } +} + +func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { + switch value := value.(type) { + case string: + if !f.needsQuoting(value) { + b.WriteString(value) + } else { + fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter) + } + case error: + errmsg := value.Error() + if !f.needsQuoting(errmsg) { + b.WriteString(errmsg) + } else { + fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter) + } + default: + fmt.Fprint(b, value) + } +} + +// This is to not silently overwrite `time`, `msg` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// would just silently drop the user provided level. Instead with this code +// it'll be logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +func prefixFieldClashes(data logrus.Fields) { + if t, ok := data["time"]; ok { + data["fields.time"] = t + } + + if m, ok := data["msg"]; ok { + data["fields.msg"] = m + } + + if l, ok := data["level"]; ok { + data["fields.level"] = l + } +} |