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  }