...

Source file src/go.uber.org/zap/sugar.go

Documentation: go.uber.org/zap

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zap
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"go.uber.org/zap/zapcore"
    27  
    28  	"go.uber.org/multierr"
    29  )
    30  
    31  const (
    32  	_oddNumberErrMsg    = "Ignored key without a value."
    33  	_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."
    34  	_multipleErrMsg     = "Multiple errors without a key."
    35  )
    36  
    37  // A SugaredLogger wraps the base Logger functionality in a slower, but less
    38  // verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar
    39  // method.
    40  //
    41  // Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
    42  // For each log level, it exposes four methods:
    43  //
    44  //   - methods named after the log level for log.Print-style logging
    45  //   - methods ending in "w" for loosely-typed structured logging
    46  //   - methods ending in "f" for log.Printf-style logging
    47  //   - methods ending in "ln" for log.Println-style logging
    48  //
    49  // For example, the methods for InfoLevel are:
    50  //
    51  //	Info(...any)           Print-style logging
    52  //	Infow(...any)          Structured logging (read as "info with")
    53  //	Infof(string, ...any)  Printf-style logging
    54  //	Infoln(...any)         Println-style logging
    55  type SugaredLogger struct {
    56  	base *Logger
    57  }
    58  
    59  // Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring
    60  // is quite inexpensive, so it's reasonable for a single application to use
    61  // both Loggers and SugaredLoggers, converting between them on the boundaries
    62  // of performance-sensitive code.
    63  func (s *SugaredLogger) Desugar() *Logger {
    64  	base := s.base.clone()
    65  	base.callerSkip -= 2
    66  	return base
    67  }
    68  
    69  // Named adds a sub-scope to the logger's name. See Logger.Named for details.
    70  func (s *SugaredLogger) Named(name string) *SugaredLogger {
    71  	return &SugaredLogger{base: s.base.Named(name)}
    72  }
    73  
    74  // WithOptions clones the current SugaredLogger, applies the supplied Options,
    75  // and returns the result. It's safe to use concurrently.
    76  func (s *SugaredLogger) WithOptions(opts ...Option) *SugaredLogger {
    77  	base := s.base.clone()
    78  	for _, opt := range opts {
    79  		opt.apply(base)
    80  	}
    81  	return &SugaredLogger{base: base}
    82  }
    83  
    84  // With adds a variadic number of fields to the logging context. It accepts a
    85  // mix of strongly-typed Field objects and loosely-typed key-value pairs. When
    86  // processing pairs, the first element of the pair is used as the field key
    87  // and the second as the field value.
    88  //
    89  // For example,
    90  //
    91  //	 sugaredLogger.With(
    92  //	   "hello", "world",
    93  //	   "failure", errors.New("oh no"),
    94  //	   Stack(),
    95  //	   "count", 42,
    96  //	   "user", User{Name: "alice"},
    97  //	)
    98  //
    99  // is the equivalent of
   100  //
   101  //	unsugared.With(
   102  //	  String("hello", "world"),
   103  //	  String("failure", "oh no"),
   104  //	  Stack(),
   105  //	  Int("count", 42),
   106  //	  Object("user", User{Name: "alice"}),
   107  //	)
   108  //
   109  // Note that the keys in key-value pairs should be strings. In development,
   110  // passing a non-string key panics. In production, the logger is more
   111  // forgiving: a separate error is logged, but the key-value pair is skipped
   112  // and execution continues. Passing an orphaned key triggers similar behavior:
   113  // panics in development and errors in production.
   114  func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger {
   115  	return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)}
   116  }
   117  
   118  // WithLazy adds a variadic number of fields to the logging context lazily.
   119  // The fields are evaluated only if the logger is further chained with [With]
   120  // or is written to with any of the log level methods.
   121  // Until that occurs, the logger may retain references to objects inside the fields,
   122  // and logging will reflect the state of an object at the time of logging,
   123  // not the time of WithLazy().
   124  //
   125  // Similar to [With], fields added to the child don't affect the parent,
   126  // and vice versa. Also, the keys in key-value pairs should be strings. In development,
   127  // passing a non-string key panics, while in production it logs an error and skips the pair.
   128  // Passing an orphaned key has the same behavior.
   129  func (s *SugaredLogger) WithLazy(args ...interface{}) *SugaredLogger {
   130  	return &SugaredLogger{base: s.base.WithLazy(s.sweetenFields(args)...)}
   131  }
   132  
   133  // Level reports the minimum enabled level for this logger.
   134  //
   135  // For NopLoggers, this is [zapcore.InvalidLevel].
   136  func (s *SugaredLogger) Level() zapcore.Level {
   137  	return zapcore.LevelOf(s.base.core)
   138  }
   139  
   140  // Log logs the provided arguments at provided level.
   141  // Spaces are added between arguments when neither is a string.
   142  func (s *SugaredLogger) Log(lvl zapcore.Level, args ...interface{}) {
   143  	s.log(lvl, "", args, nil)
   144  }
   145  
   146  // Debug logs the provided arguments at [DebugLevel].
   147  // Spaces are added between arguments when neither is a string.
   148  func (s *SugaredLogger) Debug(args ...interface{}) {
   149  	s.log(DebugLevel, "", args, nil)
   150  }
   151  
   152  // Info logs the provided arguments at [InfoLevel].
   153  // Spaces are added between arguments when neither is a string.
   154  func (s *SugaredLogger) Info(args ...interface{}) {
   155  	s.log(InfoLevel, "", args, nil)
   156  }
   157  
   158  // Warn logs the provided arguments at [WarnLevel].
   159  // Spaces are added between arguments when neither is a string.
   160  func (s *SugaredLogger) Warn(args ...interface{}) {
   161  	s.log(WarnLevel, "", args, nil)
   162  }
   163  
   164  // Error logs the provided arguments at [ErrorLevel].
   165  // Spaces are added between arguments when neither is a string.
   166  func (s *SugaredLogger) Error(args ...interface{}) {
   167  	s.log(ErrorLevel, "", args, nil)
   168  }
   169  
   170  // DPanic logs the provided arguments at [DPanicLevel].
   171  // In development, the logger then panics. (See [DPanicLevel] for details.)
   172  // Spaces are added between arguments when neither is a string.
   173  func (s *SugaredLogger) DPanic(args ...interface{}) {
   174  	s.log(DPanicLevel, "", args, nil)
   175  }
   176  
   177  // Panic constructs a message with the provided arguments and panics.
   178  // Spaces are added between arguments when neither is a string.
   179  func (s *SugaredLogger) Panic(args ...interface{}) {
   180  	s.log(PanicLevel, "", args, nil)
   181  }
   182  
   183  // Fatal constructs a message with the provided arguments and calls os.Exit.
   184  // Spaces are added between arguments when neither is a string.
   185  func (s *SugaredLogger) Fatal(args ...interface{}) {
   186  	s.log(FatalLevel, "", args, nil)
   187  }
   188  
   189  // Logf formats the message according to the format specifier
   190  // and logs it at provided level.
   191  func (s *SugaredLogger) Logf(lvl zapcore.Level, template string, args ...interface{}) {
   192  	s.log(lvl, template, args, nil)
   193  }
   194  
   195  // Debugf formats the message according to the format specifier
   196  // and logs it at [DebugLevel].
   197  func (s *SugaredLogger) Debugf(template string, args ...interface{}) {
   198  	s.log(DebugLevel, template, args, nil)
   199  }
   200  
   201  // Infof formats the message according to the format specifier
   202  // and logs it at [InfoLevel].
   203  func (s *SugaredLogger) Infof(template string, args ...interface{}) {
   204  	s.log(InfoLevel, template, args, nil)
   205  }
   206  
   207  // Warnf formats the message according to the format specifier
   208  // and logs it at [WarnLevel].
   209  func (s *SugaredLogger) Warnf(template string, args ...interface{}) {
   210  	s.log(WarnLevel, template, args, nil)
   211  }
   212  
   213  // Errorf formats the message according to the format specifier
   214  // and logs it at [ErrorLevel].
   215  func (s *SugaredLogger) Errorf(template string, args ...interface{}) {
   216  	s.log(ErrorLevel, template, args, nil)
   217  }
   218  
   219  // DPanicf formats the message according to the format specifier
   220  // and logs it at [DPanicLevel].
   221  // In development, the logger then panics. (See [DPanicLevel] for details.)
   222  func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {
   223  	s.log(DPanicLevel, template, args, nil)
   224  }
   225  
   226  // Panicf formats the message according to the format specifier
   227  // and panics.
   228  func (s *SugaredLogger) Panicf(template string, args ...interface{}) {
   229  	s.log(PanicLevel, template, args, nil)
   230  }
   231  
   232  // Fatalf formats the message according to the format specifier
   233  // and calls os.Exit.
   234  func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {
   235  	s.log(FatalLevel, template, args, nil)
   236  }
   237  
   238  // Logw logs a message with some additional context. The variadic key-value
   239  // pairs are treated as they are in With.
   240  func (s *SugaredLogger) Logw(lvl zapcore.Level, msg string, keysAndValues ...interface{}) {
   241  	s.log(lvl, msg, nil, keysAndValues)
   242  }
   243  
   244  // Debugw logs a message with some additional context. The variadic key-value
   245  // pairs are treated as they are in With.
   246  //
   247  // When debug-level logging is disabled, this is much faster than
   248  //
   249  //	s.With(keysAndValues).Debug(msg)
   250  func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) {
   251  	s.log(DebugLevel, msg, nil, keysAndValues)
   252  }
   253  
   254  // Infow logs a message with some additional context. The variadic key-value
   255  // pairs are treated as they are in With.
   256  func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) {
   257  	s.log(InfoLevel, msg, nil, keysAndValues)
   258  }
   259  
   260  // Warnw logs a message with some additional context. The variadic key-value
   261  // pairs are treated as they are in With.
   262  func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) {
   263  	s.log(WarnLevel, msg, nil, keysAndValues)
   264  }
   265  
   266  // Errorw logs a message with some additional context. The variadic key-value
   267  // pairs are treated as they are in With.
   268  func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) {
   269  	s.log(ErrorLevel, msg, nil, keysAndValues)
   270  }
   271  
   272  // DPanicw logs a message with some additional context. In development, the
   273  // logger then panics. (See DPanicLevel for details.) The variadic key-value
   274  // pairs are treated as they are in With.
   275  func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) {
   276  	s.log(DPanicLevel, msg, nil, keysAndValues)
   277  }
   278  
   279  // Panicw logs a message with some additional context, then panics. The
   280  // variadic key-value pairs are treated as they are in With.
   281  func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) {
   282  	s.log(PanicLevel, msg, nil, keysAndValues)
   283  }
   284  
   285  // Fatalw logs a message with some additional context, then calls os.Exit. The
   286  // variadic key-value pairs are treated as they are in With.
   287  func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
   288  	s.log(FatalLevel, msg, nil, keysAndValues)
   289  }
   290  
   291  // Logln logs a message at provided level.
   292  // Spaces are always added between arguments.
   293  func (s *SugaredLogger) Logln(lvl zapcore.Level, args ...interface{}) {
   294  	s.logln(lvl, args, nil)
   295  }
   296  
   297  // Debugln logs a message at [DebugLevel].
   298  // Spaces are always added between arguments.
   299  func (s *SugaredLogger) Debugln(args ...interface{}) {
   300  	s.logln(DebugLevel, args, nil)
   301  }
   302  
   303  // Infoln logs a message at [InfoLevel].
   304  // Spaces are always added between arguments.
   305  func (s *SugaredLogger) Infoln(args ...interface{}) {
   306  	s.logln(InfoLevel, args, nil)
   307  }
   308  
   309  // Warnln logs a message at [WarnLevel].
   310  // Spaces are always added between arguments.
   311  func (s *SugaredLogger) Warnln(args ...interface{}) {
   312  	s.logln(WarnLevel, args, nil)
   313  }
   314  
   315  // Errorln logs a message at [ErrorLevel].
   316  // Spaces are always added between arguments.
   317  func (s *SugaredLogger) Errorln(args ...interface{}) {
   318  	s.logln(ErrorLevel, args, nil)
   319  }
   320  
   321  // DPanicln logs a message at [DPanicLevel].
   322  // In development, the logger then panics. (See [DPanicLevel] for details.)
   323  // Spaces are always added between arguments.
   324  func (s *SugaredLogger) DPanicln(args ...interface{}) {
   325  	s.logln(DPanicLevel, args, nil)
   326  }
   327  
   328  // Panicln logs a message at [PanicLevel] and panics.
   329  // Spaces are always added between arguments.
   330  func (s *SugaredLogger) Panicln(args ...interface{}) {
   331  	s.logln(PanicLevel, args, nil)
   332  }
   333  
   334  // Fatalln logs a message at [FatalLevel] and calls os.Exit.
   335  // Spaces are always added between arguments.
   336  func (s *SugaredLogger) Fatalln(args ...interface{}) {
   337  	s.logln(FatalLevel, args, nil)
   338  }
   339  
   340  // Sync flushes any buffered log entries.
   341  func (s *SugaredLogger) Sync() error {
   342  	return s.base.Sync()
   343  }
   344  
   345  // log message with Sprint, Sprintf, or neither.
   346  func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) {
   347  	// If logging at this level is completely disabled, skip the overhead of
   348  	// string formatting.
   349  	if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {
   350  		return
   351  	}
   352  
   353  	msg := getMessage(template, fmtArgs)
   354  	if ce := s.base.Check(lvl, msg); ce != nil {
   355  		ce.Write(s.sweetenFields(context)...)
   356  	}
   357  }
   358  
   359  // logln message with Sprintln
   360  func (s *SugaredLogger) logln(lvl zapcore.Level, fmtArgs []interface{}, context []interface{}) {
   361  	if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {
   362  		return
   363  	}
   364  
   365  	msg := getMessageln(fmtArgs)
   366  	if ce := s.base.Check(lvl, msg); ce != nil {
   367  		ce.Write(s.sweetenFields(context)...)
   368  	}
   369  }
   370  
   371  // getMessage format with Sprint, Sprintf, or neither.
   372  func getMessage(template string, fmtArgs []interface{}) string {
   373  	if len(fmtArgs) == 0 {
   374  		return template
   375  	}
   376  
   377  	if template != "" {
   378  		return fmt.Sprintf(template, fmtArgs...)
   379  	}
   380  
   381  	if len(fmtArgs) == 1 {
   382  		if str, ok := fmtArgs[0].(string); ok {
   383  			return str
   384  		}
   385  	}
   386  	return fmt.Sprint(fmtArgs...)
   387  }
   388  
   389  // getMessageln format with Sprintln.
   390  func getMessageln(fmtArgs []interface{}) string {
   391  	msg := fmt.Sprintln(fmtArgs...)
   392  	return msg[:len(msg)-1]
   393  }
   394  
   395  func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
   396  	if len(args) == 0 {
   397  		return nil
   398  	}
   399  
   400  	var (
   401  		// Allocate enough space for the worst case; if users pass only structured
   402  		// fields, we shouldn't penalize them with extra allocations.
   403  		fields    = make([]Field, 0, len(args))
   404  		invalid   invalidPairs
   405  		seenError bool
   406  	)
   407  
   408  	for i := 0; i < len(args); {
   409  		// This is a strongly-typed field. Consume it and move on.
   410  		if f, ok := args[i].(Field); ok {
   411  			fields = append(fields, f)
   412  			i++
   413  			continue
   414  		}
   415  
   416  		// If it is an error, consume it and move on.
   417  		if err, ok := args[i].(error); ok {
   418  			if !seenError {
   419  				seenError = true
   420  				fields = append(fields, Error(err))
   421  			} else {
   422  				s.base.Error(_multipleErrMsg, Error(err))
   423  			}
   424  			i++
   425  			continue
   426  		}
   427  
   428  		// Make sure this element isn't a dangling key.
   429  		if i == len(args)-1 {
   430  			s.base.Error(_oddNumberErrMsg, Any("ignored", args[i]))
   431  			break
   432  		}
   433  
   434  		// Consume this value and the next, treating them as a key-value pair. If the
   435  		// key isn't a string, add this pair to the slice of invalid pairs.
   436  		key, val := args[i], args[i+1]
   437  		if keyStr, ok := key.(string); !ok {
   438  			// Subsequent errors are likely, so allocate once up front.
   439  			if cap(invalid) == 0 {
   440  				invalid = make(invalidPairs, 0, len(args)/2)
   441  			}
   442  			invalid = append(invalid, invalidPair{i, key, val})
   443  		} else {
   444  			fields = append(fields, Any(keyStr, val))
   445  		}
   446  		i += 2
   447  	}
   448  
   449  	// If we encountered any invalid key-value pairs, log an error.
   450  	if len(invalid) > 0 {
   451  		s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid))
   452  	}
   453  	return fields
   454  }
   455  
   456  type invalidPair struct {
   457  	position   int
   458  	key, value interface{}
   459  }
   460  
   461  func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {
   462  	enc.AddInt64("position", int64(p.position))
   463  	Any("key", p.key).AddTo(enc)
   464  	Any("value", p.value).AddTo(enc)
   465  	return nil
   466  }
   467  
   468  type invalidPairs []invalidPair
   469  
   470  func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
   471  	var err error
   472  	for i := range ps {
   473  		err = multierr.Append(err, enc.AppendObject(ps[i]))
   474  	}
   475  	return err
   476  }
   477  

View as plain text