istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/log/scope.go (about) 1 // Copyright 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 "runtime" 20 "strings" 21 "sync" 22 "sync/atomic" 23 "time" 24 25 "go.uber.org/zap" 26 "go.uber.org/zap/zapcore" 27 ) 28 29 // Scope constrains logging control to a named scope level. It gives users a fine grained control over output severity 30 // threshold and stack traces. 31 // 32 // Scope supports structured logging using WithLabels: 33 // 34 // s := RegisterScope("MyScope", "Description", 0) 35 // s = s.WithLabels("foo", "bar", "baz", 123, "qux", 0.123) 36 // s.Info("Hello") // <time> info MyScope Hello foo=bar baz=123 qux=0.123 37 // 38 // The output format can be globally configured to be JSON instead, using Options in this package. 39 // 40 // e.g. <time> info MyScope { "message":"Hello","foo":"bar","baz":123 } 41 // 42 // Scope also supports an error dictionary. The caller can pass a *structured.Error object as the first parameter 43 // to any of the output functions (Fatal*, Error* etc.) and this will append the fields in the object to the output: 44 // 45 // e := &structured.Error{MoreInfo:"See the documentation in istio.io/helpful_link"} 46 // s.WithLabels("foo", "bar").Error(e, "Hello") 47 // <time> info MyScope Hello moreInfo=See the documentation in istio.io/helpful_link foo=bar 48 // 49 // See structured.Error for additional guidance on defining errors in a dictionary. 50 type Scope struct { 51 // immutable, set at creation 52 name string 53 nameToEmit string 54 description string 55 callerSkip int 56 57 // set by the Configure method and adjustable dynamically 58 outputLevel atomic.Value 59 stackTraceLevel atomic.Value 60 logCallers atomic.Value 61 62 // labels data - key slice to preserve ordering 63 labelKeys []string 64 labels map[string]any 65 } 66 67 var ( 68 scopes = make(map[string]*Scope) 69 lock sync.RWMutex 70 ) 71 72 // RegisterScope registers a new logging scope. If the same name is used multiple times 73 // for a single process, the same Scope struct is returned. 74 // 75 // Scope names cannot include colons, commas, or periods. 76 func RegisterScope(name string, description string) *Scope { 77 // We only allow internal callers to set callerSkip 78 return registerScope(name, description, 0) 79 } 80 81 func registerScope(name string, description string, callerSkip int) *Scope { 82 if strings.ContainsAny(name, ":,.") { 83 panic(fmt.Sprintf("scope name %s is invalid, it cannot contain colons, commas, or periods", name)) 84 } 85 86 lock.Lock() 87 defer lock.Unlock() 88 89 s, ok := scopes[name] 90 if !ok { 91 s = &Scope{ 92 name: name, 93 description: description, 94 callerSkip: callerSkip, 95 } 96 s.SetOutputLevel(InfoLevel) 97 s.SetStackTraceLevel(NoneLevel) 98 s.SetLogCallers(false) 99 100 if name != DefaultScopeName { 101 s.nameToEmit = name 102 } 103 104 scopes[name] = s 105 } 106 107 s.labels = make(map[string]any) 108 109 return s 110 } 111 112 // FindScope returns a previously registered scope, or nil if the named scope wasn't previously registered 113 func FindScope(scope string) *Scope { 114 lock.RLock() 115 defer lock.RUnlock() 116 117 s := scopes[scope] 118 return s 119 } 120 121 // Scopes returns a snapshot of the currently defined set of scopes 122 func Scopes() map[string]*Scope { 123 lock.RLock() 124 defer lock.RUnlock() 125 126 s := make(map[string]*Scope, len(scopes)) 127 for k, v := range scopes { 128 s[k] = v 129 } 130 131 return s 132 } 133 134 // Fatal uses fmt.Sprint to construct and log a message at fatal level. 135 func (s *Scope) Fatal(msg any) { 136 if s.GetOutputLevel() >= FatalLevel { 137 s.emit(zapcore.FatalLevel, fmt.Sprint(msg)) 138 } 139 } 140 141 // Fatalf uses fmt.Sprintf to construct and log a message at fatal level. 142 func (s *Scope) Fatalf(format string, args ...any) { 143 if s.GetOutputLevel() >= FatalLevel { 144 msg := maybeSprintf(format, args) 145 s.emit(zapcore.FatalLevel, msg) 146 } 147 } 148 149 // FatalEnabled returns whether output of messages using this scope is currently enabled for fatal-level output. 150 func (s *Scope) FatalEnabled() bool { 151 return s.GetOutputLevel() >= FatalLevel 152 } 153 154 // Error outputs a message at error level. 155 func (s *Scope) Error(msg any) { 156 if s.GetOutputLevel() >= ErrorLevel { 157 s.emit(zapcore.ErrorLevel, fmt.Sprint(msg)) 158 } 159 } 160 161 // Errorf uses fmt.Sprintf to construct and log a message at error level. 162 func (s *Scope) Errorf(format string, args ...any) { 163 if s.GetOutputLevel() >= ErrorLevel { 164 msg := maybeSprintf(format, args) 165 s.emit(zapcore.ErrorLevel, msg) 166 } 167 } 168 169 // ErrorEnabled returns whether output of messages using this scope is currently enabled for error-level output. 170 func (s *Scope) ErrorEnabled() bool { 171 return s.GetOutputLevel() >= ErrorLevel 172 } 173 174 // Warn outputs a message at warn level. 175 func (s *Scope) Warn(msg any) { 176 if s.GetOutputLevel() >= WarnLevel { 177 s.emit(zapcore.WarnLevel, fmt.Sprint(msg)) 178 } 179 } 180 181 // Warnf uses fmt.Sprintf to construct and log a message at warn level. 182 func (s *Scope) Warnf(format string, args ...any) { 183 if s.GetOutputLevel() >= WarnLevel { 184 msg := maybeSprintf(format, args) 185 s.emit(zapcore.WarnLevel, msg) 186 } 187 } 188 189 // WarnEnabled returns whether output of messages using this scope is currently enabled for warn-level output. 190 func (s *Scope) WarnEnabled() bool { 191 return s.GetOutputLevel() >= WarnLevel 192 } 193 194 // Info outputs a message at info level. 195 func (s *Scope) Info(msg any) { 196 if s.GetOutputLevel() >= InfoLevel { 197 s.emit(zapcore.InfoLevel, fmt.Sprint(msg)) 198 } 199 } 200 201 // Infof uses fmt.Sprintf to construct and log a message at info level. 202 func (s *Scope) Infof(format string, args ...any) { 203 if s.GetOutputLevel() >= InfoLevel { 204 msg := maybeSprintf(format, args) 205 s.emit(zapcore.InfoLevel, msg) 206 } 207 } 208 209 // InfoEnabled returns whether output of messages using this scope is currently enabled for info-level output. 210 func (s *Scope) InfoEnabled() bool { 211 return s.GetOutputLevel() >= InfoLevel 212 } 213 214 // Debug outputs a message at debug level. 215 func (s *Scope) Debug(msg any) { 216 if s.GetOutputLevel() >= DebugLevel { 217 s.emit(zapcore.DebugLevel, fmt.Sprint(msg)) 218 } 219 } 220 221 // LogWithTime outputs a message with a given timestamp. 222 func (s *Scope) LogWithTime(level Level, msg string, t time.Time) { 223 if s.GetOutputLevel() >= level { 224 s.emitWithTime(levelToZap[level], msg, t) 225 } 226 } 227 228 // Debugf uses fmt.Sprintf to construct and log a message at debug level. 229 func (s *Scope) Debugf(format string, args ...any) { 230 if s.GetOutputLevel() >= DebugLevel { 231 msg := maybeSprintf(format, args) 232 s.emit(zapcore.DebugLevel, msg) 233 } 234 } 235 236 // DebugEnabled returns whether output of messages using this scope is currently enabled for debug-level output. 237 func (s *Scope) DebugEnabled() bool { 238 return s.GetOutputLevel() >= DebugLevel 239 } 240 241 // Name returns this scope's name. 242 func (s *Scope) Name() string { 243 return s.name 244 } 245 246 // Description returns this scope's description 247 func (s *Scope) Description() string { 248 return s.description 249 } 250 251 // SetOutputLevel adjusts the output level associated with the scope. 252 func (s *Scope) SetOutputLevel(l Level) { 253 s.outputLevel.Store(l) 254 } 255 256 // GetOutputLevel returns the output level associated with the scope. 257 func (s *Scope) GetOutputLevel() Level { 258 return s.outputLevel.Load().(Level) 259 } 260 261 // SetStackTraceLevel adjusts the stack tracing level associated with the scope. 262 func (s *Scope) SetStackTraceLevel(l Level) { 263 s.stackTraceLevel.Store(l) 264 } 265 266 // GetStackTraceLevel returns the stack tracing level associated with the scope. 267 func (s *Scope) GetStackTraceLevel() Level { 268 return s.stackTraceLevel.Load().(Level) 269 } 270 271 // SetLogCallers adjusts the output level associated with the scope. 272 func (s *Scope) SetLogCallers(logCallers bool) { 273 s.logCallers.Store(logCallers) 274 } 275 276 // GetLogCallers returns the output level associated with the scope. 277 func (s *Scope) GetLogCallers() bool { 278 return s.logCallers.Load().(bool) 279 } 280 281 // copy makes a copy of s and returns a pointer to it. 282 func (s *Scope) copy() *Scope { 283 out := *s 284 out.labels = copyStringInterfaceMap(s.labels) 285 return &out 286 } 287 288 // WithLabels adds a key-value pairs to the labels in s. The key must be a string, while the value may be any type. 289 // It returns a copy of s, with the labels added. 290 // e.g. newScope := oldScope.WithLabels("foo", "bar", "baz", 123, "qux", 0.123) 291 func (s *Scope) WithLabels(kvlist ...any) *Scope { 292 out := s.copy() 293 if len(kvlist)%2 != 0 { 294 out.labels["WithLabels error"] = fmt.Sprintf("even number of parameters required, got %d", len(kvlist)) 295 return out 296 } 297 298 for i := 0; i < len(kvlist); i += 2 { 299 keyi := kvlist[i] 300 key, ok := keyi.(string) 301 if !ok { 302 out.labels["WithLabels error"] = fmt.Sprintf("label name %v must be a string, got %T ", keyi, keyi) 303 return out 304 } 305 _, override := out.labels[key] 306 out.labels[key] = kvlist[i+1] 307 if override { 308 // Key already set, just modify the value 309 continue 310 } 311 out.labelKeys = append(out.labelKeys, key) 312 } 313 return out 314 } 315 316 func (s *Scope) emit(level zapcore.Level, msg string) { 317 s.emitWithTime(level, msg, time.Now()) 318 } 319 320 func (s *Scope) emitWithTime(level zapcore.Level, msg string, t time.Time) { 321 if t.IsZero() { 322 t = time.Now() 323 } 324 325 e := zapcore.Entry{ 326 Message: msg, 327 Level: level, 328 Time: t, 329 LoggerName: s.nameToEmit, 330 } 331 332 if s.GetLogCallers() { 333 e.Caller = zapcore.NewEntryCaller(runtime.Caller(s.callerSkip + callerSkipOffset)) 334 } 335 336 if dumpStack(level, s) { 337 e.Stack = zap.Stack("").String 338 } 339 340 var fields []zapcore.Field 341 if useJSON.Load().(bool) { 342 fields = make([]zapcore.Field, 0, len(s.labelKeys)) 343 for _, k := range s.labelKeys { 344 v := s.labels[k] 345 fields = append(fields, zap.Field{ 346 Key: k, 347 Interface: v, 348 Type: zapcore.ReflectType, 349 }) 350 } 351 } else if len(s.labelKeys) > 0 { 352 sb := &strings.Builder{} 353 // Assume roughly 15 chars per kv pair. Its fine to be off, this is just an optimization 354 sb.Grow(len(msg) + 15*len(s.labelKeys)) 355 sb.WriteString(msg) 356 sb.WriteString("\t") 357 space := false 358 for _, k := range s.labelKeys { 359 if space { 360 sb.WriteString(" ") 361 } 362 sb.WriteString(k) 363 sb.WriteString("=") 364 sb.WriteString(fmt.Sprint(s.labels[k])) 365 space = true 366 } 367 e.Message = sb.String() 368 } 369 370 pt := funcs.Load().(patchTable) 371 if pt.write != nil { 372 if err := pt.write(e, fields); err != nil { 373 _, _ = fmt.Fprintf(pt.errorSink, "%v log write error: %v\n", time.Now(), err) 374 _ = pt.errorSink.Sync() 375 } 376 } 377 } 378 379 func copyStringInterfaceMap(m map[string]any) map[string]any { 380 out := make(map[string]any, len(m)) 381 for k, v := range m { 382 out[k] = v 383 } 384 return out 385 } 386 387 func maybeSprintf(format string, args []any) string { 388 msg := format 389 if len(args) > 0 { 390 msg = fmt.Sprintf(format, args...) 391 } 392 return msg 393 }