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