Skip to content

Commit

Permalink
SugaredLogger: Add *ln variants (#1080)
Browse files Browse the repository at this point in the history
The existing `SugaredLogger.${Level}(..)` methods use `fmt.Print`-style
formatting.  Add `${Level}ln` variants for `Println`-stye formatting.

Refs #889
  • Loading branch information
zhupeijun committed Apr 12, 2022
1 parent 1d435ba commit d38507c
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 4 deletions.
72 changes: 68 additions & 4 deletions sugar.go
Expand Up @@ -38,10 +38,19 @@ const (
// 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.
// For each log level, it exposes four methods:
//
// - methods named after the log level for log.Print-style logging
// - methods ending in "w" for loosely-typed structured logging
// - methods ending in "f" for log.Printf-style logging
// - methods ending in "ln" for log.Println-style logging
//
// For example, the methods for InfoLevel are:
//
// Info(...any) Print-style logging
// Infow(...any) Structured logging (read as "info with")
// Infof(string, ...any) Printf-style logging
// Infoln(...any) Println-style logging
type SugaredLogger struct {
base *Logger
}
Expand Down Expand Up @@ -220,11 +229,48 @@ func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
s.log(FatalLevel, msg, nil, keysAndValues)
}

// Debugln uses fmt.Sprintln to construct and log a message.
func (s *SugaredLogger) Debugln(args ...interface{}) {
s.logln(DebugLevel, "", args, nil)
}

// Infoln uses fmt.Sprintln to construct and log a message.
func (s *SugaredLogger) Infoln(args ...interface{}) {
s.logln(InfoLevel, "", args, nil)
}

// Warnln uses fmt.Sprintln to construct and log a message.
func (s *SugaredLogger) Warnln(args ...interface{}) {
s.logln(WarnLevel, "", args, nil)
}

// Errorln uses fmt.Sprintln to construct and log a message.
func (s *SugaredLogger) Errorln(args ...interface{}) {
s.logln(ErrorLevel, "", args, nil)
}

// DPanicln uses fmt.Sprintln to construct and log a message. In development, the
// logger then panics. (See DPanicLevel for details.)
func (s *SugaredLogger) DPanicln(args ...interface{}) {
s.logln(DPanicLevel, "", args, nil)
}

// Panicln uses fmt.Sprintln to construct and log a message, then panics.
func (s *SugaredLogger) Panicln(args ...interface{}) {
s.logln(PanicLevel, "", args, nil)
}

// Fatalln uses fmt.Sprintln to construct and log a message, then calls os.Exit.
func (s *SugaredLogger) Fatalln(args ...interface{}) {
s.logln(FatalLevel, "", args, nil)
}

// Sync flushes any buffered log entries.
func (s *SugaredLogger) Sync() error {
return s.base.Sync()
}

// log message with Sprint, Sprintf, or neither.
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.
Expand All @@ -238,6 +284,18 @@ func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interf
}
}

// logln message with Sprintln
func (s *SugaredLogger) logln(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) {
if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {
return
}

msg := getMessageln(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 {
Expand All @@ -256,6 +314,12 @@ func getMessage(template string, fmtArgs []interface{}) string {
return fmt.Sprint(fmtArgs...)
}

// getMessageln format with Sprintln.
func getMessageln(fmtArgs []interface{}) string {
msg := fmt.Sprintln(fmtArgs...)
return msg[:len(msg)-1]
}

func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
if len(args) == 0 {
return nil
Expand Down
73 changes: 73 additions & 0 deletions sugar_test.go
Expand Up @@ -261,6 +261,49 @@ func TestSugarTemplatedLogging(t *testing.T) {
}
}

func TestSugarLnLogging(t *testing.T) {
tests := []struct {
args []interface{}
expect string
}{
{nil, ""},
{[]interface{}{}, ""},
{[]interface{}{""}, ""},
{[]interface{}{"foo"}, "foo"},
{[]interface{}{"foo", "bar"}, "foo bar"},
}

// Common to all test cases.
context := []interface{}{"foo", "bar"}
expectedFields := []Field{String("foo", "bar")}

for _, tt := range tests {
withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
logger.With(context...).Debugln(tt.args...)
logger.With(context...).Infoln(tt.args...)
logger.With(context...).Warnln(tt.args...)
logger.With(context...).Errorln(tt.args...)
logger.With(context...).DPanicln(tt.args...)

expected := make([]observer.LoggedEntry, 5)
for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} {
expected[i] = observer.LoggedEntry{
Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
Context: expectedFields,
}
}
assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
})
}
}

func TestSugarLnLoggingIgnored(t *testing.T) {
withSugar(t, WarnLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
logger.Infoln("hello")
assert.Zero(t, logs.Len(), "Expected zero log statements.")
})
}

func TestSugarPanicLogging(t *testing.T) {
tests := []struct {
loggerLevel zapcore.Level
Expand All @@ -276,6 +319,9 @@ func TestSugarPanicLogging(t *testing.T) {
{FatalLevel, func(s *SugaredLogger) { s.Panicw("foo") }, ""},
{PanicLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
{DebugLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
{FatalLevel, func(s *SugaredLogger) { s.Panicln("foo") }, ""},
{PanicLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"},
{DebugLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -308,6 +354,9 @@ func TestSugarFatalLogging(t *testing.T) {
{FatalLevel + 1, func(s *SugaredLogger) { s.Fatalw("foo") }, ""},
{FatalLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
{DebugLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
{FatalLevel + 1, func(s *SugaredLogger) { s.Fatalln("foo") }, ""},
{FatalLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"},
{DebugLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -385,10 +434,34 @@ func TestSugarWithOptionsIncreaseLevel(t *testing.T) {
})
}

func TestSugarLnWithOptionsIncreaseLevel(t *testing.T) {
withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
logger = logger.WithOptions(IncreaseLevel(WarnLevel))
logger.Infoln("logger.Infoln")
logger.Warnln("logger.Warnln")
logger.Errorln("logger.Errorln")
require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.")
assert.Equal(
t,
logs.AllUntimed()[0].Message,
"logger.Warnln",
"Expected first logged message to be warn level message",
)
})
}

func BenchmarkSugarSingleStrArg(b *testing.B) {
withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) {
for i := 0; i < b.N; i++ {
log.Info("hello world")
}
})
}

func BenchmarkLnSugarSingleStrArg(b *testing.B) {
withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) {
for i := 0; i < b.N; i++ {
log.Infoln("hello world")
}
})
}

0 comments on commit d38507c

Please sign in to comment.