istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/log/config.go (about)

     1  // Copyright 2017 Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package log provides the canonical logging functionality used by Go-based
    16  // Istio components.
    17  //
    18  // Istio's logging subsystem is built on top of the [Zap](https://godoc.org/go.uber.org/zap) package.
    19  // High performance scenarios should use the Error, Warn, Info, and Debug methods. Lower perf
    20  // scenarios can use the more expensive convenience methods such as Debugf and Warnw.
    21  //
    22  // The package provides direct integration with the Cobra command-line processor which makes it
    23  // easy to build programs that use a consistent interface for logging. Here's an example
    24  // of a simple Cobra-based program using this log package:
    25  //
    26  //			func main() {
    27  //				// get the default logging options
    28  //				options := log.DefaultOptions()
    29  //
    30  //				rootCmd := &cobra.Command{
    31  //					Run: func(cmd *cobra.Command, args []string) {
    32  //
    33  //						// configure the logging system
    34  //						if err := log.Configure(options); err != nil {
    35  //	                     // print an error and quit
    36  //	                 }
    37  //
    38  //						// output some logs
    39  //						log.Info("Hello")
    40  //						log.Sync()
    41  //					},
    42  //				}
    43  //
    44  //				// add logging-specific flags to the cobra command
    45  //				options.AttachCobraFlags(rootCmd)
    46  //				rootCmd.SetArgs(os.Args[1:])
    47  //				rootCmd.Execute()
    48  //			}
    49  //
    50  // Once configured, this package intercepts the output of the standard golang "log" package as well as anything
    51  // sent to the global zap logger (zap.L()).
    52  package log
    53  
    54  import (
    55  	"os"
    56  	"strings"
    57  	"sync/atomic"
    58  	"time"
    59  
    60  	"go.uber.org/zap"
    61  	"go.uber.org/zap/zapcore"
    62  	"go.uber.org/zap/zapgrpc"
    63  	"google.golang.org/grpc/grpclog"
    64  	"gopkg.in/natefinch/lumberjack.v2"
    65  	"k8s.io/klog/v2"
    66  )
    67  
    68  const (
    69  	// none is used to disable logging output as well as to disable stack tracing.
    70  	none          zapcore.Level = 100
    71  	GrpcScopeName string        = "grpc"
    72  )
    73  
    74  var levelToZap = map[Level]zapcore.Level{
    75  	DebugLevel: zapcore.DebugLevel,
    76  	InfoLevel:  zapcore.InfoLevel,
    77  	WarnLevel:  zapcore.WarnLevel,
    78  	ErrorLevel: zapcore.ErrorLevel,
    79  	FatalLevel: zapcore.FatalLevel,
    80  	NoneLevel:  none,
    81  }
    82  
    83  var defaultEncoderConfig = zapcore.EncoderConfig{
    84  	TimeKey:        "time",
    85  	LevelKey:       "level",
    86  	NameKey:        "scope",
    87  	CallerKey:      "caller",
    88  	MessageKey:     "msg",
    89  	StacktraceKey:  "stack",
    90  	LineEnding:     zapcore.DefaultLineEnding,
    91  	EncodeLevel:    zapcore.LowercaseLevelEncoder,
    92  	EncodeCaller:   zapcore.ShortCallerEncoder,
    93  	EncodeDuration: zapcore.StringDurationEncoder,
    94  	EncodeTime:     formatDate,
    95  }
    96  
    97  // functions that can be replaced in a test setting
    98  type patchTable struct {
    99  	write       func(ent zapcore.Entry, fields []zapcore.Field) error
   100  	sync        func() error
   101  	exitProcess func(code int)
   102  	errorSink   zapcore.WriteSyncer
   103  	close       func() error
   104  }
   105  
   106  var (
   107  	// function table that can be replaced by tests
   108  	funcs = &atomic.Value{}
   109  	// controls whether all output is JSON or CLI style. This makes it easier to query how the zap encoder is configured
   110  	// vs. reading it's internal state.
   111  	useJSON atomic.Value
   112  )
   113  
   114  func init() {
   115  	// use our defaults for starters so that logging works even before everything is fully configured
   116  	_ = Configure(DefaultOptions())
   117  }
   118  
   119  // See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
   120  var stackdriverSeverityMapping = map[zapcore.Level]string{
   121  	zapcore.DebugLevel:  "Debug",
   122  	zapcore.InfoLevel:   "Info",
   123  	zapcore.WarnLevel:   "Warning",
   124  	zapcore.ErrorLevel:  "Error",
   125  	zapcore.DPanicLevel: "Critical",
   126  	zapcore.FatalLevel:  "Critical",
   127  	zapcore.PanicLevel:  "Critical",
   128  }
   129  
   130  func encodeStackdriverLevel(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
   131  	enc.AppendString(stackdriverSeverityMapping[l])
   132  }
   133  
   134  // prepZap sets up the core Zap loggers
   135  func prepZap(options *Options) (zapcore.Core, func(string) zapcore.Core, zapcore.WriteSyncer, error) {
   136  	var enc zapcore.Encoder
   137  	if options.useStackdriverFormat {
   138  		// See also: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
   139  		encCfg := zapcore.EncoderConfig{
   140  			TimeKey:        "timestamp",
   141  			LevelKey:       "severity",
   142  			NameKey:        "logger",
   143  			CallerKey:      "caller",
   144  			MessageKey:     "message",
   145  			StacktraceKey:  "stacktrace",
   146  			LineEnding:     zapcore.DefaultLineEnding,
   147  			EncodeLevel:    encodeStackdriverLevel,
   148  			EncodeTime:     zapcore.RFC3339NanoTimeEncoder,
   149  			EncodeDuration: zapcore.SecondsDurationEncoder,
   150  			EncodeCaller:   zapcore.ShortCallerEncoder,
   151  		}
   152  		enc = zapcore.NewJSONEncoder(encCfg)
   153  		useJSON.Store(true)
   154  	} else {
   155  		encCfg := defaultEncoderConfig
   156  
   157  		if options.JSONEncoding {
   158  			enc = zapcore.NewJSONEncoder(encCfg)
   159  			useJSON.Store(true)
   160  		} else {
   161  			enc = zapcore.NewConsoleEncoder(encCfg)
   162  			useJSON.Store(false)
   163  		}
   164  	}
   165  
   166  	var rotaterSink zapcore.WriteSyncer
   167  	if options.RotateOutputPath != "" {
   168  		rotaterSink = zapcore.AddSync(&lumberjack.Logger{
   169  			Filename:   options.RotateOutputPath,
   170  			MaxSize:    options.RotationMaxSize,
   171  			MaxBackups: options.RotationMaxBackups,
   172  			MaxAge:     options.RotationMaxAge,
   173  		})
   174  	}
   175  
   176  	errSink, closeErrorSink, err := zap.Open(options.ErrorOutputPaths...)
   177  	if err != nil {
   178  		return nil, nil, nil, err
   179  	}
   180  
   181  	var outputSink zapcore.WriteSyncer
   182  	if len(options.OutputPaths) > 0 {
   183  		outputSink, _, err = zap.Open(options.OutputPaths...)
   184  		if err != nil {
   185  			closeErrorSink()
   186  			return nil, nil, nil, err
   187  		}
   188  	}
   189  
   190  	var sink zapcore.WriteSyncer
   191  	if rotaterSink != nil && outputSink != nil {
   192  		sink = zapcore.NewMultiWriteSyncer(outputSink, rotaterSink)
   193  	} else if rotaterSink != nil {
   194  		sink = rotaterSink
   195  	} else {
   196  		sink = outputSink
   197  	}
   198  
   199  	alwaysOn := zapcore.NewCore(enc, sink, zap.NewAtomicLevelAt(zapcore.DebugLevel))
   200  	conditionallyOn := func(scopeName string) zapcore.Core {
   201  		scope := FindScope(scopeName)
   202  		enabler := func(lvl zapcore.Level) bool {
   203  			switch lvl {
   204  			case zapcore.ErrorLevel:
   205  				return scope.ErrorEnabled()
   206  			case zapcore.WarnLevel:
   207  				return scope.WarnEnabled()
   208  			case zapcore.InfoLevel:
   209  				return scope.InfoEnabled()
   210  			}
   211  			return scope.DebugEnabled()
   212  		}
   213  		return zapcore.NewCore(enc, sink, zap.LevelEnablerFunc(enabler))
   214  	}
   215  	return alwaysOn,
   216  		conditionallyOn,
   217  		errSink, nil
   218  }
   219  
   220  func formatDate(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
   221  	t = t.UTC()
   222  	year, month, day := t.Date()
   223  	hour, minute, second := t.Clock()
   224  	micros := t.Nanosecond() / 1000
   225  
   226  	buf := make([]byte, 27)
   227  
   228  	buf[0] = byte((year/1000)%10) + '0'
   229  	buf[1] = byte((year/100)%10) + '0'
   230  	buf[2] = byte((year/10)%10) + '0'
   231  	buf[3] = byte(year%10) + '0'
   232  	buf[4] = '-'
   233  	buf[5] = byte((month)/10) + '0'
   234  	buf[6] = byte((month)%10) + '0'
   235  	buf[7] = '-'
   236  	buf[8] = byte((day)/10) + '0'
   237  	buf[9] = byte((day)%10) + '0'
   238  	buf[10] = 'T'
   239  	buf[11] = byte((hour)/10) + '0'
   240  	buf[12] = byte((hour)%10) + '0'
   241  	buf[13] = ':'
   242  	buf[14] = byte((minute)/10) + '0'
   243  	buf[15] = byte((minute)%10) + '0'
   244  	buf[16] = ':'
   245  	buf[17] = byte((second)/10) + '0'
   246  	buf[18] = byte((second)%10) + '0'
   247  	buf[19] = '.'
   248  	buf[20] = byte((micros/100000)%10) + '0'
   249  	buf[21] = byte((micros/10000)%10) + '0'
   250  	buf[22] = byte((micros/1000)%10) + '0'
   251  	buf[23] = byte((micros/100)%10) + '0'
   252  	buf[24] = byte((micros/10)%10) + '0'
   253  	buf[25] = byte((micros)%10) + '0'
   254  	buf[26] = 'Z'
   255  
   256  	enc.AppendString(string(buf))
   257  }
   258  
   259  func updateScopes(options *Options) error {
   260  	// snapshot what's there
   261  	allScopes := Scopes()
   262  
   263  	// Join defaultOutputLevels and outputLevels
   264  	levels := options.defaultOutputLevels
   265  	if levels == "" {
   266  		levels = options.outputLevels
   267  	} else if options.outputLevels != "" {
   268  		levels = levels + "," + options.outputLevels
   269  	}
   270  
   271  	// update the output levels of all listed scopes
   272  	if err := processLevels(allScopes, levels, func(s *Scope, l Level) { s.SetOutputLevel(l) }); err != nil {
   273  		return err
   274  	}
   275  
   276  	// update the stack tracing levels of all listed scopes
   277  	if err := processLevels(allScopes, options.stackTraceLevels, func(s *Scope, l Level) { s.SetStackTraceLevel(l) }); err != nil {
   278  		return err
   279  	}
   280  
   281  	// update the caller location setting of all listed scopes
   282  	sc := strings.Split(options.logCallers, ",")
   283  	for _, s := range sc {
   284  		if s == "" {
   285  			continue
   286  		}
   287  
   288  		if s == OverrideScopeName {
   289  			// ignore everything else and just apply the override value
   290  			for _, scope := range allScopes {
   291  				scope.SetLogCallers(true)
   292  			}
   293  		}
   294  
   295  		if scope, ok := allScopes[s]; ok {
   296  			scope.SetLogCallers(true)
   297  		}
   298  	}
   299  
   300  	// If gRPC logging is enabled, turn on gRPC logging automatically
   301  	if grpcScope.GetOutputLevel() != NoneLevel {
   302  		options.logGRPC = true
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  // processLevels breaks down an argument string into a set of scope & levels and then
   309  // tries to apply the result to the scopes. It supports the use of a global override.
   310  func processLevels(allScopes map[string]*Scope, arg string, setter func(*Scope, Level)) error {
   311  	levels := strings.Split(arg, ",")
   312  	for _, sl := range levels {
   313  		s, l, err := convertScopedLevel(sl)
   314  		if err != nil {
   315  			return err
   316  		}
   317  
   318  		if scope, ok := allScopes[s]; ok {
   319  			setter(scope, l)
   320  		} else if s == OverrideScopeName {
   321  			// override replaces everything
   322  			for _, scope := range allScopes {
   323  				setter(scope, l)
   324  			}
   325  		}
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  // Configure initializes Istio's logging subsystem.
   332  //
   333  // You typically call this once at process startup.
   334  // Once this call returns, the logging system is ready to accept data.
   335  // nolint: staticcheck
   336  func Configure(options *Options) error {
   337  	if err := updateScopes(options); err != nil {
   338  		return err
   339  	}
   340  	baseLogger, logBuilder, errSink, err := prepZap(options)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	defaultLogger := logBuilder(DefaultScopeName)
   345  	allLoggers := []*zapcore.Core{&baseLogger, &defaultLogger}
   346  
   347  	var grpcLogger zapcore.Core
   348  	if options.logGRPC {
   349  		grpcLogger = logBuilder(GrpcScopeName)
   350  		allLoggers = append(allLoggers, &grpcLogger)
   351  	}
   352  
   353  	closeFns := make([]func() error, 0)
   354  
   355  	for _, ext := range options.extensions {
   356  		for _, logger := range allLoggers {
   357  			newLogger, closeFn, err := ext(*logger)
   358  			if err != nil {
   359  				return err
   360  			}
   361  			*logger = newLogger
   362  			closeFns = append(closeFns, closeFn)
   363  		}
   364  	}
   365  
   366  	pt := patchTable{
   367  		write: func(ent zapcore.Entry, fields []zapcore.Field) error {
   368  			err := baseLogger.Write(ent, fields)
   369  			if ent.Level == zapcore.FatalLevel {
   370  				funcs.Load().(patchTable).exitProcess(1)
   371  			}
   372  
   373  			return err
   374  		},
   375  		sync:        baseLogger.Sync,
   376  		exitProcess: os.Exit,
   377  		errorSink:   errSink,
   378  		close: func() error {
   379  			// best-effort to sync
   380  			baseLogger.Sync() // nolint: errcheck
   381  			for _, f := range closeFns {
   382  				if err := f(); err != nil {
   383  					return err
   384  				}
   385  			}
   386  			return nil
   387  		},
   388  	}
   389  	funcs.Store(pt)
   390  
   391  	opts := []zap.Option{
   392  		zap.ErrorOutput(errSink),
   393  		zap.AddCallerSkip(1),
   394  	}
   395  
   396  	if defaultScope.GetLogCallers() {
   397  		opts = append(opts, zap.AddCaller())
   398  	}
   399  
   400  	l := defaultScope.GetStackTraceLevel()
   401  	if l != NoneLevel {
   402  		opts = append(opts, zap.AddStacktrace(levelToZap[l]))
   403  	}
   404  
   405  	defaultZapLogger := zap.New(defaultLogger, opts...)
   406  
   407  	// capture global zap logging and force it through our logger
   408  	_ = zap.ReplaceGlobals(defaultZapLogger)
   409  
   410  	// capture standard golang "log" package output and force it through our logger
   411  	_ = zap.RedirectStdLog(defaultZapLogger)
   412  
   413  	// capture gRPC logging
   414  	if options.logGRPC {
   415  		grpclog.SetLoggerV2(zapgrpc.NewLogger(zap.New(grpcLogger, opts...).WithOptions(zap.AddCallerSkip(3))))
   416  	}
   417  
   418  	// capture klog (Kubernetes logging) through our logging
   419  	configureKlog.Do(func() {
   420  		klog.SetLogger(NewLogrAdapter(KlogScope))
   421  	})
   422  	// --vklog is non zero then KlogScope should be increased.
   423  	// klog is a special case.
   424  	if klogVerbose() {
   425  		KlogScope.SetOutputLevel(DebugLevel)
   426  	}
   427  
   428  	return nil
   429  }
   430  
   431  // Sync flushes any buffered log entries.
   432  // Processes should normally take care to call Sync before exiting.
   433  func Sync() error {
   434  	return funcs.Load().(patchTable).sync()
   435  }
   436  
   437  // Close implements io.Closer.
   438  func Close() error {
   439  	return funcs.Load().(patchTable).close()
   440  }