blob: 8904cd0871e73de5ee9cc7f343ee7f171a76337e [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001// 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
21package zap
22
23import (
24 "fmt"
25
26 "go.uber.org/zap/zapcore"
27
28 "go.uber.org/multierr"
29)
30
31const (
32 _oddNumberErrMsg = "Ignored key without a value."
33 _nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."
Abhay Kumar40252eb2025-10-13 13:25:53 +000034 _multipleErrMsg = "Multiple errors without a key."
Scott Baker2c1c4822019-10-16 11:02:41 -070035)
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.
Abhay Kumar40252eb2025-10-13 13:25:53 +000042// 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
Scott Baker2c1c4822019-10-16 11:02:41 -070055type 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.
63func (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.
70func (s *SugaredLogger) Named(name string) *SugaredLogger {
71 return &SugaredLogger{base: s.base.Named(name)}
72}
73
Abhay Kumar40252eb2025-10-13 13:25:53 +000074// WithOptions clones the current SugaredLogger, applies the supplied Options,
75// and returns the result. It's safe to use concurrently.
76func (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
Scott Baker2c1c4822019-10-16 11:02:41 -070084// 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,
Abhay Kumar40252eb2025-10-13 13:25:53 +000090//
91// sugaredLogger.With(
92// "hello", "world",
93// "failure", errors.New("oh no"),
94// Stack(),
95// "count", 42,
96// "user", User{Name: "alice"},
97// )
98//
Scott Baker2c1c4822019-10-16 11:02:41 -070099// is the equivalent of
Abhay Kumar40252eb2025-10-13 13:25:53 +0000100//
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// )
Scott Baker2c1c4822019-10-16 11:02:41 -0700108//
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.
114func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger {
115 return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)}
116}
117
Abhay Kumar40252eb2025-10-13 13:25:53 +0000118// 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.
129func (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].
136func (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.
142func (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.
Scott Baker2c1c4822019-10-16 11:02:41 -0700148func (s *SugaredLogger) Debug(args ...interface{}) {
149 s.log(DebugLevel, "", args, nil)
150}
151
Abhay Kumar40252eb2025-10-13 13:25:53 +0000152// Info logs the provided arguments at [InfoLevel].
153// Spaces are added between arguments when neither is a string.
Scott Baker2c1c4822019-10-16 11:02:41 -0700154func (s *SugaredLogger) Info(args ...interface{}) {
155 s.log(InfoLevel, "", args, nil)
156}
157
Abhay Kumar40252eb2025-10-13 13:25:53 +0000158// Warn logs the provided arguments at [WarnLevel].
159// Spaces are added between arguments when neither is a string.
Scott Baker2c1c4822019-10-16 11:02:41 -0700160func (s *SugaredLogger) Warn(args ...interface{}) {
161 s.log(WarnLevel, "", args, nil)
162}
163
Abhay Kumar40252eb2025-10-13 13:25:53 +0000164// Error logs the provided arguments at [ErrorLevel].
165// Spaces are added between arguments when neither is a string.
Scott Baker2c1c4822019-10-16 11:02:41 -0700166func (s *SugaredLogger) Error(args ...interface{}) {
167 s.log(ErrorLevel, "", args, nil)
168}
169
Abhay Kumar40252eb2025-10-13 13:25:53 +0000170// 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.
Scott Baker2c1c4822019-10-16 11:02:41 -0700173func (s *SugaredLogger) DPanic(args ...interface{}) {
174 s.log(DPanicLevel, "", args, nil)
175}
176
Abhay Kumar40252eb2025-10-13 13:25:53 +0000177// Panic constructs a message with the provided arguments and panics.
178// Spaces are added between arguments when neither is a string.
Scott Baker2c1c4822019-10-16 11:02:41 -0700179func (s *SugaredLogger) Panic(args ...interface{}) {
180 s.log(PanicLevel, "", args, nil)
181}
182
Abhay Kumar40252eb2025-10-13 13:25:53 +0000183// Fatal constructs a message with the provided arguments and calls os.Exit.
184// Spaces are added between arguments when neither is a string.
Scott Baker2c1c4822019-10-16 11:02:41 -0700185func (s *SugaredLogger) Fatal(args ...interface{}) {
186 s.log(FatalLevel, "", args, nil)
187}
188
Abhay Kumar40252eb2025-10-13 13:25:53 +0000189// Logf formats the message according to the format specifier
190// and logs it at provided level.
191func (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].
Scott Baker2c1c4822019-10-16 11:02:41 -0700197func (s *SugaredLogger) Debugf(template string, args ...interface{}) {
198 s.log(DebugLevel, template, args, nil)
199}
200
Abhay Kumar40252eb2025-10-13 13:25:53 +0000201// Infof formats the message according to the format specifier
202// and logs it at [InfoLevel].
Scott Baker2c1c4822019-10-16 11:02:41 -0700203func (s *SugaredLogger) Infof(template string, args ...interface{}) {
204 s.log(InfoLevel, template, args, nil)
205}
206
Abhay Kumar40252eb2025-10-13 13:25:53 +0000207// Warnf formats the message according to the format specifier
208// and logs it at [WarnLevel].
Scott Baker2c1c4822019-10-16 11:02:41 -0700209func (s *SugaredLogger) Warnf(template string, args ...interface{}) {
210 s.log(WarnLevel, template, args, nil)
211}
212
Abhay Kumar40252eb2025-10-13 13:25:53 +0000213// Errorf formats the message according to the format specifier
214// and logs it at [ErrorLevel].
Scott Baker2c1c4822019-10-16 11:02:41 -0700215func (s *SugaredLogger) Errorf(template string, args ...interface{}) {
216 s.log(ErrorLevel, template, args, nil)
217}
218
Abhay Kumar40252eb2025-10-13 13:25:53 +0000219// 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.)
Scott Baker2c1c4822019-10-16 11:02:41 -0700222func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {
223 s.log(DPanicLevel, template, args, nil)
224}
225
Abhay Kumar40252eb2025-10-13 13:25:53 +0000226// Panicf formats the message according to the format specifier
227// and panics.
Scott Baker2c1c4822019-10-16 11:02:41 -0700228func (s *SugaredLogger) Panicf(template string, args ...interface{}) {
229 s.log(PanicLevel, template, args, nil)
230}
231
Abhay Kumar40252eb2025-10-13 13:25:53 +0000232// Fatalf formats the message according to the format specifier
233// and calls os.Exit.
Scott Baker2c1c4822019-10-16 11:02:41 -0700234func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {
235 s.log(FatalLevel, template, args, nil)
236}
237
Abhay Kumar40252eb2025-10-13 13:25:53 +0000238// Logw logs a message with some additional context. The variadic key-value
239// pairs are treated as they are in With.
240func (s *SugaredLogger) Logw(lvl zapcore.Level, msg string, keysAndValues ...interface{}) {
241 s.log(lvl, msg, nil, keysAndValues)
242}
243
Scott Baker2c1c4822019-10-16 11:02:41 -0700244// 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
Abhay Kumar40252eb2025-10-13 13:25:53 +0000248//
249// s.With(keysAndValues).Debug(msg)
Scott Baker2c1c4822019-10-16 11:02:41 -0700250func (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.
256func (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.
262func (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.
268func (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.
275func (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.
281func (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.
287func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
288 s.log(FatalLevel, msg, nil, keysAndValues)
289}
290
Abhay Kumar40252eb2025-10-13 13:25:53 +0000291// Logln logs a message at provided level.
292// Spaces are always added between arguments.
293func (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.
299func (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.
305func (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.
311func (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.
317func (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.
324func (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.
330func (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.
336func (s *SugaredLogger) Fatalln(args ...interface{}) {
337 s.logln(FatalLevel, args, nil)
338}
339
Scott Baker2c1c4822019-10-16 11:02:41 -0700340// Sync flushes any buffered log entries.
341func (s *SugaredLogger) Sync() error {
342 return s.base.Sync()
343}
344
Abhay Kumar40252eb2025-10-13 13:25:53 +0000345// log message with Sprint, Sprintf, or neither.
Scott Baker2c1c4822019-10-16 11:02:41 -0700346func (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
khenaidoo26721882021-08-11 17:42:52 -0400353 msg := getMessage(template, fmtArgs)
Scott Baker2c1c4822019-10-16 11:02:41 -0700354 if ce := s.base.Check(lvl, msg); ce != nil {
355 ce.Write(s.sweetenFields(context)...)
356 }
357}
358
Abhay Kumar40252eb2025-10-13 13:25:53 +0000359// logln message with Sprintln
360func (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
khenaidoo26721882021-08-11 17:42:52 -0400371// getMessage format with Sprint, Sprintf, or neither.
372func 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
Abhay Kumar40252eb2025-10-13 13:25:53 +0000389// getMessageln format with Sprintln.
390func getMessageln(fmtArgs []interface{}) string {
391 msg := fmt.Sprintln(fmtArgs...)
392 return msg[:len(msg)-1]
393}
394
Scott Baker2c1c4822019-10-16 11:02:41 -0700395func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
396 if len(args) == 0 {
397 return nil
398 }
399
Abhay Kumar40252eb2025-10-13 13:25:53 +0000400 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 )
Scott Baker2c1c4822019-10-16 11:02:41 -0700407
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
Abhay Kumar40252eb2025-10-13 13:25:53 +0000416 // 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
Scott Baker2c1c4822019-10-16 11:02:41 -0700428 // Make sure this element isn't a dangling key.
429 if i == len(args)-1 {
khenaidoo26721882021-08-11 17:42:52 -0400430 s.base.Error(_oddNumberErrMsg, Any("ignored", args[i]))
Scott Baker2c1c4822019-10-16 11:02:41 -0700431 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 {
khenaidoo26721882021-08-11 17:42:52 -0400451 s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid))
Scott Baker2c1c4822019-10-16 11:02:41 -0700452 }
453 return fields
454}
455
456type invalidPair struct {
457 position int
458 key, value interface{}
459}
460
461func (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
468type invalidPairs []invalidPair
469
470func (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}