-
Notifications
You must be signed in to change notification settings - Fork 290
/
logger.go
188 lines (153 loc) · 4.67 KB
/
logger.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
package logger
import (
"context"
"fmt"
"io"
"os"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
)
// Logger with better controls for levels and colors.
//
// Note that our loggers often serve as both traditional loggers (where each
// call to PodLog() is a discrete log entry that may be emitted as JSON or with
// newlines) and as Writers (where each call to Write() may be part of a larger
// output stream, and each message may not end in a newline).
//
// Logger implementations that bridge these two worlds should have discrete
// messages (like Infof) append a newline to the string before passing it to
// Write().
type Logger interface {
// log information that we always want to show
Infof(format string, a ...interface{})
// log information that a tilt user might not want to see on every run, but that they might find
// useful when debugging their Tiltfile/docker/k8s configs
Verbosef(format string, a ...interface{})
// log information that is likely to only be of interest to tilt developers
Debugf(format string, a ...interface{})
Write(level Level, s string)
// gets an io.Writer that filters to the specified level for, e.g., passing to a subprocess
Writer(level Level) io.Writer
Level() Level
SupportsColor() bool
}
var _ Logger = logger{}
type Level int
const (
NoneLvl = iota
InfoLvl
VerboseLvl
DebugLvl
)
const loggerContextKey = "Logger"
func Get(ctx context.Context) Logger {
val := ctx.Value(loggerContextKey)
if val != nil {
return val.(Logger)
}
// No logger found in context, something is wrong.
panic("Called logger.Get(ctx) on a context with no logger attached!")
}
func NewLogger(level Level, writer io.Writer) Logger {
// adapted from fatih/color
supportsColor := true
if os.Getenv("TERM") == "dumb" {
supportsColor = false
} else {
file, isFile := writer.(*os.File)
if isFile {
fd := file.Fd()
supportsColor = isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
}
}
return logger{level, writer, supportsColor}
}
func WithLogger(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerContextKey, logger)
}
type logger struct {
level Level
writer io.Writer
supportsColor bool
}
func (l logger) Level() Level {
return l.level
}
func (l logger) Infof(format string, a ...interface{}) {
l.writef(InfoLvl, format+"\n", a...)
}
func (l logger) Verbosef(format string, a ...interface{}) {
l.writef(VerboseLvl, format+"\n", a...)
}
func (l logger) Debugf(format string, a ...interface{}) {
l.writef(DebugLvl, format+"\n", a...)
}
func (l logger) writef(level Level, format string, a ...interface{}) {
if l.level >= level {
// swallowing errors because:
// 1) if we can't write to the log, what else are we going to do?
// 2) a logger interface that returns error becomes really distracting at call sites,
// increasing friction and reducing logging
_, _ = fmt.Fprintf(l.writer, format, a...)
}
}
func (l logger) Write(level Level, s string) {
if l.level >= level {
// swallowing errors because:
// 1) if we can't write to the log, what else are we going to do?
// 2) a logger interface that returns error becomes really distracting at call sites,
// increasing friction and reducing logging
_, _ = fmt.Fprintf(l.writer, s)
}
}
type levelWriter struct {
logger logger
level Level
}
var _ io.Writer = levelWriter{}
func (lw levelWriter) Write(p []byte) (n int, err error) {
if lw.logger.level >= lw.level {
return lw.logger.writer.Write(p)
} else {
return len(p), nil
}
}
func (l logger) Writer(level Level) io.Writer {
return levelWriter{l, level}
}
func (l logger) SupportsColor() bool {
return l.supportsColor
}
func getColor(l Logger, c color.Attribute) *color.Color {
color := color.New(c)
if !l.SupportsColor() {
color.DisableColor()
}
return color
}
func Blue(l Logger) *color.Color { return getColor(l, color.FgBlue) }
func Yellow(l Logger) *color.Color { return getColor(l, color.FgYellow) }
func Green(l Logger) *color.Color { return getColor(l, color.FgGreen) }
func Red(l Logger) *color.Color { return getColor(l, color.FgRed) }
// Returns a context containing a logger that forks all of its output
// to both the parent context's logger and to the given `io.Writer`
func CtxWithForkedOutput(ctx context.Context, writer io.Writer) context.Context {
l := Get(ctx)
write := func(level Level, b []byte) error {
l.Write(level, string(b))
if l.Level() >= level {
b = append([]byte{}, b...)
_, err := writer.Write(b)
if err != nil {
return err
}
}
return nil
}
forkedLogger := funcLogger{
supportsColor: l.SupportsColor(),
level: l.Level(),
write: write,
}
return WithLogger(ctx, forkedLogger)
}