]>
Commit | Line | Data |
---|---|---|
9b12e4fe JC |
1 | package logrus |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "fmt" | |
6 | "runtime" | |
7 | "sort" | |
8 | "strings" | |
9 | "time" | |
10 | ) | |
11 | ||
12 | const ( | |
13 | nocolor = 0 | |
14 | red = 31 | |
15 | green = 32 | |
16 | yellow = 33 | |
17 | blue = 34 | |
18 | gray = 37 | |
19 | ) | |
20 | ||
21 | var ( | |
22 | baseTimestamp time.Time | |
23 | isTerminal bool | |
24 | ) | |
25 | ||
26 | func init() { | |
27 | baseTimestamp = time.Now() | |
28 | isTerminal = IsTerminal() | |
29 | } | |
30 | ||
31 | func miniTS() int { | |
32 | return int(time.Since(baseTimestamp) / time.Second) | |
33 | } | |
34 | ||
35 | type TextFormatter struct { | |
36 | // Set to true to bypass checking for a TTY before outputting colors. | |
37 | ForceColors bool | |
38 | ||
39 | // Force disabling colors. | |
40 | DisableColors bool | |
41 | ||
42 | // Disable timestamp logging. useful when output is redirected to logging | |
43 | // system that already adds timestamps. | |
44 | DisableTimestamp bool | |
45 | ||
46 | // Enable logging the full timestamp when a TTY is attached instead of just | |
47 | // the time passed since beginning of execution. | |
48 | FullTimestamp bool | |
49 | ||
50 | // TimestampFormat to use for display when a full timestamp is printed | |
51 | TimestampFormat string | |
52 | ||
53 | // The fields are sorted by default for a consistent output. For applications | |
54 | // that log extremely frequently and don't use the JSON formatter this may not | |
55 | // be desired. | |
56 | DisableSorting bool | |
57 | } | |
58 | ||
59 | func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { | |
60 | var keys []string = make([]string, 0, len(entry.Data)) | |
61 | for k := range entry.Data { | |
62 | keys = append(keys, k) | |
63 | } | |
64 | ||
65 | if !f.DisableSorting { | |
66 | sort.Strings(keys) | |
67 | } | |
68 | ||
69 | b := &bytes.Buffer{} | |
70 | ||
71 | prefixFieldClashes(entry.Data) | |
72 | ||
73 | isColorTerminal := isTerminal && (runtime.GOOS != "windows") | |
74 | isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors | |
75 | ||
76 | timestampFormat := f.TimestampFormat | |
77 | if timestampFormat == "" { | |
78 | timestampFormat = DefaultTimestampFormat | |
79 | } | |
80 | if isColored { | |
81 | f.printColored(b, entry, keys, timestampFormat) | |
82 | } else { | |
83 | if !f.DisableTimestamp { | |
84 | f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) | |
85 | } | |
86 | f.appendKeyValue(b, "level", entry.Level.String()) | |
87 | if entry.Message != "" { | |
88 | f.appendKeyValue(b, "msg", entry.Message) | |
89 | } | |
90 | for _, key := range keys { | |
91 | f.appendKeyValue(b, key, entry.Data[key]) | |
92 | } | |
93 | } | |
94 | ||
95 | b.WriteByte('\n') | |
96 | return b.Bytes(), nil | |
97 | } | |
98 | ||
99 | func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { | |
100 | var levelColor int | |
101 | switch entry.Level { | |
102 | case DebugLevel: | |
103 | levelColor = gray | |
104 | case WarnLevel: | |
105 | levelColor = yellow | |
106 | case ErrorLevel, FatalLevel, PanicLevel: | |
107 | levelColor = red | |
108 | default: | |
109 | levelColor = blue | |
110 | } | |
111 | ||
112 | levelText := strings.ToUpper(entry.Level.String())[0:4] | |
113 | ||
114 | if !f.FullTimestamp { | |
115 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) | |
116 | } else { | |
117 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) | |
118 | } | |
119 | for _, k := range keys { | |
120 | v := entry.Data[k] | |
121 | fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v) | |
122 | } | |
123 | } | |
124 | ||
125 | func needsQuoting(text string) bool { | |
126 | for _, ch := range text { | |
127 | if !((ch >= 'a' && ch <= 'z') || | |
128 | (ch >= 'A' && ch <= 'Z') || | |
129 | (ch >= '0' && ch <= '9') || | |
130 | ch == '-' || ch == '.') { | |
131 | return false | |
132 | } | |
133 | } | |
134 | return true | |
135 | } | |
136 | ||
137 | func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { | |
138 | ||
139 | b.WriteString(key) | |
140 | b.WriteByte('=') | |
141 | ||
142 | switch value := value.(type) { | |
143 | case string: | |
144 | if needsQuoting(value) { | |
145 | b.WriteString(value) | |
146 | } else { | |
147 | fmt.Fprintf(b, "%q", value) | |
148 | } | |
149 | case error: | |
150 | errmsg := value.Error() | |
151 | if needsQuoting(errmsg) { | |
152 | b.WriteString(errmsg) | |
153 | } else { | |
154 | fmt.Fprintf(b, "%q", value) | |
155 | } | |
156 | default: | |
157 | fmt.Fprint(b, value) | |
158 | } | |
159 | ||
160 | b.WriteByte(' ') | |
161 | } |