github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/logger/logger.go (about) 1 package logger 2 3 import ( 4 "context" 5 "io" 6 "os" 7 8 "github.com/fatih/color" 9 10 "github.com/mattn/go-isatty" 11 ) 12 13 // Logger with better controls for levels and colors. 14 // 15 // Note that our loggers often serve as both traditional loggers (where each 16 // call to PodLog() is a discrete log entry that may be emitted as JSON or with 17 // newlines) and as Writers (where each call to Write() may be part of a larger 18 // output stream, and each message may not end in a newline). 19 // 20 // Logger implementations that bridge these two worlds should have discrete 21 // messages (like Infof) append a newline to the string before passing it to 22 // Write(). 23 type Logger interface { 24 // log information that is likely to only be of interest to tilt developers 25 Debugf(format string, a ...interface{}) 26 27 // log information that a tilt user might not want to see on every run, but that they might find 28 // useful when debugging their Tiltfile/docker/k8s configs 29 Verbosef(format string, a ...interface{}) 30 31 // log information that we always want to show 32 Infof(format string, a ...interface{}) 33 34 // Warnings to show in the alert pane. 35 Warnf(format string, a ...interface{}) 36 37 // Halting errors to show in the alert pane. 38 Errorf(format string, a ...interface{}) 39 40 Write(level Level, bytes []byte) 41 42 // gets an io.Writer that filters to the specified level for, e.g., passing to a subprocess 43 Writer(level Level) io.Writer 44 45 Level() Level 46 47 SupportsColor() bool 48 49 WithFields(fields Fields) Logger 50 } 51 52 type LogHandler interface { 53 Write(level Level, fields Fields, bytes []byte) error 54 } 55 56 type Level struct { 57 // UGH, for backwards-compatibility, the serialized value doesn't say anything 58 // about relative priority. 59 id int32 60 61 severity int32 62 } 63 64 func (l Level) ToProtoID() int32 { 65 return l.id 66 } 67 68 // If l is the logger level, determine if we should display 69 // logs of the given severity. 70 func (l Level) ShouldDisplay(log Level) bool { 71 return l.severity <= log.severity 72 } 73 74 func (l Level) AsSevereAs(log Level) bool { 75 return l.severity >= log.severity 76 } 77 78 var ( 79 NoneLvl = Level{id: 0, severity: 0} 80 DebugLvl = Level{id: 3, severity: 100} 81 VerboseLvl = Level{id: 2, severity: 200} 82 InfoLvl = Level{id: 1, severity: 300} 83 WarnLvl = Level{id: 4, severity: 400} 84 ErrorLvl = Level{id: 5, severity: 500} 85 ) 86 87 type contextKey struct{} 88 89 var LoggerContextKey = contextKey{} 90 91 func Get(ctx context.Context) Logger { 92 val := ctx.Value(LoggerContextKey) 93 94 if val != nil { 95 return val.(Logger) 96 } 97 98 // No logger found in context, something is wrong. 99 panic("Called logger.Get(ctx) on a context with no logger attached!") 100 } 101 102 func NewLogger(minLevel Level, writer io.Writer) Logger { 103 // adapted from fatih/color 104 supportsColor := true 105 if os.Getenv("TERM") == "dumb" { 106 supportsColor = false 107 } else { 108 file, isFile := writer.(*os.File) 109 if isFile { 110 fd := file.Fd() 111 supportsColor = isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) 112 } 113 } 114 return NewFuncLogger(supportsColor, minLevel, func(level Level, fields Fields, bytes []byte) error { 115 _, err := writer.Write(bytes) 116 return err 117 }) 118 } 119 120 func NewTestLogger(writer io.Writer) Logger { 121 return NewFuncLogger(false, DebugLvl, func(level Level, fields Fields, bytes []byte) error { 122 _, err := writer.Write(bytes) 123 return err 124 }) 125 } 126 127 func WithLogger(ctx context.Context, logger Logger) context.Context { 128 return context.WithValue(ctx, LoggerContextKey, logger) 129 } 130 131 func getColor(l Logger, c color.Attribute) *color.Color { 132 color := color.New(c) 133 if !l.SupportsColor() { 134 color.DisableColor() 135 } 136 return color 137 } 138 139 func Blue(l Logger) *color.Color { return getColor(l, color.FgBlue) } 140 func Yellow(l Logger) *color.Color { return getColor(l, color.FgYellow) } 141 func Green(l Logger) *color.Color { return getColor(l, color.FgGreen) } 142 func Red(l Logger) *color.Color { return getColor(l, color.FgRed) } 143 144 func CtxWithLogHandler(ctx context.Context, handler LogHandler) context.Context { 145 original := Get(ctx) 146 newLogger := NewFuncLogger(original.SupportsColor(), original.Level(), handler.Write) 147 return WithLogger(ctx, newLogger) 148 } 149 150 // Returns a context containing a logger that forks all of its output 151 // to both the parent context's logger and to the given `io.Writer` 152 func CtxWithForkedOutput(ctx context.Context, writer io.Writer) context.Context { 153 l := Get(ctx) 154 155 write := func(level Level, fields Fields, b []byte) error { 156 l.Write(level, b) 157 if l.Level().ShouldDisplay(level) { 158 b = append([]byte{}, b...) 159 _, err := writer.Write(b) 160 if err != nil { 161 return err 162 } 163 } 164 return nil 165 } 166 167 forkedLogger := NewFuncLogger(l.SupportsColor(), l.Level(), write) 168 return WithLogger(ctx, forkedLogger) 169 }