22 baseTimestamp time.Time
26 baseTimestamp = time.Now()
29 type TextFormatter struct {
30 // Set to true to bypass checking for a TTY before outputting colors.
33 // Force disabling colors.
36 // Disable timestamp logging. useful when output is redirected to logging
37 // system that already adds timestamps.
40 // Enable logging the full timestamp when a TTY is attached instead of just
41 // the time passed since beginning of execution.
44 // TimestampFormat to use for display when a full timestamp is printed
45 TimestampFormat string
47 // The fields are sorted by default for a consistent output. For applications
48 // that log extremely frequently and don't use the JSON formatter this may not
52 // QuoteEmptyFields will wrap empty fields in quotes if true
55 // QuoteCharacter can be set to the override the default quoting character "
56 // with something else. For example: ', or `.
59 // Whether the logger's out is to a terminal
65 func (f *TextFormatter) init(entry *Entry) {
66 if len(f.QuoteCharacter) == 0 {
67 f.QuoteCharacter = "\""
69 if entry.Logger != nil {
70 f.isTerminal = IsTerminal(entry.Logger.Out)
74 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
76 keys := make([]string, 0, len(entry.Data))
77 for k := range entry.Data {
78 keys = append(keys, k)
81 if !f.DisableSorting {
84 if entry.Buffer != nil {
90 prefixFieldClashes(entry.Data)
92 f.Do(func() { f.init(entry) })
94 isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
96 timestampFormat := f.TimestampFormat
97 if timestampFormat == "" {
98 timestampFormat = DefaultTimestampFormat
101 f.printColored(b, entry, keys, timestampFormat)
103 if !f.DisableTimestamp {
104 f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
106 f.appendKeyValue(b, "level", entry.Level.String())
107 if entry.Message != "" {
108 f.appendKeyValue(b, "msg", entry.Message)
110 for _, key := range keys {
111 f.appendKeyValue(b, key, entry.Data[key])
116 return b.Bytes(), nil
119 func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
126 case ErrorLevel, FatalLevel, PanicLevel:
132 levelText := strings.ToUpper(entry.Level.String())[0:4]
134 if f.DisableTimestamp {
135 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
136 } else if !f.FullTimestamp {
137 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
139 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
141 for _, k := range keys {
143 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
148 func (f *TextFormatter) needsQuoting(text string) bool {
149 if f.QuoteEmptyFields && len(text) == 0 {
152 for _, ch := range text {
153 if !((ch >= 'a' && ch <= 'z') ||
154 (ch >= 'A' && ch <= 'Z') ||
155 (ch >= '0' && ch <= '9') ||
156 ch == '-' || ch == '.') {
163 func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
167 f.appendValue(b, value)
171 func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
172 switch value := value.(type) {
174 if !f.needsQuoting(value) {
177 fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter)
180 errmsg := value.Error()
181 if !f.needsQuoting(errmsg) {
182 b.WriteString(errmsg)
184 fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter)