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
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."
34)
35
36// A SugaredLogger wraps the base Logger functionality in a slower, but less
37// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar
38// method.
39//
40// Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
41// For each log level, it exposes three methods: one for loosely-typed
42// structured logging, one for println-style formatting, and one for
43// printf-style formatting. For example, SugaredLoggers can produce InfoLevel
44// output with Infow ("info with" structured context), Info, or Infof.
45type SugaredLogger struct {
46	base *Logger
47}
48
49// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring
50// is quite inexpensive, so it's reasonable for a single application to use
51// both Loggers and SugaredLoggers, converting between them on the boundaries
52// of performance-sensitive code.
53func (s *SugaredLogger) Desugar() *Logger {
54	base := s.base.clone()
55	base.callerSkip -= 2
56	return base
57}
58
59// Named adds a sub-scope to the logger's name. See Logger.Named for details.
60func (s *SugaredLogger) Named(name string) *SugaredLogger {
61	return &SugaredLogger{base: s.base.Named(name)}
62}
63
64// With adds a variadic number of fields to the logging context. It accepts a
65// mix of strongly-typed Field objects and loosely-typed key-value pairs. When
66// processing pairs, the first element of the pair is used as the field key
67// and the second as the field value.
68//
69// For example,
70//   sugaredLogger.With(
71//     "hello", "world",
72//     "failure", errors.New("oh no"),
73//     Stack(),
74//     "count", 42,
75//     "user", User{Name: "alice"},
76//  )
77// is the equivalent of
78//   unsugared.With(
79//     String("hello", "world"),
80//     String("failure", "oh no"),
81//     Stack(),
82//     Int("count", 42),
83//     Object("user", User{Name: "alice"}),
84//   )
85//
86// Note that the keys in key-value pairs should be strings. In development,
87// passing a non-string key panics. In production, the logger is more
88// forgiving: a separate error is logged, but the key-value pair is skipped
89// and execution continues. Passing an orphaned key triggers similar behavior:
90// panics in development and errors in production.
91func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger {
92	return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)}
93}
94
95// Debug uses fmt.Sprint to construct and log a message.
96func (s *SugaredLogger) Debug(args ...interface{}) {
97	s.log(DebugLevel, "", args, nil)
98}
99
100// Info uses fmt.Sprint to construct and log a message.
101func (s *SugaredLogger) Info(args ...interface{}) {
102	s.log(InfoLevel, "", args, nil)
103}
104
105// Warn uses fmt.Sprint to construct and log a message.
106func (s *SugaredLogger) Warn(args ...interface{}) {
107	s.log(WarnLevel, "", args, nil)
108}
109
110// Error uses fmt.Sprint to construct and log a message.
111func (s *SugaredLogger) Error(args ...interface{}) {
112	s.log(ErrorLevel, "", args, nil)
113}
114
115// DPanic uses fmt.Sprint to construct and log a message. In development, the
116// logger then panics. (See DPanicLevel for details.)
117func (s *SugaredLogger) DPanic(args ...interface{}) {
118	s.log(DPanicLevel, "", args, nil)
119}
120
121// Panic uses fmt.Sprint to construct and log a message, then panics.
122func (s *SugaredLogger) Panic(args ...interface{}) {
123	s.log(PanicLevel, "", args, nil)
124}
125
126// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
127func (s *SugaredLogger) Fatal(args ...interface{}) {
128	s.log(FatalLevel, "", args, nil)
129}
130
131// Debugf uses fmt.Sprintf to log a templated message.
132func (s *SugaredLogger) Debugf(template string, args ...interface{}) {
133	s.log(DebugLevel, template, args, nil)
134}
135
136// Infof uses fmt.Sprintf to log a templated message.
137func (s *SugaredLogger) Infof(template string, args ...interface{}) {
138	s.log(InfoLevel, template, args, nil)
139}
140
141// Warnf uses fmt.Sprintf to log a templated message.
142func (s *SugaredLogger) Warnf(template string, args ...interface{}) {
143	s.log(WarnLevel, template, args, nil)
144}
145
146// Errorf uses fmt.Sprintf to log a templated message.
147func (s *SugaredLogger) Errorf(template string, args ...interface{}) {
148	s.log(ErrorLevel, template, args, nil)
149}
150
151// DPanicf uses fmt.Sprintf to log a templated message. In development, the
152// logger then panics. (See DPanicLevel for details.)
153func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {
154	s.log(DPanicLevel, template, args, nil)
155}
156
157// Panicf uses fmt.Sprintf to log a templated message, then panics.
158func (s *SugaredLogger) Panicf(template string, args ...interface{}) {
159	s.log(PanicLevel, template, args, nil)
160}
161
162// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
163func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {
164	s.log(FatalLevel, template, args, nil)
165}
166
167// Debugw logs a message with some additional context. The variadic key-value
168// pairs are treated as they are in With.
169//
170// When debug-level logging is disabled, this is much faster than
171//  s.With(keysAndValues).Debug(msg)
172func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) {
173	s.log(DebugLevel, msg, nil, keysAndValues)
174}
175
176// Infow logs a message with some additional context. The variadic key-value
177// pairs are treated as they are in With.
178func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) {
179	s.log(InfoLevel, msg, nil, keysAndValues)
180}
181
182// Warnw logs a message with some additional context. The variadic key-value
183// pairs are treated as they are in With.
184func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) {
185	s.log(WarnLevel, msg, nil, keysAndValues)
186}
187
188// Errorw logs a message with some additional context. The variadic key-value
189// pairs are treated as they are in With.
190func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) {
191	s.log(ErrorLevel, msg, nil, keysAndValues)
192}
193
194// DPanicw logs a message with some additional context. In development, the
195// logger then panics. (See DPanicLevel for details.) The variadic key-value
196// pairs are treated as they are in With.
197func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) {
198	s.log(DPanicLevel, msg, nil, keysAndValues)
199}
200
201// Panicw logs a message with some additional context, then panics. The
202// variadic key-value pairs are treated as they are in With.
203func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) {
204	s.log(PanicLevel, msg, nil, keysAndValues)
205}
206
207// Fatalw logs a message with some additional context, then calls os.Exit. The
208// variadic key-value pairs are treated as they are in With.
209func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
210	s.log(FatalLevel, msg, nil, keysAndValues)
211}
212
213// Sync flushes any buffered log entries.
214func (s *SugaredLogger) Sync() error {
215	return s.base.Sync()
216}
217
218func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) {
219	// If logging at this level is completely disabled, skip the overhead of
220	// string formatting.
221	if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {
222		return
223	}
224
225	// Format with Sprint, Sprintf, or neither.
226	msg := template
227	if msg == "" && len(fmtArgs) > 0 {
228		msg = fmt.Sprint(fmtArgs...)
229	} else if msg != "" && len(fmtArgs) > 0 {
230		msg = fmt.Sprintf(template, fmtArgs...)
231	}
232
233	if ce := s.base.Check(lvl, msg); ce != nil {
234		ce.Write(s.sweetenFields(context)...)
235	}
236}
237
238func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
239	if len(args) == 0 {
240		return nil
241	}
242
243	// Allocate enough space for the worst case; if users pass only structured
244	// fields, we shouldn't penalize them with extra allocations.
245	fields := make([]Field, 0, len(args))
246	var invalid invalidPairs
247
248	for i := 0; i < len(args); {
249		// This is a strongly-typed field. Consume it and move on.
250		if f, ok := args[i].(Field); ok {
251			fields = append(fields, f)
252			i++
253			continue
254		}
255
256		// Make sure this element isn't a dangling key.
257		if i == len(args)-1 {
258			s.base.DPanic(_oddNumberErrMsg, Any("ignored", args[i]))
259			break
260		}
261
262		// Consume this value and the next, treating them as a key-value pair. If the
263		// key isn't a string, add this pair to the slice of invalid pairs.
264		key, val := args[i], args[i+1]
265		if keyStr, ok := key.(string); !ok {
266			// Subsequent errors are likely, so allocate once up front.
267			if cap(invalid) == 0 {
268				invalid = make(invalidPairs, 0, len(args)/2)
269			}
270			invalid = append(invalid, invalidPair{i, key, val})
271		} else {
272			fields = append(fields, Any(keyStr, val))
273		}
274		i += 2
275	}
276
277	// If we encountered any invalid key-value pairs, log an error.
278	if len(invalid) > 0 {
279		s.base.DPanic(_nonStringKeyErrMsg, Array("invalid", invalid))
280	}
281	return fields
282}
283
284type invalidPair struct {
285	position   int
286	key, value interface{}
287}
288
289func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {
290	enc.AddInt64("position", int64(p.position))
291	Any("key", p.key).AddTo(enc)
292	Any("value", p.value).AddTo(enc)
293	return nil
294}
295
296type invalidPairs []invalidPair
297
298func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
299	var err error
300	for i := range ps {
301		err = multierr.Append(err, enc.AppendObject(ps[i]))
302	}
303	return err
304}
305