github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/output/log/log.go (about) 1 /* 2 Copyright 2021 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Includes code from github.com/sirupsen/logrus (MIT License) 18 19 package log 20 21 import ( 22 "context" 23 "fmt" 24 "io" 25 stdlog "log" 26 27 ggcrlogs "github.com/google/go-containerregistry/pkg/logs" 28 "github.com/sirupsen/logrus" 29 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" 31 ) 32 33 // Logging levels. Defining our own so we can encapsulate the underlying logger implementation. 34 const ( 35 // PanicLevel level, highest level of severity. Logs and then calls panic with the 36 // message passed to Debug, Info, ... 37 PanicLevel Level = iota 38 // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the 39 // logging level is set to Panic. 40 FatalLevel 41 // ErrorLevel level. Logs. Used for errors that should definitely be noted. 42 // Commonly used for hooks to send errors to an error tracking service. 43 ErrorLevel 44 // WarnLevel level. Non-critical entries that deserve eyes. 45 WarnLevel 46 // InfoLevel level. General operational entries about what's going on inside the 47 // application. 48 InfoLevel 49 // DebugLevel level. Usually only enabled when debugging. Very verbose logging. 50 DebugLevel 51 // TraceLevel level. Designates finer-grained informational events than the Debug. 52 TraceLevel 53 ) 54 55 // Level type for logging levels 56 type Level uint32 57 58 // AllLevels exposes all logging levels 59 var AllLevels = []Level{ 60 PanicLevel, 61 FatalLevel, 62 ErrorLevel, 63 WarnLevel, 64 InfoLevel, 65 DebugLevel, 66 TraceLevel, 67 } 68 69 // DefaultLogLevel for the global Skaffold logger 70 const DefaultLogLevel = WarnLevel 71 72 type contextKey struct{} 73 74 var ContextKey = contextKey{} 75 76 // logger is the global logrus.Logger for Skaffold 77 // TODO: Make this not global. 78 var logger = New() 79 80 type EventContext struct { 81 Task constants.Phase 82 Subtask string 83 } 84 85 // String converts the Level to a string. E.g. PanicLevel becomes "panic". 86 func (level Level) String() string { 87 switch level { 88 case TraceLevel: 89 return "trace" 90 case DebugLevel: 91 return "debug" 92 case InfoLevel: 93 return "info" 94 case WarnLevel: 95 return "warning" 96 case ErrorLevel: 97 return "error" 98 case FatalLevel: 99 return "fatal" 100 case PanicLevel: 101 return "panic" 102 } 103 return "unknown" 104 } 105 106 // Entry takes an context.Context and constructs a logrus.Entry from it, adding 107 // fields for task and subtask information 108 func Entry(ctx context.Context) *logrus.Entry { 109 val := ctx.Value(ContextKey) 110 if eventContext, ok := val.(EventContext); ok { 111 return logger.WithFields(logrus.Fields{ 112 "task": eventContext.Task, 113 "subtask": eventContext.Subtask, 114 }) 115 } 116 117 // Use constants.DevLoop as the default task, as it's the highest level task we 118 // can default to if one isn't specified. 119 return logger.WithFields(logrus.Fields{ 120 "task": constants.DevLoop, 121 "subtask": constants.SubtaskIDNone, 122 }) 123 } 124 125 // IsDebugLevelEnabled returns true if debug level log is enabled. 126 func IsDebugLevelEnabled() bool { 127 return logger.IsLevelEnabled(logrus.DebugLevel) 128 } 129 130 // IsTraceLevelEnabled returns true if trace level log is enabled. 131 func IsTraceLevelEnabled() bool { 132 return logger.IsLevelEnabled(logrus.TraceLevel) 133 } 134 135 // New returns a new logrus.Logger. 136 // We use a new instance instead of the default logrus singleton to avoid clashes with dependencies that also use logrus. 137 func New() *logrus.Logger { 138 return logrus.New() 139 } 140 141 // KanikoLogLevel makes sure kaniko logs at least at Info level and at most Debug level (trace doesn't work with Kaniko) 142 func KanikoLogLevel() logrus.Level { 143 if GetLevel() <= InfoLevel { 144 return logrus.InfoLevel 145 } 146 return logrus.DebugLevel 147 } 148 149 // SetupLogs sets up logrus logger for skaffold command line 150 func SetupLogs(stdErr io.Writer, level string, timestamp bool, hook logrus.Hook) error { 151 logger.SetOutput(stdErr) 152 lvl, err := logrus.ParseLevel(level) 153 if err != nil { 154 return fmt.Errorf("parsing log level: %w", err) 155 } 156 logger.SetLevel(lvl) 157 logger.SetFormatter(&logrus.TextFormatter{ 158 FullTimestamp: timestamp, 159 }) 160 logger.AddHook(hook) 161 setupStdLog(logger, lvl, stdlog.Default()) 162 setupGGCRLogging(logger, lvl) 163 return nil 164 } 165 166 // AddHook adds a hook to the global Skaffold logger. 167 func AddHook(hook logrus.Hook) { 168 logger.AddHook(hook) 169 } 170 171 // SetLevel sets the global Skaffold logger level. 172 func SetLevel(level Level) { 173 logger.SetLevel(logrus.AllLevels[level]) 174 } 175 176 // GetLevel returns the global Skaffold logger level. 177 func GetLevel() Level { 178 return AllLevels[logger.GetLevel()] 179 } 180 181 // setupStdLog writes Go's standard library `log` messages to logrus at Info level. 182 // 183 // This function uses SetFlags() to standardize the output format. 184 func setupStdLog(logger *logrus.Logger, lvl logrus.Level, stdlogger *stdlog.Logger) { 185 stdlogger.SetFlags(0) 186 if lvl >= logrus.InfoLevel { 187 stdlogger.SetOutput(logger.WriterLevel(logrus.InfoLevel)) 188 } 189 } 190 191 // setupGGCRLogging enables go-containerregistry logging, mapping its levels to our levels. 192 // 193 // The mapping is: 194 // - ggcr Warn -> Skaffold Error 195 // - ggcr Progress -> Skaffold Info 196 // - ggcr Debug -> Skaffold Trace 197 // 198 // The reasons for this mapping are: 199 // - `ggcr` defines `Warn` as "non-fatal errors": https://github.com/google/go-containerregistry/blob/main/pkg/logs/logs.go#L24 200 // - `ggcr` `Debug` logging is _very_ verbose and includes HTTP requests and responses, with non-sensitive headers and non-binary payloads. 201 // 202 // This function uses SetFlags() to standardize the output format. 203 func setupGGCRLogging(logger *logrus.Logger, lvl logrus.Level) { 204 if lvl >= logrus.ErrorLevel { 205 ggcrlogs.Warn.SetOutput(logger.WriterLevel(logrus.ErrorLevel)) 206 ggcrlogs.Warn.SetFlags(0) 207 } 208 if lvl >= logrus.InfoLevel { 209 ggcrlogs.Progress.SetOutput(logger.WriterLevel(logrus.InfoLevel)) 210 ggcrlogs.Progress.SetFlags(0) 211 } 212 if lvl >= logrus.TraceLevel { 213 ggcrlogs.Debug.SetOutput(logger.WriterLevel(logrus.TraceLevel)) 214 ggcrlogs.Debug.SetFlags(0) 215 } 216 }