You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
10 KiB
315 lines
10 KiB
// Copyright (c) 2016 Uber Technologies, Inc. |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy |
|
// of this software and associated documentation files (the "Software"), to deal |
|
// in the Software without restriction, including without limitation the rights |
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
// copies of the Software, and to permit persons to whom the Software is |
|
// furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in |
|
// all copies or substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
// THE SOFTWARE. |
|
|
|
package zap |
|
|
|
import ( |
|
"fmt" |
|
|
|
"go.uber.org/zap/zapcore" |
|
|
|
"go.uber.org/multierr" |
|
) |
|
|
|
const ( |
|
_oddNumberErrMsg = "Ignored key without a value." |
|
_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." |
|
) |
|
|
|
// A SugaredLogger wraps the base Logger functionality in a slower, but less |
|
// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar |
|
// method. |
|
// |
|
// Unlike the Logger, the SugaredLogger doesn't insist on structured logging. |
|
// For each log level, it exposes three methods: one for loosely-typed |
|
// structured logging, one for println-style formatting, and one for |
|
// printf-style formatting. For example, SugaredLoggers can produce InfoLevel |
|
// output with Infow ("info with" structured context), Info, or Infof. |
|
type SugaredLogger struct { |
|
base *Logger |
|
} |
|
|
|
// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring |
|
// is quite inexpensive, so it's reasonable for a single application to use |
|
// both Loggers and SugaredLoggers, converting between them on the boundaries |
|
// of performance-sensitive code. |
|
func (s *SugaredLogger) Desugar() *Logger { |
|
base := s.base.clone() |
|
base.callerSkip -= 2 |
|
return base |
|
} |
|
|
|
// Named adds a sub-scope to the logger's name. See Logger.Named for details. |
|
func (s *SugaredLogger) Named(name string) *SugaredLogger { |
|
return &SugaredLogger{base: s.base.Named(name)} |
|
} |
|
|
|
// With adds a variadic number of fields to the logging context. It accepts a |
|
// mix of strongly-typed Field objects and loosely-typed key-value pairs. When |
|
// processing pairs, the first element of the pair is used as the field key |
|
// and the second as the field value. |
|
// |
|
// For example, |
|
// sugaredLogger.With( |
|
// "hello", "world", |
|
// "failure", errors.New("oh no"), |
|
// Stack(), |
|
// "count", 42, |
|
// "user", User{Name: "alice"}, |
|
// ) |
|
// is the equivalent of |
|
// unsugared.With( |
|
// String("hello", "world"), |
|
// String("failure", "oh no"), |
|
// Stack(), |
|
// Int("count", 42), |
|
// Object("user", User{Name: "alice"}), |
|
// ) |
|
// |
|
// Note that the keys in key-value pairs should be strings. In development, |
|
// passing a non-string key panics. In production, the logger is more |
|
// forgiving: a separate error is logged, but the key-value pair is skipped |
|
// and execution continues. Passing an orphaned key triggers similar behavior: |
|
// panics in development and errors in production. |
|
func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { |
|
return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} |
|
} |
|
|
|
// Debug uses fmt.Sprint to construct and log a message. |
|
func (s *SugaredLogger) Debug(args ...interface{}) { |
|
s.log(DebugLevel, "", args, nil) |
|
} |
|
|
|
// Info uses fmt.Sprint to construct and log a message. |
|
func (s *SugaredLogger) Info(args ...interface{}) { |
|
s.log(InfoLevel, "", args, nil) |
|
} |
|
|
|
// Warn uses fmt.Sprint to construct and log a message. |
|
func (s *SugaredLogger) Warn(args ...interface{}) { |
|
s.log(WarnLevel, "", args, nil) |
|
} |
|
|
|
// Error uses fmt.Sprint to construct and log a message. |
|
func (s *SugaredLogger) Error(args ...interface{}) { |
|
s.log(ErrorLevel, "", args, nil) |
|
} |
|
|
|
// DPanic uses fmt.Sprint to construct and log a message. In development, the |
|
// logger then panics. (See DPanicLevel for details.) |
|
func (s *SugaredLogger) DPanic(args ...interface{}) { |
|
s.log(DPanicLevel, "", args, nil) |
|
} |
|
|
|
// Panic uses fmt.Sprint to construct and log a message, then panics. |
|
func (s *SugaredLogger) Panic(args ...interface{}) { |
|
s.log(PanicLevel, "", args, nil) |
|
} |
|
|
|
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit. |
|
func (s *SugaredLogger) Fatal(args ...interface{}) { |
|
s.log(FatalLevel, "", args, nil) |
|
} |
|
|
|
// Debugf uses fmt.Sprintf to log a templated message. |
|
func (s *SugaredLogger) Debugf(template string, args ...interface{}) { |
|
s.log(DebugLevel, template, args, nil) |
|
} |
|
|
|
// Infof uses fmt.Sprintf to log a templated message. |
|
func (s *SugaredLogger) Infof(template string, args ...interface{}) { |
|
s.log(InfoLevel, template, args, nil) |
|
} |
|
|
|
// Warnf uses fmt.Sprintf to log a templated message. |
|
func (s *SugaredLogger) Warnf(template string, args ...interface{}) { |
|
s.log(WarnLevel, template, args, nil) |
|
} |
|
|
|
// Errorf uses fmt.Sprintf to log a templated message. |
|
func (s *SugaredLogger) Errorf(template string, args ...interface{}) { |
|
s.log(ErrorLevel, template, args, nil) |
|
} |
|
|
|
// DPanicf uses fmt.Sprintf to log a templated message. In development, the |
|
// logger then panics. (See DPanicLevel for details.) |
|
func (s *SugaredLogger) DPanicf(template string, args ...interface{}) { |
|
s.log(DPanicLevel, template, args, nil) |
|
} |
|
|
|
// Panicf uses fmt.Sprintf to log a templated message, then panics. |
|
func (s *SugaredLogger) Panicf(template string, args ...interface{}) { |
|
s.log(PanicLevel, template, args, nil) |
|
} |
|
|
|
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit. |
|
func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { |
|
s.log(FatalLevel, template, args, nil) |
|
} |
|
|
|
// Debugw logs a message with some additional context. The variadic key-value |
|
// pairs are treated as they are in With. |
|
// |
|
// When debug-level logging is disabled, this is much faster than |
|
// s.With(keysAndValues).Debug(msg) |
|
func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { |
|
s.log(DebugLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Infow logs a message with some additional context. The variadic key-value |
|
// pairs are treated as they are in With. |
|
func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) { |
|
s.log(InfoLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Warnw logs a message with some additional context. The variadic key-value |
|
// pairs are treated as they are in With. |
|
func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) { |
|
s.log(WarnLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Errorw logs a message with some additional context. The variadic key-value |
|
// pairs are treated as they are in With. |
|
func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { |
|
s.log(ErrorLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// DPanicw logs a message with some additional context. In development, the |
|
// logger then panics. (See DPanicLevel for details.) The variadic key-value |
|
// pairs are treated as they are in With. |
|
func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) { |
|
s.log(DPanicLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Panicw logs a message with some additional context, then panics. The |
|
// variadic key-value pairs are treated as they are in With. |
|
func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) { |
|
s.log(PanicLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Fatalw logs a message with some additional context, then calls os.Exit. The |
|
// variadic key-value pairs are treated as they are in With. |
|
func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { |
|
s.log(FatalLevel, msg, nil, keysAndValues) |
|
} |
|
|
|
// Sync flushes any buffered log entries. |
|
func (s *SugaredLogger) Sync() error { |
|
return s.base.Sync() |
|
} |
|
|
|
func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { |
|
// If logging at this level is completely disabled, skip the overhead of |
|
// string formatting. |
|
if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { |
|
return |
|
} |
|
|
|
msg := getMessage(template, fmtArgs) |
|
if ce := s.base.Check(lvl, msg); ce != nil { |
|
ce.Write(s.sweetenFields(context)...) |
|
} |
|
} |
|
|
|
// getMessage format with Sprint, Sprintf, or neither. |
|
func getMessage(template string, fmtArgs []interface{}) string { |
|
if len(fmtArgs) == 0 { |
|
return template |
|
} |
|
|
|
if template != "" { |
|
return fmt.Sprintf(template, fmtArgs...) |
|
} |
|
|
|
if len(fmtArgs) == 1 { |
|
if str, ok := fmtArgs[0].(string); ok { |
|
return str |
|
} |
|
} |
|
return fmt.Sprint(fmtArgs...) |
|
} |
|
|
|
func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { |
|
if len(args) == 0 { |
|
return nil |
|
} |
|
|
|
// Allocate enough space for the worst case; if users pass only structured |
|
// fields, we shouldn't penalize them with extra allocations. |
|
fields := make([]Field, 0, len(args)) |
|
var invalid invalidPairs |
|
|
|
for i := 0; i < len(args); { |
|
// This is a strongly-typed field. Consume it and move on. |
|
if f, ok := args[i].(Field); ok { |
|
fields = append(fields, f) |
|
i++ |
|
continue |
|
} |
|
|
|
// Make sure this element isn't a dangling key. |
|
if i == len(args)-1 { |
|
s.base.Error(_oddNumberErrMsg, Any("ignored", args[i])) |
|
break |
|
} |
|
|
|
// Consume this value and the next, treating them as a key-value pair. If the |
|
// key isn't a string, add this pair to the slice of invalid pairs. |
|
key, val := args[i], args[i+1] |
|
if keyStr, ok := key.(string); !ok { |
|
// Subsequent errors are likely, so allocate once up front. |
|
if cap(invalid) == 0 { |
|
invalid = make(invalidPairs, 0, len(args)/2) |
|
} |
|
invalid = append(invalid, invalidPair{i, key, val}) |
|
} else { |
|
fields = append(fields, Any(keyStr, val)) |
|
} |
|
i += 2 |
|
} |
|
|
|
// If we encountered any invalid key-value pairs, log an error. |
|
if len(invalid) > 0 { |
|
s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid)) |
|
} |
|
return fields |
|
} |
|
|
|
type invalidPair struct { |
|
position int |
|
key, value interface{} |
|
} |
|
|
|
func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { |
|
enc.AddInt64("position", int64(p.position)) |
|
Any("key", p.key).AddTo(enc) |
|
Any("value", p.value).AddTo(enc) |
|
return nil |
|
} |
|
|
|
type invalidPairs []invalidPair |
|
|
|
func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { |
|
var err error |
|
for i := range ps { |
|
err = multierr.Append(err, enc.AppendObject(ps[i])) |
|
} |
|
return err |
|
}
|
|
|