-
Notifications
You must be signed in to change notification settings - Fork 386
/
conslogging.go
348 lines (306 loc) · 8.26 KB
/
conslogging.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
package conslogging
import (
"fmt"
"io"
"strings"
"sync"
"unicode/utf8"
"github.com/fatih/color"
)
// ColorMode is the mode in which colors are represented in the output.
type ColorMode int
const (
// AutoColor automatically detects the presence of a TTY to decide if
// color should be used.
AutoColor ColorMode = iota
// NoColor disables use of color.
NoColor
// ForceColor forces use of color.
ForceColor
)
const (
// NoPadding means the old behavior of printing the full target only.
NoPadding int = -1
// DefaultPadding always prints 20 characters for the target, right
// justified. If it is longer, it prints the right 20 characters.
DefaultPadding int = 20
)
var currentConsoleMutex sync.Mutex
// ConsoleLogger is a writer for consoles.
type ConsoleLogger struct {
prefix string
// metadataMode are printed in a different color.
metadataMode bool
// isLocal has a special prefix *local* added.
isLocal bool
// salt is a salt used for color consistency
// (the same salt will get the same color).
salt string
colorMode ColorMode
isCached bool
isFailed bool
verbose bool
// The following are shared between instances and are protected by the mutex.
mu *sync.Mutex
saltColors map[string]*color.Color
nextColorIndex *int
outW io.Writer
errW io.Writer
trailingLine bool
prefixPadding int
}
func (cl ConsoleLogger) clone() ConsoleLogger {
return ConsoleLogger{
outW: cl.outW,
errW: cl.errW,
prefix: cl.prefix,
metadataMode: cl.metadataMode,
isLocal: cl.isLocal,
verbose: cl.verbose,
salt: cl.salt,
isCached: cl.isCached,
isFailed: cl.isFailed,
saltColors: cl.saltColors,
colorMode: cl.colorMode,
nextColorIndex: cl.nextColorIndex,
prefixPadding: cl.prefixPadding,
mu: cl.mu,
}
}
// WithPrefix returns a ConsoleLogger with a prefix added.
func (cl ConsoleLogger) WithPrefix(prefix string) ConsoleLogger {
ret := cl.clone()
ret.prefix = prefix
ret.salt = prefix
return ret
}
// WithMetadataMode returns a ConsoleLogger with metadata printing mode set.
func (cl ConsoleLogger) WithMetadataMode(metadataMode bool) ConsoleLogger {
ret := cl.clone()
ret.metadataMode = metadataMode
return ret
}
// WithLocal returns a ConsoleLogger with local set.
func (cl ConsoleLogger) WithLocal(isLocal bool) ConsoleLogger {
ret := cl.clone()
ret.isLocal = isLocal
return ret
}
// WithPrefixAndSalt returns a ConsoleLogger with a prefix and a seed added.
func (cl ConsoleLogger) WithPrefixAndSalt(prefix string, salt string) ConsoleLogger {
ret := cl.clone()
ret.prefix = prefix
ret.salt = salt
return ret
}
// Prefix returns the console's prefix.
func (cl ConsoleLogger) Prefix() string {
return cl.prefix
}
// WithCached returns a ConsoleLogger with isCached flag set accordingly.
func (cl ConsoleLogger) WithCached(isCached bool) ConsoleLogger {
ret := cl.clone()
ret.isCached = isCached
return ret
}
// WithFailed returns a ConsoleLogger with isFailed flag set accordingly.
func (cl ConsoleLogger) WithFailed(isFailed bool) ConsoleLogger {
ret := cl.clone()
ret.isFailed = isFailed
return ret
}
// PrintSuccess prints the success message.
func (cl ConsoleLogger) PrintSuccess(msg string) {
cl.mu.Lock()
defer cl.mu.Unlock()
cl.PrintBar(successColor, " SUCCESS ", msg)
}
// PrintFailure prints the failure message.
func (cl ConsoleLogger) PrintFailure(msg string) {
cl.mu.Lock()
defer cl.mu.Unlock()
cl.PrintBar(warnColor, " FAILURE ", msg)
}
// PrefixColor returns the color used for the prefix.
func (cl ConsoleLogger) PrefixColor() *color.Color {
c, found := cl.saltColors[cl.salt]
if !found {
c = availablePrefixColors[*cl.nextColorIndex]
cl.saltColors[cl.salt] = c
*cl.nextColorIndex = (*cl.nextColorIndex + 1) % len(availablePrefixColors)
}
return cl.color(c)
}
// PrintBar prints an earthly message bar
func (cl ConsoleLogger) PrintBar(c *color.Color, center, msg string) {
if msg != "" {
center = fmt.Sprintf("%s[%s] ", center, msg)
}
totalWidth := 80
sideWidth := (totalWidth - len(center)) / 2
if sideWidth < 0 {
sideWidth = 0
}
eqBar := strings.Repeat("=", sideWidth)
leftBar := eqBar
rightBar := eqBar
// Ensure the width is always totalWidth
if len(center)%2 == 1 && sideWidth > 0 {
rightBar += "="
}
cl.color(c).Fprintf(cl.outW, "%s%s%s\n", leftBar, center, rightBar)
}
// Warnf prints a warning message in red to errWriter
func (cl ConsoleLogger) Warnf(format string, args ...interface{}) {
cl.mu.Lock()
defer cl.mu.Unlock()
c := cl.color(warnColor)
text := fmt.Sprintf(format, args...)
text = strings.TrimSuffix(text, "\n")
for _, line := range strings.Split(text, "\n") {
cl.printPrefix(true)
c.Fprintf(cl.errW, "%s\n", line)
}
}
// Printf prints formatted text to the console.
func (cl ConsoleLogger) Printf(format string, args ...interface{}) {
cl.mu.Lock()
defer cl.mu.Unlock()
c := cl.color(noColor)
if cl.metadataMode {
c = cl.color(metadataModeColor)
}
text := fmt.Sprintf(format, args...)
text = strings.TrimSuffix(text, "\n")
for _, line := range strings.Split(text, "\n") {
cl.printPrefix(false)
c.Fprintf(cl.outW, "%s", line)
// Don't use a background color for \n.
cl.color(noColor).Fprintf(cl.outW, "\n")
}
}
// PrintBytes prints bytes directly to the console.
func (cl ConsoleLogger) PrintBytes(data []byte) {
cl.mu.Lock()
defer cl.mu.Unlock()
c := cl.color(noColor)
if cl.metadataMode {
c = cl.color(metadataModeColor)
}
output := make([]byte, 0, len(data))
for len(data) > 0 {
r, size := utf8.DecodeRune(data)
ch := data[:size]
data = data[size:]
switch r {
case '\r':
output = append(output, ch...)
cl.trailingLine = false
case '\n':
output = append(output, ch...)
cl.trailingLine = false
default:
if !cl.trailingLine {
if len(output) > 0 {
c.Fprintf(cl.outW, "%s", string(output))
output = output[:0]
}
cl.printPrefix(false)
cl.trailingLine = true
}
output = append(output, ch...)
}
}
if len(output) > 0 {
c.Fprintf(cl.outW, "%s", string(output))
// output = output[:0] // needed if output is used further in the future
}
}
// VerbosePrintf prints formatted text to the console when verbose flag is set.
func (cl ConsoleLogger) VerbosePrintf(format string, args ...interface{}) {
if cl.verbose {
cl.WithMetadataMode(true).Printf(format, args...)
}
}
// VerboseBytes prints bytes directly to the console when verbose flag is set.
func (cl ConsoleLogger) VerboseBytes(data []byte) {
if cl.verbose {
cl.WithMetadataMode(true).PrintBytes(data)
}
}
func (cl ConsoleLogger) printPrefix(useErrWriter bool) {
var w io.Writer
if useErrWriter {
w = cl.errW
} else {
w = cl.outW
}
// Assumes mu locked.
if cl.prefix == "" {
return
}
c := cl.PrefixColor()
c.Fprintf(w, cl.prettyPrefix())
if cl.isLocal {
w.Write([]byte(" *"))
cl.color(localColor).Fprintf(w, "local")
w.Write([]byte("*"))
}
if cl.isFailed {
w.Write([]byte(" *"))
cl.color(warnColor).Fprintf(w, "failed")
w.Write([]byte("*"))
}
w.Write([]byte(" | "))
if cl.isCached {
w.Write([]byte("*"))
cl.color(cachedColor).Fprintf(w, "cached")
w.Write([]byte("* "))
}
}
func (cl ConsoleLogger) color(c *color.Color) *color.Color {
switch cl.colorMode {
case NoColor:
return noColor
case ForceColor:
return c
case AutoColor:
if color.NoColor {
return noColor
}
return c
}
return noColor
}
func (cl ConsoleLogger) prettyPrefix() string {
if cl.prefixPadding == NoPadding {
return cl.prefix
}
var brackets string
bracketParts := strings.SplitN(cl.prefix, "(", 2)
if len(bracketParts) > 1 {
brackets = fmt.Sprintf("(%s", bracketParts[1])
}
prettyPrefix := bracketParts[0]
if len(cl.prefix) > cl.prefixPadding {
parts := strings.Split(cl.prefix, "/")
target := parts[len(parts)-1]
truncated := ""
for _, part := range parts[:len(parts)-1] {
letter := part
if len(part) > 0 && part != ".." {
letter = string(part[0])
}
truncated += letter + "/"
}
prettyPrefix = truncated + target
}
formatString := fmt.Sprintf("%%%vv", cl.prefixPadding)
return fmt.Sprintf(formatString, fmt.Sprintf("%s%s", prettyPrefix, brackets))
}
// WithVerbose toggles the verbose level
func (cl ConsoleLogger) WithVerbose(verbose bool) ConsoleLogger {
ret := cl.clone()
ret.verbose = verbose
return ret
}