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  }