mirror of
https://github.com/charmbracelet/log.git
synced 2025-11-08 23:04:53 -06:00
335 lines
7.4 KiB
Go
335 lines
7.4 KiB
Go
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestJson(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
l := New(&buf)
|
|
l.SetFormatter(JSONFormatter)
|
|
cases := []struct {
|
|
name string
|
|
expected string
|
|
msg string
|
|
kvs []any
|
|
f func(msg any, kvs ...any)
|
|
}{
|
|
{
|
|
name: "default logger info with timestamp",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\"}\n",
|
|
msg: "info",
|
|
kvs: nil,
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "default logger debug with timestamp",
|
|
expected: "",
|
|
msg: "info",
|
|
kvs: nil,
|
|
f: l.Debug,
|
|
},
|
|
{
|
|
name: "default logger error with timestamp",
|
|
expected: "{\"level\":\"error\",\"msg\":\"info\"}\n",
|
|
msg: "info",
|
|
kvs: nil,
|
|
f: l.Error,
|
|
},
|
|
{
|
|
name: "multiline message",
|
|
expected: "{\"level\":\"error\",\"msg\":\"info\\ninfo\"}\n",
|
|
msg: "info\ninfo",
|
|
kvs: nil,
|
|
f: l.Error,
|
|
},
|
|
{
|
|
name: "multiline kvs",
|
|
expected: "{\"level\":\"error\",\"msg\":\"info\",\"multiline\":\"info\\ninfo\"}\n",
|
|
msg: "info",
|
|
kvs: []any{"multiline", "info\ninfo"},
|
|
f: l.Error,
|
|
},
|
|
{
|
|
name: "odd number of kvs",
|
|
expected: "{\"level\":\"error\",\"msg\":\"info\",\"foo\":\"bar\",\"baz\":\"missing value\"}\n",
|
|
msg: "info",
|
|
kvs: []any{"foo", "bar", "baz"},
|
|
f: l.Error,
|
|
},
|
|
{
|
|
name: "error field",
|
|
expected: "{\"level\":\"error\",\"msg\":\"info\",\"error\":\"error message\"}\n",
|
|
msg: "info",
|
|
kvs: []any{"error", errors.New("error message")},
|
|
f: l.Error,
|
|
},
|
|
{
|
|
name: "struct field",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"struct\":{}}\n",
|
|
msg: "info",
|
|
kvs: []any{"struct", struct{ foo string }{foo: "bar"}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "slice field",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[1,2,3]}\n",
|
|
msg: "info",
|
|
kvs: []any{"slice", []int{1, 2, 3}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "slice of structs",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
|
|
msg: "info",
|
|
kvs: []any{"slice", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "slice of strings",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[\"foo\",\"bar\"]}\n",
|
|
msg: "info",
|
|
kvs: []any{"slice", []string{"foo", "bar"}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "slice of errors",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
|
|
msg: "info",
|
|
kvs: []any{"slice", []error{errors.New("error message1"), errors.New("error message2")}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "map of strings",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"map\":{\"a\":\"b\",\"foo\":\"bar\"}}\n",
|
|
msg: "info",
|
|
kvs: []any{"map", map[string]string{"a": "b", "foo": "bar"}},
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "slog any value error type",
|
|
expected: "{\"level\":\"info\",\"msg\":\"info\",\"error\":\"error message\"}\n",
|
|
msg: "info",
|
|
kvs: []any{"error", slogAnyValue(fmt.Errorf("error message"))},
|
|
f: l.Info,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
buf.Reset()
|
|
t.Run(c.name, func(t *testing.T) {
|
|
c.f(c.msg, c.kvs...)
|
|
require.Equal(t, c.expected, buf.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJsonCaller(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
l := New(&buf)
|
|
l.SetFormatter(JSONFormatter)
|
|
l.SetReportCaller(true)
|
|
l.SetLevel(DebugLevel)
|
|
_, file, line, _ := runtime.Caller(0)
|
|
cases := []struct {
|
|
name string
|
|
expected string
|
|
msg string
|
|
kvs []any
|
|
|
|
f func(msg any, kvs ...any)
|
|
}{
|
|
{
|
|
name: "simple caller",
|
|
expected: fmt.Sprintf("{\"level\":\"info\",\"caller\":\"log/%s:%d\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
|
|
msg: "info",
|
|
kvs: nil,
|
|
f: l.Info,
|
|
},
|
|
{
|
|
name: "nested caller",
|
|
expected: fmt.Sprintf("{\"level\":\"info\",\"caller\":\"log/%s:%d\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
|
|
msg: "info",
|
|
kvs: nil,
|
|
f: func(msg any, kvs ...any) {
|
|
l.Helper()
|
|
l.Info(msg, kvs...)
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
buf.Reset()
|
|
t.Run(c.name, func(t *testing.T) {
|
|
c.f(c.msg, c.kvs...)
|
|
require.Equal(t, c.expected, buf.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJsonTime(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
logger := New(&buf)
|
|
logger.SetTimeFunction(_zeroTime)
|
|
logger.SetFormatter(JSONFormatter)
|
|
logger.SetReportTimestamp(true)
|
|
logger.Info("info")
|
|
require.Equal(t, "{\"time\":\"0002/01/01 00:00:00\",\"level\":\"info\",\"msg\":\"info\"}\n", buf.String())
|
|
}
|
|
|
|
func TestJsonPrefix(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
logger := New(&buf)
|
|
logger.SetFormatter(JSONFormatter)
|
|
logger.SetPrefix("my-prefix")
|
|
logger.Info("info")
|
|
require.Equal(t, "{\"level\":\"info\",\"prefix\":\"my-prefix\",\"msg\":\"info\"}\n", buf.String())
|
|
}
|
|
|
|
func TestJsonCustomKey(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
oldTsKey := TimestampKey
|
|
defer func() {
|
|
TimestampKey = oldTsKey
|
|
}()
|
|
TimestampKey = "other-time"
|
|
logger := New(&buf)
|
|
logger.SetTimeFunction(_zeroTime)
|
|
logger.SetFormatter(JSONFormatter)
|
|
logger.SetReportTimestamp(true)
|
|
logger.Info("info")
|
|
require.Equal(t, "{\"other-time\":\"0002/01/01 00:00:00\",\"level\":\"info\",\"msg\":\"info\"}\n", buf.String())
|
|
}
|
|
|
|
func TestJsonWriter(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
fn func(w *jsonWriter)
|
|
expected string
|
|
}{
|
|
{
|
|
"string",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", "value")
|
|
w.end()
|
|
},
|
|
`{"a":"value"}`,
|
|
},
|
|
{
|
|
"int",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", 123)
|
|
w.end()
|
|
},
|
|
`{"a":123}`,
|
|
},
|
|
{
|
|
"bytes",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("b", []byte{0x0, 0x1})
|
|
w.end()
|
|
},
|
|
`{"b":"AAE="}`,
|
|
},
|
|
{
|
|
"no fields",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.end()
|
|
},
|
|
`{}`,
|
|
},
|
|
{
|
|
"multiple in asc order",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", "value")
|
|
w.objectItem("b", "some-other")
|
|
w.end()
|
|
},
|
|
`{"a":"value","b":"some-other"}`,
|
|
},
|
|
{
|
|
"multiple in desc order",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("b", "some-other")
|
|
w.objectItem("a", "value")
|
|
w.end()
|
|
},
|
|
`{"b":"some-other","a":"value"}`,
|
|
},
|
|
{
|
|
"depth",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", map[string]int{"b": 123})
|
|
w.end()
|
|
},
|
|
`{"a":{"b":123}}`,
|
|
},
|
|
{
|
|
"key contains reserved",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a:\"b", "value")
|
|
w.end()
|
|
},
|
|
`{"a:\"b":"value"}`,
|
|
},
|
|
{
|
|
"pointer",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", ptr("pointer"))
|
|
w.end()
|
|
},
|
|
`{"a":"pointer"}`,
|
|
},
|
|
{
|
|
"double-pointer",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", ptr(ptr("pointer")))
|
|
w.end()
|
|
},
|
|
`{"a":"pointer"}`,
|
|
},
|
|
{
|
|
"invalid",
|
|
func(w *jsonWriter) {
|
|
w.start()
|
|
w.objectItem("a", invalidJSON{})
|
|
w.end()
|
|
},
|
|
`{"a":"invalid value"}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
tc.fn(&jsonWriter{w: &buf})
|
|
require.Equal(t, tc.expected, buf.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func ptr[T any](v T) *T {
|
|
return &v
|
|
}
|
|
|
|
type invalidJSON struct{}
|
|
|
|
func (invalidJSON) MarshalJSON() ([]byte, error) {
|
|
return nil, errors.New("invalid json error")
|
|
}
|