blob: 26dcc1553855c65aa529b72722dcf34d8e3f0cec [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001package logrus
2
3import (
4 "bytes"
5 "fmt"
6 "sort"
7 "strings"
8 "sync"
9 "time"
10)
11
12const (
13 nocolor = 0
14 red = 31
15 green = 32
16 yellow = 33
17 blue = 34
18 gray = 37
19)
20
21var (
22 baseTimestamp time.Time
23)
24
25func init() {
26 baseTimestamp = time.Now()
27}
28
29type TextFormatter struct {
30 // Set to true to bypass checking for a TTY before outputting colors.
31 ForceColors bool
32
33 // Force disabling colors.
34 DisableColors bool
35
36 // Disable timestamp logging. useful when output is redirected to logging
37 // system that already adds timestamps.
38 DisableTimestamp bool
39
40 // Enable logging the full timestamp when a TTY is attached instead of just
41 // the time passed since beginning of execution.
42 FullTimestamp bool
43
44 // TimestampFormat to use for display when a full timestamp is printed
45 TimestampFormat string
46
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
49 // be desired.
50 DisableSorting bool
51
52 // QuoteEmptyFields will wrap empty fields in quotes if true
53 QuoteEmptyFields bool
54
55 // QuoteCharacter can be set to the override the default quoting character "
56 // with something else. For example: ', or `.
57 QuoteCharacter string
58
59 // Whether the logger's out is to a terminal
60 isTerminal bool
61
62 sync.Once
63}
64
65func (f *TextFormatter) init(entry *Entry) {
66 if len(f.QuoteCharacter) == 0 {
67 f.QuoteCharacter = "\""
68 }
69 if entry.Logger != nil {
70 f.isTerminal = IsTerminal(entry.Logger.Out)
71 }
72}
73
74func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
75 var b *bytes.Buffer
76 keys := make([]string, 0, len(entry.Data))
77 for k := range entry.Data {
78 keys = append(keys, k)
79 }
80
81 if !f.DisableSorting {
82 sort.Strings(keys)
83 }
84 if entry.Buffer != nil {
85 b = entry.Buffer
86 } else {
87 b = &bytes.Buffer{}
88 }
89
90 prefixFieldClashes(entry.Data)
91
92 f.Do(func() { f.init(entry) })
93
94 isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
95
96 timestampFormat := f.TimestampFormat
97 if timestampFormat == "" {
98 timestampFormat = DefaultTimestampFormat
99 }
100 if isColored {
101 f.printColored(b, entry, keys, timestampFormat)
102 } else {
103 if !f.DisableTimestamp {
104 f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
105 }
106 f.appendKeyValue(b, "level", entry.Level.String())
107 if entry.Message != "" {
108 f.appendKeyValue(b, "msg", entry.Message)
109 }
110 for _, key := range keys {
111 f.appendKeyValue(b, key, entry.Data[key])
112 }
113 }
114
115 b.WriteByte('\n')
116 return b.Bytes(), nil
117}
118
119func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
120 var levelColor int
121 switch entry.Level {
122 case DebugLevel:
123 levelColor = gray
124 case WarnLevel:
125 levelColor = yellow
126 case ErrorLevel, FatalLevel, PanicLevel:
127 levelColor = red
128 default:
129 levelColor = blue
130 }
131
132 levelText := strings.ToUpper(entry.Level.String())[0:4]
133
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)
138 } else {
139 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
140 }
141 for _, k := range keys {
142 v := entry.Data[k]
143 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
144 f.appendValue(b, v)
145 }
146}
147
148func (f *TextFormatter) needsQuoting(text string) bool {
149 if f.QuoteEmptyFields && len(text) == 0 {
150 return true
151 }
152 for _, ch := range text {
153 if !((ch >= 'a' && ch <= 'z') ||
154 (ch >= 'A' && ch <= 'Z') ||
155 (ch >= '0' && ch <= '9') ||
156 ch == '-' || ch == '.') {
157 return true
158 }
159 }
160 return false
161}
162
163func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
164
165 b.WriteString(key)
166 b.WriteByte('=')
167 f.appendValue(b, value)
168 b.WriteByte(' ')
169}
170
171func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
172 switch value := value.(type) {
173 case string:
174 if !f.needsQuoting(value) {
175 b.WriteString(value)
176 } else {
177 b.WriteString(f.quoteString(value))
178 }
179 case error:
180 errmsg := value.Error()
181 if !f.needsQuoting(errmsg) {
182 b.WriteString(errmsg)
183 } else {
184 b.WriteString(f.quoteString(errmsg))
185 }
186 default:
187 fmt.Fprint(b, value)
188 }
189}
190
191func (f *TextFormatter) quoteString(v string) string {
192 escapedQuote := fmt.Sprintf("\\%s", f.QuoteCharacter)
193 escapedValue := strings.Replace(v, f.QuoteCharacter, escapedQuote, -1)
194
195 return fmt.Sprintf("%s%v%s", f.QuoteCharacter, escapedValue, f.QuoteCharacter)
196}