istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/log/options.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
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  
    22  	"github.com/spf13/cobra"
    23  	"go.uber.org/zap/zapcore"
    24  )
    25  
    26  const (
    27  	DefaultScopeName          = "default"
    28  	OverrideScopeName         = "all"
    29  	defaultOutputLevel        = InfoLevel
    30  	defaultStackTraceLevel    = NoneLevel
    31  	defaultOutputPath         = "stdout"
    32  	defaultErrorOutputPath    = "stderr"
    33  	defaultRotationMaxAge     = 30
    34  	defaultRotationMaxSize    = 100 * 1024 * 1024
    35  	defaultRotationMaxBackups = 1000
    36  )
    37  
    38  // Level is an enumeration of all supported log levels.
    39  type Level int
    40  
    41  const (
    42  	// NoneLevel disables logging
    43  	NoneLevel Level = iota
    44  	// FatalLevel enables fatal level logging
    45  	FatalLevel
    46  	// ErrorLevel enables error level logging
    47  	ErrorLevel
    48  	// WarnLevel enables warn level logging
    49  	WarnLevel
    50  	// InfoLevel enables info level logging
    51  	InfoLevel
    52  	// DebugLevel enables debug level logging
    53  	DebugLevel
    54  )
    55  
    56  var levelToString = map[Level]string{
    57  	DebugLevel: "debug",
    58  	InfoLevel:  "info",
    59  	WarnLevel:  "warn",
    60  	ErrorLevel: "error",
    61  	FatalLevel: "fatal",
    62  	NoneLevel:  "none",
    63  }
    64  
    65  var stringToLevel = map[string]Level{
    66  	"debug": DebugLevel,
    67  	"info":  InfoLevel,
    68  	"warn":  WarnLevel,
    69  	"error": ErrorLevel,
    70  	"fatal": FatalLevel,
    71  	"none":  NoneLevel,
    72  }
    73  
    74  // Options defines the set of options supported by Istio's component logging package.
    75  type Options struct {
    76  	// OutputPaths is a list of file system paths to write the log data to.
    77  	// The special values stdout and stderr can be used to output to the
    78  	// standard I/O streams. This defaults to stdout.
    79  	OutputPaths []string
    80  
    81  	// ErrorOutputPaths is a list of file system paths to write logger errors to.
    82  	// The special values stdout and stderr can be used to output to the
    83  	// standard I/O streams. This defaults to stderr.
    84  	ErrorOutputPaths []string
    85  
    86  	// RotateOutputPath is the path to a rotating log file. This file should
    87  	// be automatically rotated over time, based on the rotation parameters such
    88  	// as RotationMaxSize and RotationMaxAge. The default is to not rotate.
    89  	//
    90  	// This path is used as a foundational path. This is where log output is normally
    91  	// saved. When a rotation needs to take place because the file got too big or too
    92  	// old, then the file is renamed by appending a timestamp to the name. Such renamed
    93  	// files are called backups. Once a backup has been created,
    94  	// output resumes to this path.
    95  	RotateOutputPath string
    96  
    97  	// RotationMaxSize is the maximum size in megabytes of a log file before it gets
    98  	// rotated. It defaults to 100 megabytes.
    99  	RotationMaxSize int
   100  
   101  	// RotationMaxAge is the maximum number of days to retain old log files based on the
   102  	// timestamp encoded in their filename. Note that a day is defined as 24
   103  	// hours and may not exactly correspond to calendar days due to daylight
   104  	// savings, leap seconds, etc. The default is to remove log files
   105  	// older than 30 days.
   106  	RotationMaxAge int
   107  
   108  	// RotationMaxBackups is the maximum number of old log files to retain.  The default
   109  	// is to retain at most 1000 logs.
   110  	RotationMaxBackups int
   111  
   112  	// JSONEncoding controls whether the log is formatted as JSON.
   113  	JSONEncoding bool
   114  
   115  	// logGRPC indicates that Grpc logs should be captured.
   116  	// This is enabled by a --log_output_level=grpc:<level> typically
   117  	logGRPC bool
   118  
   119  	outputLevels        string
   120  	defaultOutputLevels string
   121  	logCallers          string
   122  	stackTraceLevels    string
   123  
   124  	useStackdriverFormat bool
   125  	extensions           []Extension
   126  }
   127  
   128  // DefaultOptions returns a new set of options, initialized to the defaults
   129  func DefaultOptions() *Options {
   130  	return &Options{
   131  		OutputPaths:          []string{defaultOutputPath},
   132  		ErrorOutputPaths:     []string{defaultErrorOutputPath},
   133  		RotationMaxSize:      defaultRotationMaxSize,
   134  		RotationMaxAge:       defaultRotationMaxAge,
   135  		RotationMaxBackups:   defaultRotationMaxBackups,
   136  		defaultOutputLevels:  "default:info,grpc:none",
   137  		stackTraceLevels:     DefaultScopeName + ":" + levelToString[defaultStackTraceLevel],
   138  		logGRPC:              false,
   139  		useStackdriverFormat: false,
   140  	}
   141  }
   142  
   143  // WithStackdriverLoggingFormat configures logging output to match Stackdriver structured logging conventions.
   144  func (o *Options) WithStackdriverLoggingFormat() *Options {
   145  	o.useStackdriverFormat = true
   146  	return o
   147  }
   148  
   149  // WithTeeToUDS configures a parallel logging pipeline that writes logs to a server over UDS.
   150  // addr is the socket that the server listens on, and path is the HTTP path that process the log message.
   151  func (o *Options) WithTeeToUDS(addr, path string) *Options {
   152  	return o.WithExtension(func(c zapcore.Core) (zapcore.Core, func() error, error) {
   153  		return teeToUDSServer(c, addr, path), func() error { return nil }, nil
   154  	})
   155  }
   156  
   157  // Extension provides an extension mechanism for logs.
   158  // This is essentially like https://pkg.go.dev/golang.org/x/exp/slog#Handler.
   159  // This interface should be considered unstable; we will likely swap it for slog in the future and not expose zap internals.
   160  // Returns a modified Core interface, and a Close() function.
   161  type Extension func(c zapcore.Core) (zapcore.Core, func() error, error)
   162  
   163  func (o *Options) WithExtension(e Extension) *Options {
   164  	o.extensions = append(o.extensions, e)
   165  	return o
   166  }
   167  
   168  // SetDefaultOutputLevel sets the minimum log output level for a given scope.
   169  // This can be overwritten by flags
   170  func (o *Options) SetDefaultOutputLevel(scope string, level Level) {
   171  	sl := scope + ":" + levelToString[level]
   172  	levels := strings.Split(o.defaultOutputLevels, ",")
   173  	if scope == DefaultScopeName {
   174  		// see if we have an entry without a scope prefix (which represents the default scope)
   175  		for i, ol := range levels {
   176  			if !strings.Contains(ol, ":") {
   177  				levels[i] = sl
   178  				o.defaultOutputLevels = strings.Join(levels, ",")
   179  				return
   180  			}
   181  		}
   182  	}
   183  
   184  	prefix := scope + ":"
   185  	for i, ol := range levels {
   186  		if strings.HasPrefix(ol, prefix) {
   187  			levels[i] = sl
   188  			o.defaultOutputLevels = strings.Join(levels, ",")
   189  			return
   190  		}
   191  	}
   192  
   193  	levels = append(levels, sl)
   194  	o.defaultOutputLevels = strings.Join(levels, ",")
   195  }
   196  
   197  func convertScopedLevel(sl string) (string, Level, error) {
   198  	var s string
   199  	var l string
   200  
   201  	pieces := strings.Split(sl, ":")
   202  	if len(pieces) == 1 {
   203  		s = DefaultScopeName
   204  		l = pieces[0]
   205  	} else if len(pieces) == 2 {
   206  		s = pieces[0]
   207  		l = pieces[1]
   208  	} else {
   209  		return "", NoneLevel, fmt.Errorf("invalid output level format '%s'", sl)
   210  	}
   211  
   212  	level, ok := stringToLevel[l]
   213  	if !ok {
   214  		return "", NoneLevel, fmt.Errorf("invalid output level '%s'", sl)
   215  	}
   216  
   217  	return s, level, nil
   218  }
   219  
   220  // AttachCobraFlags attaches a set of Cobra flags to the given Cobra command.
   221  //
   222  // Cobra is the command-line processor that Istio uses. This command attaches
   223  // the necessary set of flags to expose a CLI to let the user control all
   224  // logging options.
   225  func (o *Options) AttachCobraFlags(cmd *cobra.Command) {
   226  	o.AttachFlags(
   227  		cmd.PersistentFlags().StringArrayVar,
   228  		cmd.PersistentFlags().StringVar,
   229  		cmd.PersistentFlags().IntVar,
   230  		cmd.PersistentFlags().BoolVar)
   231  }
   232  
   233  // AttachFlags allows attaching of flags through a set of lambda functions.
   234  func (o *Options) AttachFlags(
   235  	stringArrayVar func(p *[]string, name string, value []string, usage string),
   236  	stringVar func(p *string, name string, value string, usage string),
   237  	intVar func(p *int, name string, value int, usage string),
   238  	boolVar func(p *bool, name string, value bool, usage string),
   239  ) {
   240  	stringArrayVar(&o.OutputPaths, "log_target", o.OutputPaths,
   241  		"The set of paths where to output the log. This can be any path as well as the special values stdout and stderr")
   242  
   243  	stringVar(&o.RotateOutputPath, "log_rotate", o.RotateOutputPath,
   244  		"The path for the optional rotating log file")
   245  
   246  	intVar(&o.RotationMaxAge, "log_rotate_max_age", o.RotationMaxAge,
   247  		"The maximum age in days of a log file beyond which the file is rotated (0 indicates no limit)")
   248  
   249  	intVar(&o.RotationMaxSize, "log_rotate_max_size", o.RotationMaxSize,
   250  		"The maximum size in megabytes of a log file beyond which the file is rotated")
   251  
   252  	intVar(&o.RotationMaxBackups, "log_rotate_max_backups", o.RotationMaxBackups,
   253  		"The maximum number of log file backups to keep before older files are deleted (0 indicates no limit)")
   254  
   255  	boolVar(&o.JSONEncoding, "log_as_json", o.JSONEncoding,
   256  		"Whether to format output as JSON or in plain console-friendly format")
   257  
   258  	levelListString := fmt.Sprintf("[%s, %s, %s, %s, %s, %s]",
   259  		levelToString[DebugLevel],
   260  		levelToString[InfoLevel],
   261  		levelToString[WarnLevel],
   262  		levelToString[ErrorLevel],
   263  		levelToString[FatalLevel],
   264  		levelToString[NoneLevel])
   265  
   266  	allScopes := Scopes()
   267  	if len(allScopes) > 1 {
   268  		keys := make([]string, 0, len(allScopes))
   269  		for name := range allScopes {
   270  			keys = append(keys, name)
   271  		}
   272  		keys = append(keys, OverrideScopeName)
   273  		sort.Strings(keys)
   274  		s := strings.Join(keys, ", ")
   275  
   276  		stringVar(&o.outputLevels, "log_output_level", o.outputLevels,
   277  			fmt.Sprintf("Comma-separated minimum per-scope logging level of messages to output, in the form of "+
   278  				"<scope>:<level>,<scope>:<level>,... where scope can be one of [%s] and level can be one of %s",
   279  				s, levelListString))
   280  
   281  		stringVar(&o.stackTraceLevels, "log_stacktrace_level", o.stackTraceLevels,
   282  			fmt.Sprintf("Comma-separated minimum per-scope logging level at which stack traces are captured, in the form of "+
   283  				"<scope>:<level>,<scope:level>,... where scope can be one of [%s] and level can be one of %s",
   284  				s, levelListString))
   285  
   286  		stringVar(&o.logCallers, "log_caller", o.logCallers,
   287  			fmt.Sprintf("Comma-separated list of scopes for which to include caller information, scopes can be any of [%s]", s))
   288  	} else {
   289  		stringVar(&o.outputLevels, "log_output_level", o.outputLevels,
   290  			fmt.Sprintf("The minimum logging level of messages to output,  can be one of %s",
   291  				levelListString))
   292  
   293  		stringVar(&o.stackTraceLevels, "log_stacktrace_level", o.stackTraceLevels,
   294  			fmt.Sprintf("The minimum logging level at which stack traces are captured, can be one of %s",
   295  				levelListString))
   296  
   297  		stringVar(&o.logCallers, "log_caller", o.logCallers,
   298  			"Comma-separated list of scopes for which to include called information, scopes can be any of [default]")
   299  	}
   300  
   301  	// NOTE: we don't currently expose a command-line option to control ErrorOutputPaths since it
   302  	// seems too esoteric.
   303  }