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