sugar.go raw

   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