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 }