Skip to content

Commit

Permalink
fix: more tests, improve codecov, broken caller
Browse files Browse the repository at this point in the history
  • Loading branch information
rhnvrm committed Jul 7, 2022
1 parent de8a6c9 commit 41c09e0
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 46 deletions.
28 changes: 0 additions & 28 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,6 @@ func (bb *byteBuffer) AppendFloat(f float64, bitSize int) {
bb.B = strconv.AppendFloat(bb.B, f, 'f', -1, bitSize)
}

// Len returns the length of the underlying buffer.
func (bb *byteBuffer) Len() int {
return len(bb.B)
}

// Cap returns the capacity of the underlying buffer.
func (bb *byteBuffer) Cap() int {
return cap(bb.B)
}

// Bytes returns a mutable reference to the underlying buffer.
func (bb *byteBuffer) Bytes() []byte {
return bb.B
Expand All @@ -81,21 +71,3 @@ func (bb *byteBuffer) Bytes() []byte {
func (bb *byteBuffer) Reset() {
bb.B = bb.B[:0]
}

// Write implements io.Writer.
func (bb *byteBuffer) Write(bs []byte) (int, error) {
bb.B = append(bb.B, bs...)
return len(bs), nil
}

// WriteByte writes a single byte to the buffer
func (bb *byteBuffer) WriteByte(v byte) error {
bb.B = append(bb.B, v)
return nil
}

// WriteString writes a string to the buffer.
func (bb *byteBuffer) WriteString(s string) (int, error) {
bb.B = append(bb.B, s...)
return len(s), nil
}
18 changes: 15 additions & 3 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
var (
hex = "0123456789abcdef"
bufPool byteBufferPool
exit = func() { os.Exit(1) }
)

type Opts struct {
Expand Down Expand Up @@ -82,6 +83,9 @@ func New(opts Opts) Logger {
if opts.Level == 0 {
opts.Level = InfoLevel
}
if opts.CallerSkipFrameCount == 0 {
opts.CallerSkipFrameCount = 3
}

return Logger{
out: newSyncWriter(opts.Writer),
Expand Down Expand Up @@ -156,7 +160,7 @@ func (l Logger) Error(msg string, fields ...any) {
// It aborts the current program with an exit code of 1.
func (l Logger) Fatal(msg string, fields ...any) {
l.handleLog(msg, FatalLevel, fields...)
os.Exit(1)
exit()
}

// handleLog emits the log after filtering log level
Expand Down Expand Up @@ -295,10 +299,16 @@ func writeToBuf(buf *byteBuffer, key string, val any, lvl Level, color, space bo
buf.AppendByte('=')

switch v := val.(type) {
case nil:
buf.AppendString("null")
case []byte:
escapeAndWriteString(buf, string(v))
case string:
escapeAndWriteString(buf, v)
case int:
buf.AppendInt(int64(v))
case int8:
buf.AppendInt(int64(v))
case int16:
buf.AppendInt(int64(v))
case int32:
Expand All @@ -309,6 +319,8 @@ func writeToBuf(buf *byteBuffer, key string, val any, lvl Level, color, space bo
buf.AppendFloat(float64(v), 32)
case float64:
buf.AppendFloat(float64(v), 64)
case bool:
buf.AppendBool(v)
case error:
escapeAndWriteString(buf, v.Error())
case fmt.Stringer:
Expand All @@ -325,7 +337,7 @@ func writeToBuf(buf *byteBuffer, key string, val any, lvl Level, color, space bo
// escapeAndWriteString escapes the string if any unwanted chars are there.
func escapeAndWriteString(buf *byteBuffer, s string) {
idx := strings.IndexFunc(s, checkEscapingRune)
if idx != -1 {
if idx != -1 || s == "null" {
writeQuotedString(buf, s)
return
}
Expand All @@ -339,7 +351,7 @@ func getColoredKey(k string, lvl Level) string {

// checkEscapingRune returns true if the rune is to be escaped.
func checkEscapingRune(r rune) bool {
return r == '=' || r == ' ' || r == '"' || r == utf8.RuneError
return r == ' ' || r == '=' || r == '"' || r == utf8.RuneError || r >= utf8.RuneSelf
}

// writeQuotedString quotes a string before writing to the buffer.
Expand Down
164 changes: 149 additions & 15 deletions log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,29 @@ package logf
import (
"bytes"
"errors"
"log"
"strconv"
"sync"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLogFormatWithEnableCaller(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf, EnableCaller: true})

l.Info("hello world")
require.Contains(t, buf.String(), `level=info message="hello world" caller=`)
require.Contains(t, buf.String(), `logf/log_test.go:18`)
buf.Reset()

lC := New(Opts{Writer: buf, EnableCaller: true, EnableColor: true})
lC.Info("hello world")
require.Contains(t, buf.String(), `logf/log_test.go:24`)
buf.Reset()
}

func TestLevelParsing(t *testing.T) {
cases := []struct {
String string
Expand All @@ -25,67 +41,185 @@ func TestLevelParsing(t *testing.T) {

for _, c := range cases {
t.Run(c.String, func(t *testing.T) {
assert.Equal(t, c.Lvl.String(), c.String, "level should be equal")
require.Equal(t, c.Lvl.String(), c.String, "level should be equal")
})
}

// Check for an invalid case.
t.Run("invalid", func(t *testing.T) {
var invalidLvl Level = 10
assert.Equal(t, invalidLvl.String(), "invalid lvl", "invalid level")
require.Equal(t, invalidLvl.String(), "invalid lvl", "invalid level")
})
}

func TestNewLoggerDefault(t *testing.T) {
l := New(Opts{})
assert.Equal(t, l.Opts.Level, InfoLevel, "level is info")
assert.Equal(t, l.Opts.EnableColor, false, "color output is disabled")
assert.Equal(t, l.Opts.EnableCaller, false, "caller is disabled")
assert.Equal(t, l.Opts.CallerSkipFrameCount, 0, "skip frame count is 0")
assert.Equal(t, l.Opts.TimestampFormat, defaultTSFormat, "timestamp format is default")
require.Equal(t, l.Opts.Level, InfoLevel, "level is info")
require.Equal(t, l.Opts.EnableColor, false, "color output is disabled")
require.Equal(t, l.Opts.EnableCaller, false, "caller is disabled")
require.Equal(t, l.Opts.CallerSkipFrameCount, 3, "skip frame count is 3")
require.Equal(t, l.Opts.TimestampFormat, defaultTSFormat, "timestamp format is default")
}

func TestNewSyncWriterWithNil(t *testing.T) {
w := newSyncWriter(nil)
require.NotNil(t, w.w, "writer should not be nil")
}

func TestLogFormat(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf})

l := New(Opts{Writer: buf, Level: DebugLevel})
// Debug log.
l.Debug("debug log")
require.Contains(t, buf.String(), `level=debug message="debug log"`)
buf.Reset()

l = New(Opts{Writer: buf})

// Debug log but with defualt level set to info.
l.Debug("debug log")
require.NotContains(t, buf.String(), `level=debug message="debug log"`)
buf.Reset()

// Info log.
l.Info("hello world")
assert.Contains(t, buf.String(), `level=info message="hello world"`, "info log")
require.Contains(t, buf.String(), `level=info message="hello world"`, "info log")
buf.Reset()

// Log with field.
l.Warn("testing fields", "stack", "testing")
assert.Contains(t, buf.String(), `level=warn message="testing fields" stack=testing`, "warning log")
require.Contains(t, buf.String(), `level=warn message="testing fields" stack=testing`, "warning log")
buf.Reset()

// Log with error.
fakeErr := errors.New("this is a fake error")
l.Error("testing error", "error", fakeErr)
assert.Contains(t, buf.String(), `level=error message="testing error" error="this is a fake error"`, "error log")
require.Contains(t, buf.String(), `level=error message="testing error" error="this is a fake error"`, "error log")
buf.Reset()

// Fatal log
var hadExit = false
exit = func() {
hadExit = true
}

l.Fatal("fatal log")
require.True(t, hadExit, "exit should have been called")
require.Contains(t, buf.String(), `level=fatal message="fatal log"`, "fatal log")
buf.Reset()
}

func TestLogFormatWithColor(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf, EnableColor: true})

// Info log.
l.Info("hello world")
require.Contains(t, buf.String(), "\x1b[36mlevel\x1b[0m=info \x1b[36mmessage\x1b[0m=\"hello world\" \n")
buf.Reset()
}

func TestLoggerTypes(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf, Level: DebugLevel})
type foo struct {
A int
}
l.Info("hello world",
"string", "foo",
"int", 1,
"int8", int8(1),
"int16", int16(1),
"int32", int32(1),
"int64", int64(1),
"float32", float32(1.0),
"float64", float64(1.0),
"struct", foo{A: 1},
"bool", true,
)

require.Contains(t, buf.String(), "level=info message=\"hello world\" string=foo int=1 int8=1 int16=1 int32=1 int64=1 float32=1 float64=1 struct={1} bool=true \n")
}

func TestLogFormatWithDefaultFields(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf, DefaultFields: []any{"defaultkey", "defaultvalue"}})

l.Info("hello world")
assert.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue`)
require.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue`)
buf.Reset()

l.Info("hello world", "component", "logf")
assert.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue component=logf`)
require.Contains(t, buf.String(), `level=info message="hello world" defaultkey=defaultvalue component=logf`)
buf.Reset()
}

type errWriter struct{}

func (w *errWriter) Write(p []byte) (int, error) {
return 0, errors.New("dummy error")
}

func TestIoWriterError(t *testing.T) {
w := &errWriter{}
l := New(Opts{Writer: w})
buf := &bytes.Buffer{}
log.SetOutput(buf)
log.SetFlags(0)
l.Info("hello world")
require.Contains(t, buf.String(), "error logging: dummy error\n")
}

func TestWriteQuotedStringCases(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf})

// cases from
// https://github.com/go-logfmt/logfmt/blob/99455b83edb21b32a1f1c0a32f5001b77487b721/encode_test.go
data := []struct {
key, value interface{}
want string
}{
{key: "k", value: "v", want: "k=v"},
{key: "k", value: nil, want: "k=null"},
{key: `\`, value: "v", want: `\=v`},
{key: "k", value: "", want: "k="},
{key: "k", value: "null", want: `k="null"`},
{key: "k", value: "<nil>", want: `k=<nil>`},
{key: "k", value: true, want: "k=true"},
{key: "k", value: 1, want: "k=1"},
{key: "k", value: 1.025, want: "k=1.025"},
{key: "k", value: 1e-3, want: "k=0.001"},
{key: "k", value: 3.5 + 2i, want: "k=(3.5+2i)"},
{key: "k", value: "v v", want: `k="v v"`},
{key: "k", value: " ", want: `k=" "`},
{key: "k", value: `"`, want: `k="\""`},
{key: "k", value: `=`, want: `k="="`},
{key: "k", value: `\`, want: `k=\`},
{key: "k", value: `=\`, want: `k="=\\"`},
{key: "k", value: `\"`, want: `k="\\\""`},
{key: "k", value: "\xbd", want: `k="\ufffd"`},
{key: "k", value: "\ufffd\x00", want: `k="\ufffd\u0000"`},
{key: "k", value: "\ufffd", want: `k="\ufffd"`},
{key: "k", value: []byte("\ufffd\x00"), want: `k="\ufffd\u0000"`},
{key: "k", value: []byte("\ufffd"), want: `k="\ufffd"`},
}

for _, d := range data {
l.Info("hello world", d.key, d.value)
require.Contains(t, buf.String(), d.want)
buf.Reset()
}
}

func TestOddNumberedFields(t *testing.T) {
buf := &bytes.Buffer{}
l := New(Opts{Writer: buf})

// Give a odd number of fields.
l.Info("hello world", "key1", "val1", "key2")
assert.Contains(t, buf.String(), `level=info message="hello world" key1=val1`)
require.Contains(t, buf.String(), `level=info message="hello world" key1=val1`)
buf.Reset()
}

Expand Down

0 comments on commit 41c09e0

Please sign in to comment.