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 15package log 16 17import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "github.com/spf13/cobra" 23) 24 25const ( 26 DefaultScopeName = "default" 27 OverrideScopeName = "all" 28 defaultOutputLevel = InfoLevel 29 defaultStackTraceLevel = NoneLevel 30 defaultOutputPath = "stdout" 31 defaultErrorOutputPath = "stderr" 32 defaultRotationMaxAge = 30 33 defaultRotationMaxSize = 100 * 1024 * 1024 34 defaultRotationMaxBackups = 1000 35) 36 37// Level is an enumeration of all supported log levels. 38type Level int 39 40const ( 41 // NoneLevel disables logging 42 NoneLevel Level = iota 43 // FatalLevel enables fatal level logging 44 FatalLevel 45 // ErrorLevel enables error level logging 46 ErrorLevel 47 // WarnLevel enables warn level logging 48 WarnLevel 49 // InfoLevel enables info level logging 50 InfoLevel 51 // DebugLevel enables debug level logging 52 DebugLevel 53) 54 55var levelToString = map[Level]string{ 56 DebugLevel: "debug", 57 InfoLevel: "info", 58 WarnLevel: "warn", 59 ErrorLevel: "error", 60 FatalLevel: "fatal", 61 NoneLevel: "none", 62} 63 64var stringToLevel = map[string]Level{ 65 "debug": DebugLevel, 66 "info": InfoLevel, 67 "warn": WarnLevel, 68 "error": ErrorLevel, 69 "fatal": FatalLevel, 70 "none": NoneLevel, 71} 72 73// Options defines the set of options supported by Istio's component logging package. 74type Options struct { 75 // OutputPaths is a list of file system paths to write the log data to. 76 // The special values stdout and stderr can be used to output to the 77 // standard I/O streams. This defaults to stdout. 78 OutputPaths []string 79 80 // ErrorOutputPaths is a list of file system paths to write logger errors to. 81 // The special values stdout and stderr can be used to output to the 82 // standard I/O streams. This defaults to stderr. 83 ErrorOutputPaths []string 84 85 // RotateOutputPath is the path to a rotating log file. This file should 86 // be automatically rotated over time, based on the rotation parameters such 87 // as RotationMaxSize and RotationMaxAge. The default is to not rotate. 88 // 89 // This path is used as a foundational path. This is where log output is normally 90 // saved. When a rotation needs to take place because the file got too big or too 91 // old, then the file is renamed by appending a timestamp to the name. Such renamed 92 // files are called backups. Once a backup has been created, 93 // output resumes to this path. 94 RotateOutputPath string 95 96 // RotationMaxSize is the maximum size in megabytes of a log file before it gets 97 // rotated. It defaults to 100 megabytes. 98 RotationMaxSize int 99 100 // RotationMaxAge is the maximum number of days to retain old log files based on the 101 // timestamp encoded in their filename. Note that a day is defined as 24 102 // hours and may not exactly correspond to calendar days due to daylight 103 // savings, leap seconds, etc. The default is to remove log files 104 // older than 30 days. 105 RotationMaxAge int 106 107 // RotationMaxBackups is the maximum number of old log files to retain. The default 108 // is to retain at most 1000 logs. 109 RotationMaxBackups int 110 111 // JSONEncoding controls whether the log is formatted as JSON. 112 JSONEncoding bool 113 114 // LogGrpc indicates that Grpc logs should be captured. The default is true. 115 // This is not exposed through the command-line flags, as this flag is mainly useful for testing: Grpc 116 // stack will hold on to the logger even though it gets closed. This causes data races. 117 LogGrpc bool 118 119 outputLevels string 120 logCallers string 121 stackTraceLevels string 122} 123 124// DefaultOptions returns a new set of options, initialized to the defaults 125func DefaultOptions() *Options { 126 return &Options{ 127 OutputPaths: []string{defaultOutputPath}, 128 ErrorOutputPaths: []string{defaultErrorOutputPath}, 129 RotationMaxSize: defaultRotationMaxSize, 130 RotationMaxAge: defaultRotationMaxAge, 131 RotationMaxBackups: defaultRotationMaxBackups, 132 outputLevels: DefaultScopeName + ":" + levelToString[defaultOutputLevel], 133 stackTraceLevels: DefaultScopeName + ":" + levelToString[defaultStackTraceLevel], 134 LogGrpc: true, 135 } 136} 137 138// SetOutputLevel sets the minimum log output level for a given scope. 139func (o *Options) SetOutputLevel(scope string, level Level) { 140 sl := scope + ":" + levelToString[level] 141 levels := strings.Split(o.outputLevels, ",") 142 143 if scope == DefaultScopeName { 144 // see if we have an entry without a scope prefix (which represents the default scope) 145 for i, ol := range levels { 146 if !strings.Contains(ol, ":") { 147 levels[i] = sl 148 o.outputLevels = strings.Join(levels, ",") 149 return 150 } 151 } 152 } 153 154 prefix := scope + ":" 155 for i, ol := range levels { 156 if strings.HasPrefix(ol, prefix) { 157 levels[i] = sl 158 o.outputLevels = strings.Join(levels, ",") 159 return 160 } 161 } 162 163 levels = append(levels, sl) 164 o.outputLevels = strings.Join(levels, ",") 165} 166 167// GetOutputLevel returns the minimum log output level for a given scope. 168func (o *Options) GetOutputLevel(scope string) (Level, error) { 169 levels := strings.Split(o.outputLevels, ",") 170 171 if scope == DefaultScopeName { 172 // see if we have an entry without a scope prefix (which represents the default scope) 173 for _, ol := range levels { 174 if !strings.Contains(ol, ":") { 175 _, l, err := convertScopedLevel(ol) 176 return l, err 177 } 178 } 179 } 180 181 prefix := scope + ":" 182 for _, ol := range levels { 183 if strings.HasPrefix(ol, prefix) { 184 _, l, err := convertScopedLevel(ol) 185 return l, err 186 } 187 } 188 189 return NoneLevel, fmt.Errorf("no level defined for scope '%s'", scope) 190} 191 192// SetStackTraceLevel sets the minimum stack tracing level for a given scope. 193func (o *Options) SetStackTraceLevel(scope string, level Level) { 194 sl := scope + ":" + levelToString[level] 195 levels := strings.Split(o.stackTraceLevels, ",") 196 197 if scope == DefaultScopeName { 198 // see if we have an entry without a scope prefix (which represents the default scope) 199 for i, ol := range levels { 200 if !strings.Contains(ol, ":") { 201 levels[i] = sl 202 o.stackTraceLevels = strings.Join(levels, ",") 203 return 204 } 205 } 206 } 207 208 prefix := scope + ":" 209 for i, ol := range levels { 210 if strings.HasPrefix(ol, prefix) { 211 levels[i] = sl 212 o.stackTraceLevels = strings.Join(levels, ",") 213 return 214 } 215 } 216 217 levels = append(levels, sl) 218 o.stackTraceLevels = strings.Join(levels, ",") 219} 220 221// GetStackTraceLevel returns the minimum stack tracing level for a given scope. 222func (o *Options) GetStackTraceLevel(scope string) (Level, error) { 223 levels := strings.Split(o.stackTraceLevels, ",") 224 225 if scope == DefaultScopeName { 226 // see if we have an entry without a scope prefix (which represents the default scope) 227 for _, ol := range levels { 228 if !strings.Contains(ol, ":") { 229 _, l, err := convertScopedLevel(ol) 230 return l, err 231 } 232 } 233 } 234 235 prefix := scope + ":" 236 for _, ol := range levels { 237 if strings.HasPrefix(ol, prefix) { 238 _, l, err := convertScopedLevel(ol) 239 return l, err 240 } 241 } 242 243 return NoneLevel, fmt.Errorf("no level defined for scope '%s'", scope) 244} 245 246// SetLogCallers sets whether to output the caller's source code location for a given scope. 247func (o *Options) SetLogCallers(scope string, include bool) { 248 scopes := strings.Split(o.logCallers, ",") 249 250 // remove any occurrence of the scope 251 for i, s := range scopes { 252 if s == scope { 253 scopes[i] = "" 254 } 255 } 256 257 if include { 258 // find a free slot if there is one 259 for i, s := range scopes { 260 if s == "" { 261 scopes[i] = scope 262 o.logCallers = strings.Join(scopes, ",") 263 return 264 } 265 } 266 267 scopes = append(scopes, scope) 268 } 269 270 o.logCallers = strings.Join(scopes, ",") 271} 272 273// GetLogCallers returns whether the caller's source code location is output for a given scope. 274func (o *Options) GetLogCallers(scope string) bool { 275 scopes := strings.Split(o.logCallers, ",") 276 277 for _, s := range scopes { 278 if s == scope { 279 return true 280 } 281 } 282 283 return false 284} 285 286func convertScopedLevel(sl string) (string, Level, error) { 287 var s string 288 var l string 289 290 pieces := strings.Split(sl, ":") 291 if len(pieces) == 1 { 292 s = DefaultScopeName 293 l = pieces[0] 294 } else if len(pieces) == 2 { 295 s = pieces[0] 296 l = pieces[1] 297 } else { 298 return "", NoneLevel, fmt.Errorf("invalid output level format '%s'", sl) 299 } 300 301 level, ok := stringToLevel[l] 302 if !ok { 303 return "", NoneLevel, fmt.Errorf("invalid output level '%s'", sl) 304 } 305 306 return s, level, nil 307} 308 309// AttachCobraFlags attaches a set of Cobra flags to the given Cobra command. 310// 311// Cobra is the command-line processor that Istio uses. This command attaches 312// the necessary set of flags to expose a CLI to let the user control all 313// logging options. 314func (o *Options) AttachCobraFlags(cmd *cobra.Command) { 315 o.AttachFlags( 316 cmd.PersistentFlags().StringArrayVar, 317 cmd.PersistentFlags().StringVar, 318 cmd.PersistentFlags().IntVar, 319 cmd.PersistentFlags().BoolVar) 320} 321 322// AttachFlags allows attaching of flags through a set of lambda functions. 323func (o *Options) AttachFlags( 324 stringArrayVar func(p *[]string, name string, value []string, usage string), 325 stringVar func(p *string, name string, value string, usage string), 326 intVar func(p *int, name string, value int, usage string), 327 boolVar func(p *bool, name string, value bool, usage string)) { 328 329 stringArrayVar(&o.OutputPaths, "log_target", o.OutputPaths, 330 "The set of paths where to output the log. This can be any path as well as the special values stdout and stderr") 331 332 stringVar(&o.RotateOutputPath, "log_rotate", o.RotateOutputPath, 333 "The path for the optional rotating log file") 334 335 intVar(&o.RotationMaxAge, "log_rotate_max_age", o.RotationMaxAge, 336 "The maximum age in days of a log file beyond which the file is rotated (0 indicates no limit)") 337 338 intVar(&o.RotationMaxSize, "log_rotate_max_size", o.RotationMaxSize, 339 "The maximum size in megabytes of a log file beyond which the file is rotated") 340 341 intVar(&o.RotationMaxBackups, "log_rotate_max_backups", o.RotationMaxBackups, 342 "The maximum number of log file backups to keep before older files are deleted (0 indicates no limit)") 343 344 boolVar(&o.JSONEncoding, "log_as_json", o.JSONEncoding, 345 "Whether to format output as JSON or in plain console-friendly format") 346 347 levelListString := fmt.Sprintf("[%s, %s, %s, %s, %s, %s]", 348 levelToString[DebugLevel], 349 levelToString[InfoLevel], 350 levelToString[WarnLevel], 351 levelToString[ErrorLevel], 352 levelToString[FatalLevel], 353 levelToString[NoneLevel]) 354 355 allScopes := Scopes() 356 if len(allScopes) > 1 { 357 keys := make([]string, 0, len(allScopes)) 358 for name := range allScopes { 359 keys = append(keys, name) 360 } 361 keys = append(keys, OverrideScopeName) 362 sort.Strings(keys) 363 s := strings.Join(keys, ", ") 364 365 stringVar(&o.outputLevels, "log_output_level", o.outputLevels, 366 fmt.Sprintf("Comma-separated minimum per-scope logging level of messages to output, in the form of "+ 367 "<scope>:<level>,<scope>:<level>,... where scope can be one of [%s] and level can be one of %s", 368 s, levelListString)) 369 370 stringVar(&o.stackTraceLevels, "log_stacktrace_level", o.stackTraceLevels, 371 fmt.Sprintf("Comma-separated minimum per-scope logging level at which stack traces are captured, in the form of "+ 372 "<scope>:<level>,<scope:level>,... where scope can be one of [%s] and level can be one of %s", 373 s, levelListString)) 374 375 stringVar(&o.logCallers, "log_caller", o.logCallers, 376 fmt.Sprintf("Comma-separated list of scopes for which to include caller information, scopes can be any of [%s]", s)) 377 } else { 378 stringVar(&o.outputLevels, "log_output_level", o.outputLevels, 379 fmt.Sprintf("The minimum logging level of messages to output, can be one of %s", 380 levelListString)) 381 382 stringVar(&o.stackTraceLevels, "log_stacktrace_level", o.stackTraceLevels, 383 fmt.Sprintf("The minimum logging level at which stack traces are captured, can be one of %s", 384 levelListString)) 385 386 stringVar(&o.logCallers, "log_caller", o.logCallers, 387 "Comma-separated list of scopes for which to include called information, scopes can be any of [default]") 388 } 389 390 // NOTE: we don't currently expose a command-line option to control ErrorOutputPaths since it 391 // seems too esoteric. 392} 393