// Copyright (c) 2016 Uber Technologies, Inc.1//2// Permission is hereby granted, free of charge, to any person obtaining a copy3// of this software and associated documentation files (the "Software"), to deal4// in the Software without restriction, including without limitation the rights5// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell6// copies of the Software, and to permit persons to whom the Software is7// furnished to do so, subject to the following conditions:8//9// The above copyright notice and this permission notice shall be included in10// all copies or substantial portions of the Software.11//12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER16// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN18// THE SOFTWARE.1920package zap2122import (23"fmt"2425"go.uber.org/zap/zapcore"2627"go.uber.org/multierr"28)2930const (31_oddNumberErrMsg = "Ignored key without a value."32_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."33_multipleErrMsg = "Multiple errors without a key."34)3536// A SugaredLogger wraps the base Logger functionality in a slower, but less37// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar38// method.39//40// Unlike the Logger, the SugaredLogger doesn't insist on structured logging.41// For each log level, it exposes four methods:42//43// - methods named after the log level for log.Print-style logging44// - methods ending in "w" for loosely-typed structured logging45// - methods ending in "f" for log.Printf-style logging46// - methods ending in "ln" for log.Println-style logging47//48// For example, the methods for InfoLevel are:49//50// Info(...any) Print-style logging51// Infow(...any) Structured logging (read as "info with")52// Infof(string, ...any) Printf-style logging53// Infoln(...any) Println-style logging54type SugaredLogger struct {55base *Logger56}5758// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring59// is quite inexpensive, so it's reasonable for a single application to use60// both Loggers and SugaredLoggers, converting between them on the boundaries61// of performance-sensitive code.62func (s *SugaredLogger) Desugar() *Logger {63base := s.base.clone()64base.callerSkip -= 265return base66}6768// Named adds a sub-scope to the logger's name. See Logger.Named for details.69func (s *SugaredLogger) Named(name string) *SugaredLogger {70return &SugaredLogger{base: s.base.Named(name)}71}7273// WithOptions clones the current SugaredLogger, applies the supplied Options,74// and returns the result. It's safe to use concurrently.75func (s *SugaredLogger) WithOptions(opts ...Option) *SugaredLogger {76base := s.base.clone()77for _, opt := range opts {78opt.apply(base)79}80return &SugaredLogger{base: base}81}8283// With adds a variadic number of fields to the logging context. It accepts a84// mix of strongly-typed Field objects and loosely-typed key-value pairs. When85// processing pairs, the first element of the pair is used as the field key86// and the second as the field value.87//88// For example,89//90// sugaredLogger.With(91// "hello", "world",92// "failure", errors.New("oh no"),93// Stack(),94// "count", 42,95// "user", User{Name: "alice"},96// )97//98// is the equivalent of99//100// unsugared.With(101// String("hello", "world"),102// String("failure", "oh no"),103// Stack(),104// Int("count", 42),105// Object("user", User{Name: "alice"}),106// )107//108// Note that the keys in key-value pairs should be strings. In development,109// passing a non-string key panics. In production, the logger is more110// forgiving: a separate error is logged, but the key-value pair is skipped111// and execution continues. Passing an orphaned key triggers similar behavior:112// panics in development and errors in production.113func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger {114return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)}115}116117// WithLazy adds a variadic number of fields to the logging context lazily.118// The fields are evaluated only if the logger is further chained with [With]119// or is written to with any of the log level methods.120// Until that occurs, the logger may retain references to objects inside the fields,121// and logging will reflect the state of an object at the time of logging,122// not the time of WithLazy().123//124// Similar to [With], fields added to the child don't affect the parent,125// and vice versa. Also, the keys in key-value pairs should be strings. In development,126// passing a non-string key panics, while in production it logs an error and skips the pair.127// Passing an orphaned key has the same behavior.128func (s *SugaredLogger) WithLazy(args ...interface{}) *SugaredLogger {129return &SugaredLogger{base: s.base.WithLazy(s.sweetenFields(args)...)}130}131132// Level reports the minimum enabled level for this logger.133//134// For NopLoggers, this is [zapcore.InvalidLevel].135func (s *SugaredLogger) Level() zapcore.Level {136return zapcore.LevelOf(s.base.core)137}138139// Log logs the provided arguments at provided level.140// Spaces are added between arguments when neither is a string.141func (s *SugaredLogger) Log(lvl zapcore.Level, args ...interface{}) {142s.log(lvl, "", args, nil)143}144145// Debug logs the provided arguments at [DebugLevel].146// Spaces are added between arguments when neither is a string.147func (s *SugaredLogger) Debug(args ...interface{}) {148s.log(DebugLevel, "", args, nil)149}150151// Info logs the provided arguments at [InfoLevel].152// Spaces are added between arguments when neither is a string.153func (s *SugaredLogger) Info(args ...interface{}) {154s.log(InfoLevel, "", args, nil)155}156157// Warn logs the provided arguments at [WarnLevel].158// Spaces are added between arguments when neither is a string.159func (s *SugaredLogger) Warn(args ...interface{}) {160s.log(WarnLevel, "", args, nil)161}162163// Error logs the provided arguments at [ErrorLevel].164// Spaces are added between arguments when neither is a string.165func (s *SugaredLogger) Error(args ...interface{}) {166s.log(ErrorLevel, "", args, nil)167}168169// DPanic logs the provided arguments at [DPanicLevel].170// In development, the logger then panics. (See [DPanicLevel] for details.)171// Spaces are added between arguments when neither is a string.172func (s *SugaredLogger) DPanic(args ...interface{}) {173s.log(DPanicLevel, "", args, nil)174}175176// Panic constructs a message with the provided arguments and panics.177// Spaces are added between arguments when neither is a string.178func (s *SugaredLogger) Panic(args ...interface{}) {179s.log(PanicLevel, "", args, nil)180}181182// Fatal constructs a message with the provided arguments and calls os.Exit.183// Spaces are added between arguments when neither is a string.184func (s *SugaredLogger) Fatal(args ...interface{}) {185s.log(FatalLevel, "", args, nil)186}187188// Logf formats the message according to the format specifier189// and logs it at provided level.190func (s *SugaredLogger) Logf(lvl zapcore.Level, template string, args ...interface{}) {191s.log(lvl, template, args, nil)192}193194// Debugf formats the message according to the format specifier195// and logs it at [DebugLevel].196func (s *SugaredLogger) Debugf(template string, args ...interface{}) {197s.log(DebugLevel, template, args, nil)198}199200// Infof formats the message according to the format specifier201// and logs it at [InfoLevel].202func (s *SugaredLogger) Infof(template string, args ...interface{}) {203s.log(InfoLevel, template, args, nil)204}205206// Warnf formats the message according to the format specifier207// and logs it at [WarnLevel].208func (s *SugaredLogger) Warnf(template string, args ...interface{}) {209s.log(WarnLevel, template, args, nil)210}211212// Errorf formats the message according to the format specifier213// and logs it at [ErrorLevel].214func (s *SugaredLogger) Errorf(template string, args ...interface{}) {215s.log(ErrorLevel, template, args, nil)216}217218// DPanicf formats the message according to the format specifier219// and logs it at [DPanicLevel].220// In development, the logger then panics. (See [DPanicLevel] for details.)221func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {222s.log(DPanicLevel, template, args, nil)223}224225// Panicf formats the message according to the format specifier226// and panics.227func (s *SugaredLogger) Panicf(template string, args ...interface{}) {228s.log(PanicLevel, template, args, nil)229}230231// Fatalf formats the message according to the format specifier232// and calls os.Exit.233func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {234s.log(FatalLevel, template, args, nil)235}236237// Logw logs a message with some additional context. The variadic key-value238// pairs are treated as they are in With.239func (s *SugaredLogger) Logw(lvl zapcore.Level, msg string, keysAndValues ...interface{}) {240s.log(lvl, msg, nil, keysAndValues)241}242243// Debugw logs a message with some additional context. The variadic key-value244// pairs are treated as they are in With.245//246// When debug-level logging is disabled, this is much faster than247//248// s.With(keysAndValues).Debug(msg)249func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) {250s.log(DebugLevel, msg, nil, keysAndValues)251}252253// Infow logs a message with some additional context. The variadic key-value254// pairs are treated as they are in With.255func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) {256s.log(InfoLevel, msg, nil, keysAndValues)257}258259// Warnw logs a message with some additional context. The variadic key-value260// pairs are treated as they are in With.261func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) {262s.log(WarnLevel, msg, nil, keysAndValues)263}264265// Errorw logs a message with some additional context. The variadic key-value266// pairs are treated as they are in With.267func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) {268s.log(ErrorLevel, msg, nil, keysAndValues)269}270271// DPanicw logs a message with some additional context. In development, the272// logger then panics. (See DPanicLevel for details.) The variadic key-value273// pairs are treated as they are in With.274func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) {275s.log(DPanicLevel, msg, nil, keysAndValues)276}277278// Panicw logs a message with some additional context, then panics. The279// variadic key-value pairs are treated as they are in With.280func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) {281s.log(PanicLevel, msg, nil, keysAndValues)282}283284// Fatalw logs a message with some additional context, then calls os.Exit. The285// variadic key-value pairs are treated as they are in With.286func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {287s.log(FatalLevel, msg, nil, keysAndValues)288}289290// Logln logs a message at provided level.291// Spaces are always added between arguments.292func (s *SugaredLogger) Logln(lvl zapcore.Level, args ...interface{}) {293s.logln(lvl, args, nil)294}295296// Debugln logs a message at [DebugLevel].297// Spaces are always added between arguments.298func (s *SugaredLogger) Debugln(args ...interface{}) {299s.logln(DebugLevel, args, nil)300}301302// Infoln logs a message at [InfoLevel].303// Spaces are always added between arguments.304func (s *SugaredLogger) Infoln(args ...interface{}) {305s.logln(InfoLevel, args, nil)306}307308// Warnln logs a message at [WarnLevel].309// Spaces are always added between arguments.310func (s *SugaredLogger) Warnln(args ...interface{}) {311s.logln(WarnLevel, args, nil)312}313314// Errorln logs a message at [ErrorLevel].315// Spaces are always added between arguments.316func (s *SugaredLogger) Errorln(args ...interface{}) {317s.logln(ErrorLevel, args, nil)318}319320// DPanicln logs a message at [DPanicLevel].321// In development, the logger then panics. (See [DPanicLevel] for details.)322// Spaces are always added between arguments.323func (s *SugaredLogger) DPanicln(args ...interface{}) {324s.logln(DPanicLevel, args, nil)325}326327// Panicln logs a message at [PanicLevel] and panics.328// Spaces are always added between arguments.329func (s *SugaredLogger) Panicln(args ...interface{}) {330s.logln(PanicLevel, args, nil)331}332333// Fatalln logs a message at [FatalLevel] and calls os.Exit.334// Spaces are always added between arguments.335func (s *SugaredLogger) Fatalln(args ...interface{}) {336s.logln(FatalLevel, args, nil)337}338339// Sync flushes any buffered log entries.340func (s *SugaredLogger) Sync() error {341return s.base.Sync()342}343344// log message with Sprint, Sprintf, or neither.345func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) {346// If logging at this level is completely disabled, skip the overhead of347// string formatting.348if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {349return350}351352msg := getMessage(template, fmtArgs)353if ce := s.base.Check(lvl, msg); ce != nil {354ce.Write(s.sweetenFields(context)...)355}356}357358// logln message with Sprintln359func (s *SugaredLogger) logln(lvl zapcore.Level, fmtArgs []interface{}, context []interface{}) {360if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {361return362}363364msg := getMessageln(fmtArgs)365if ce := s.base.Check(lvl, msg); ce != nil {366ce.Write(s.sweetenFields(context)...)367}368}369370// getMessage format with Sprint, Sprintf, or neither.371func getMessage(template string, fmtArgs []interface{}) string {372if len(fmtArgs) == 0 {373return template374}375376if template != "" {377return fmt.Sprintf(template, fmtArgs...)378}379380if len(fmtArgs) == 1 {381if str, ok := fmtArgs[0].(string); ok {382return str383}384}385return fmt.Sprint(fmtArgs...)386}387388// getMessageln format with Sprintln.389func getMessageln(fmtArgs []interface{}) string {390msg := fmt.Sprintln(fmtArgs...)391return msg[:len(msg)-1]392}393394func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {395if len(args) == 0 {396return nil397}398399var (400// Allocate enough space for the worst case; if users pass only structured401// fields, we shouldn't penalize them with extra allocations.402fields = make([]Field, 0, len(args))403invalid invalidPairs404seenError bool405)406407for i := 0; i < len(args); {408// This is a strongly-typed field. Consume it and move on.409if f, ok := args[i].(Field); ok {410fields = append(fields, f)411i++412continue413}414415// If it is an error, consume it and move on.416if err, ok := args[i].(error); ok {417if !seenError {418seenError = true419fields = append(fields, Error(err))420} else {421s.base.Error(_multipleErrMsg, Error(err))422}423i++424continue425}426427// Make sure this element isn't a dangling key.428if i == len(args)-1 {429s.base.Error(_oddNumberErrMsg, Any("ignored", args[i]))430break431}432433// Consume this value and the next, treating them as a key-value pair. If the434// key isn't a string, add this pair to the slice of invalid pairs.435key, val := args[i], args[i+1]436if keyStr, ok := key.(string); !ok {437// Subsequent errors are likely, so allocate once up front.438if cap(invalid) == 0 {439invalid = make(invalidPairs, 0, len(args)/2)440}441invalid = append(invalid, invalidPair{i, key, val})442} else {443fields = append(fields, Any(keyStr, val))444}445i += 2446}447448// If we encountered any invalid key-value pairs, log an error.449if len(invalid) > 0 {450s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid))451}452return fields453}454455type invalidPair struct {456position int457key, value interface{}458}459460func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {461enc.AddInt64("position", int64(p.position))462Any("key", p.key).AddTo(enc)463Any("value", p.value).AddTo(enc)464return nil465}466467type invalidPairs []invalidPair468469func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {470var err error471for i := range ps {472err = multierr.Append(err, enc.AppendObject(ps[i]))473}474return err475}476477478