diff --git a/.gitignore b/.gitignore index 25cfa0e..f5894b3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ examples/new/new examples/options/options examples/oven/oven examples/temperature/temperature + +.vscode +.history +go.work diff --git a/logger.go b/logger.go index 6433251..f044ecf 100644 --- a/logger.go +++ b/logger.go @@ -79,8 +79,9 @@ func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) { } if msg != nil { - m := fmt.Sprint(msg) - kvs = append(kvs, MessageKey, m) + if m := fmt.Sprint(msg); m != "" { + kvs = append(kvs, MessageKey, m) + } } // append logger fields @@ -88,6 +89,7 @@ func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) { if len(l.fields)%2 != 0 { kvs = append(kvs, ErrMissingValue) } + // append the rest kvs = append(kvs, keyvals...) if len(keyvals)%2 != 0 { diff --git a/logger_test.go b/logger_test.go index 181278f..7be9d5d 100644 --- a/logger_test.go +++ b/logger_test.go @@ -129,6 +129,47 @@ func TestLogFormatter(t *testing.T) { } } +func TestEmptyMessage(t *testing.T) { + var buf bytes.Buffer + l := New(&buf) + cases := []struct { + name string + expected string + msg string + fields []interface{} + kvs []interface{} + }{ + { + name: "empty message nil fields", + expected: "INFO\n", + msg: "", + fields: nil, + kvs: nil, + }, + { + name: "empty message with fields", + expected: "INFO foo=bar\n", + msg: "", + fields: []interface{}{"foo", "bar"}, + kvs: nil, + }, + { + name: "empty message with fields & kvs", + expected: "INFO foo=bar foobar=baz\n", + msg: "", + fields: []interface{}{"foo", "bar"}, + kvs: []interface{}{"foobar", "baz"}, + }, + } + for _, c := range cases { + buf.Reset() + t.Run(c.name, func(t *testing.T) { + l.With(c.fields...).Info(c.msg, c.kvs...) + assert.Equal(t, c.expected, buf.String()) + }) + } +} + func TestLogWithPrefix(t *testing.T) { var buf bytes.Buffer cases := []struct { diff --git a/text.go b/text.go index 301ebd0..8cdbb73 100644 --- a/text.go +++ b/text.go @@ -143,38 +143,54 @@ func needsQuoting(str string) bool { } func (l *Logger) textFormatter(keyvals ...interface{}) { - for i := 0; i < len(keyvals); i += 2 { + lenKeyvals := len(keyvals) + + for i := 0; i < lenKeyvals; i += 2 { + firstKey := i == 0 + moreKeys := i < lenKeyvals-2 + switch keyvals[i] { case TimestampKey: if t, ok := keyvals[i+1].(time.Time); ok { ts := t.Format(l.timeFormat) ts = TimestampStyle.Renderer(l.re).Render(ts) + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(ts) - l.b.WriteByte(' ') } case LevelKey: if level, ok := keyvals[i+1].(Level); ok { lvl := levelStyle(level).Renderer(l.re).String() + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(lvl) - l.b.WriteByte(' ') } case CallerKey: if caller, ok := keyvals[i+1].(string); ok { caller = fmt.Sprintf("<%s>", caller) caller = CallerStyle.Renderer(l.re).Render(caller) + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(caller) - l.b.WriteByte(' ') } case PrefixKey: if prefix, ok := keyvals[i+1].(string); ok { prefix = PrefixStyle.Renderer(l.re).Render(prefix + ":") + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(prefix) - l.b.WriteByte(' ') } case MessageKey: if msg := keyvals[i+1]; msg != nil { m := fmt.Sprint(msg) m = MessageStyle.Renderer(l.re).Render(m) + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(m) } default: @@ -182,7 +198,6 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { indentSep := indentSeparator sep = SeparatorStyle.Renderer(l.re).Render(sep) indentSep = SeparatorStyle.Renderer(l.re).Render(indentSep) - moreKeys := i < len(keyvals)-2 key := fmt.Sprint(keyvals[i]) val := fmt.Sprintf("%+v", keyvals[i+1]) raw := val == "" @@ -215,19 +230,19 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { l.b.WriteString(key) l.b.WriteString(sep + "\n") l.writeIndent(&l.b, val, indentSep, moreKeys, actualKey) - // If there are more keyvals, separate them with a space. - if moreKeys { + } else if !raw && needsQuoting(val) { + if !firstKey { l.b.WriteByte(' ') } - } else if !raw && needsQuoting(val) { - l.b.WriteByte(' ') l.b.WriteString(key) l.b.WriteString(sep) l.b.WriteString(valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`, escapeStringForOutput(val, true)))) } else { val = valueStyle.Renderer(l.re).Render(val) - l.b.WriteByte(' ') + if !firstKey { + l.b.WriteByte(' ') + } l.b.WriteString(key) l.b.WriteString(sep) l.b.WriteString(val)