| package logrus |
| |
| import ( |
| "bytes" |
| "fmt" |
| "regexp" |
| "sort" |
| "strings" |
| "time" |
| ) |
| |
| const ( |
| nocolor = 0 |
| red = 31 |
| green = 32 |
| yellow = 33 |
| blue = 34 |
| gray = 37 |
| ) |
| |
| var ( |
| baseTimestamp time.Time |
| isTerminal bool |
| noQuoteNeeded *regexp.Regexp |
| ) |
| |
| func init() { |
| baseTimestamp = time.Now() |
| isTerminal = IsTerminal() |
| } |
| |
| func miniTS() int { |
| return int(time.Since(baseTimestamp) / time.Second) |
| } |
| |
| type TextFormatter struct { |
| // Set to true to bypass checking for a TTY before outputting colors. |
| ForceColors bool |
| |
| // Force disabling colors. |
| DisableColors bool |
| |
| // Disable timestamp logging. useful when output is redirected to logging |
| // system that already adds timestamps. |
| DisableTimestamp bool |
| |
| // Enable logging the full timestamp when a TTY is attached instead of just |
| // the time passed since beginning of execution. |
| FullTimestamp bool |
| |
| // 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 |
| } |
| |
| func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { |
| var keys []string = make([]string, 0, len(entry.Data)) |
| for k := range entry.Data { |
| keys = append(keys, k) |
| } |
| |
| if !f.DisableSorting { |
| sort.Strings(keys) |
| } |
| |
| b := &bytes.Buffer{} |
| |
| prefixFieldClashes(entry.Data) |
| |
| isColored := (f.ForceColors || isTerminal) && !f.DisableColors |
| |
| if isColored { |
| f.printColored(b, entry, keys) |
| } else { |
| if !f.DisableTimestamp { |
| f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) |
| } |
| f.appendKeyValue(b, "level", entry.Level.String()) |
| f.appendKeyValue(b, "msg", entry.Message) |
| for _, key := range keys { |
| f.appendKeyValue(b, key, entry.Data[key]) |
| } |
| } |
| |
| b.WriteByte('\n') |
| return b.Bytes(), nil |
| } |
| |
| func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) { |
| var levelColor int |
| switch entry.Level { |
| case DebugLevel: |
| levelColor = gray |
| case WarnLevel: |
| levelColor = yellow |
| case ErrorLevel, FatalLevel, PanicLevel: |
| levelColor = red |
| default: |
| levelColor = blue |
| } |
| |
| levelText := strings.ToUpper(entry.Level.String())[0:4] |
| |
| if !f.FullTimestamp { |
| fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) |
| } else { |
| fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message) |
| } |
| for _, k := range keys { |
| v := entry.Data[k] |
| fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) |
| } |
| } |
| |
| func needsQuoting(text string) bool { |
| for _, ch := range text { |
| if !((ch >= 'a' && ch <= 'z') || |
| (ch >= 'A' && ch <= 'Z') || |
| (ch >= '0' && ch <= '9') || |
| ch == '-' || ch == '.') { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) { |
| switch value.(type) { |
| case string: |
| if needsQuoting(value.(string)) { |
| fmt.Fprintf(b, "%v=%s ", key, value) |
| } else { |
| fmt.Fprintf(b, "%v=%q ", key, value) |
| } |
| case error: |
| if needsQuoting(value.(error).Error()) { |
| fmt.Fprintf(b, "%v=%s ", key, value) |
| } else { |
| fmt.Fprintf(b, "%v=%q ", key, value) |
| } |
| default: |
| fmt.Fprintf(b, "%v=%v ", key, value) |
| } |
| } |