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 }