Skip to content

Commit 82a5884

Browse files
authored
Merge pull request #74 from cdr/more-assert
assert: Add Error and remove google/go-cmp
2 parents 97798d6 + f212120 commit 82a5884

File tree

9 files changed

+211
-108
lines changed

9 files changed

+211
-108
lines changed

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ require (
66
cloud.google.com/go v0.43.0
77
github.com/alecthomas/chroma v0.6.6
88
github.com/fatih/color v1.7.0
9-
github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65
109
github.com/mattn/go-colorable v0.1.2 // indirect
1110
github.com/mattn/go-isatty v0.0.9 // indirect
1211
go.opencensus.io v0.22.1

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
4848
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
4949
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
5050
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
51-
github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65 h1:B3yqxlLHBEoav+FDQM8ph7IIRA6AhQ70w119k3hoT2Y=
52-
github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
5351
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
5452
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
5553
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=

internal/assert/assert.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ import (
1010
// Equal asserts exp == act.
1111
func Equal(t testing.TB, exp, act interface{}, name string) {
1212
t.Helper()
13-
diff := CmpDiff(exp, act)
14-
if diff != "" {
15-
t.Fatalf("unexpected %v: %v", name, diff)
13+
if !reflect.DeepEqual(exp, act) {
14+
t.Fatalf("unexpected %v: exp: %q but got %q", name, exp, act)
1615
}
1716
}
1817

1918
// NotEqual asserts exp != act.
2019
func NotEqual(t testing.TB, exp, act interface{}, name string) {
2120
t.Helper()
22-
if CmpDiff(exp, act) == "" {
23-
t.Fatalf("expected different %v: %+v", name, act)
21+
if reflect.DeepEqual(exp, act) {
22+
t.Fatalf("expected different %v: %q", name, act)
2423
}
2524
}
2625

internal/assert/cmp.go

Lines changed: 0 additions & 54 deletions
This file was deleted.

internal/entryhuman/entry_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ func TestEntry(t *testing.T) {
4747
line2`)
4848
})
4949

50+
t.Run("multilineField", func(t *testing.T) {
51+
t.Parallel()
52+
53+
test(t, slog.SinkEntry{
54+
Message: "msg",
55+
Level: slog.LevelInfo,
56+
Fields: slog.M(slog.F("field", "line1\nline2")),
57+
}, `0001-01-01 00:00:00.000 [INFO] <.:0> msg ...
58+
"field": line1
59+
line2`)
60+
})
61+
5062
t.Run("named", func(t *testing.T) {
5163
t.Parallel()
5264

map.go

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,23 @@ var _ json.Marshaler = Map(nil)
2727
//
2828
// Every field value is encoded with the following process:
2929
//
30-
// 1. slog.Value is handled to allow any type to replace its representation for logging.
30+
// 1. slog.Value is checked to allow any type to replace its representation for logging.
31+
//
32+
// 2. json.Marshaller is handled.
3133
//
3234
// 2. xerrors.Formatter is handled.
3335
//
34-
// 3. Protobufs are handled with json.Marshal.
36+
// 3. Protobufs are encoded with json.Marshal.
37+
//
38+
// 4. error and fmt.Stringer are used if possible.
3539
//
36-
// 4. error and fmt.Stringer are handled.
40+
// 5. slices and arrays go through the encode function for every element.
3741
//
38-
// 5. slices and arrays are handled to go through the encode function for every value.
42+
// 6. If the value is a struct without exported fields or a type that
43+
// cannot be encoded with json.Marshal (like channels) then
44+
// fmt.Sprintf("%+v") is used.
3945
//
40-
// 6. json.Marshal is invoked as the default case.
46+
// 8. json.Marshal(v) is used for all other values.
4147
func (m Map) MarshalJSON() ([]byte, error) {
4248
b := &bytes.Buffer{}
4349
b.WriteByte('{')
@@ -56,8 +62,11 @@ func (m Map) MarshalJSON() ([]byte, error) {
5662
return b.Bytes(), nil
5763
}
5864

59-
// ForceJSON ensures the value is logged via json.Marshal even
60-
// if it implements fmt.Stringer or error.
65+
// ForceJSON ensures the value is logged via json.Marshal.
66+
//
67+
// Use it when implementing SlogValue to ensure a structured
68+
// representation of a struct if you know it's capable even
69+
// when it implements fmt.Stringer or error.
6170
func ForceJSON(v interface{}) interface{} {
6271
return jsonVal{v: v}
6372
}
@@ -66,13 +75,6 @@ type jsonVal struct {
6675
v interface{}
6776
}
6877

69-
var _ json.Marshaler = jsonVal{}
70-
71-
// MarshalJSON implements json.Marshaler.
72-
func (v jsonVal) MarshalJSON() ([]byte, error) {
73-
return json.Marshal(v.v)
74-
}
75-
7678
func marshalList(rv reflect.Value) []byte {
7779
b := &bytes.Buffer{}
7880
b.WriteByte('[')
@@ -93,6 +95,10 @@ func encode(v interface{}) []byte {
9395
switch v := v.(type) {
9496
case Value:
9597
return encode(v.SlogValue())
98+
case json.Marshaler:
99+
return encodeJSON(v)
100+
case jsonVal:
101+
return encodeJSON(v.v)
96102
case xerrors.Formatter:
97103
return encode(errorChain(v))
98104
case interface {
@@ -103,28 +109,47 @@ func encode(v interface{}) []byte {
103109
return encode(fmt.Sprint(v))
104110
default:
105111
rv := reflect.Indirect(reflect.ValueOf(v))
106-
if rv.IsValid() {
107-
switch rv.Type().Kind() {
108-
case reflect.Slice:
109-
if rv.IsNil() {
110-
break
111-
}
112-
fallthrough
113-
case reflect.Array:
112+
if !rv.IsValid() {
113+
return encodeJSON(v)
114+
}
115+
116+
switch rv.Type().Kind() {
117+
case reflect.Slice:
118+
if !rv.IsNil() {
114119
return marshalList(rv)
115120
}
116-
}
121+
case reflect.Array:
122+
return marshalList(rv)
123+
case reflect.Struct:
124+
typ := rv.Type()
125+
for i := 0; i < rv.NumField(); i++ {
126+
// Found an exported field.
127+
if typ.Field(i).PkgPath == "" {
128+
return encodeJSON(v)
129+
}
130+
}
117131

118-
b, err := json.Marshal(v)
119-
if err != nil {
120-
return encode(M(
121-
Error(xerrors.Errorf("failed to marshal to JSON: %w", err)),
122-
F("type", reflect.TypeOf(v)),
123-
F("value", fmt.Sprintf("%+v", v)),
124-
))
132+
return encodeJSON(fmt.Sprintf("%+v", v))
133+
case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func:
134+
// These types cannot be directly encoded with json.Marshal.
135+
// See https://golang.org/pkg/encoding/json/#Marshal
136+
return encodeJSON(fmt.Sprintf("%+v", v))
125137
}
126-
return b
138+
139+
return encodeJSON(v)
140+
}
141+
}
142+
143+
func encodeJSON(v interface{}) []byte {
144+
b, err := json.Marshal(v)
145+
if err != nil {
146+
return encode(M(
147+
Error(xerrors.Errorf("failed to marshal to JSON: %w", err)),
148+
F("type", reflect.TypeOf(v)),
149+
F("value", fmt.Sprintf("%+v", v)),
150+
))
127151
}
152+
return b
128153
}
129154

130155
func errorChain(f xerrors.Formatter) []interface{} {

map_test.go

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package slog_test
33
import (
44
"bytes"
55
"encoding/json"
6-
"fmt"
76
"io"
87
"runtime"
98
"strings"
109
"testing"
10+
"time"
1111

1212
"golang.org/x/xerrors"
1313

@@ -86,19 +86,19 @@ func TestMap(t *testing.T) {
8686
mapTestFile := strings.Replace(mapTestFile, "_test", "", 1)
8787

8888
test(t, slog.M(
89-
slog.F("meow", indentJSON),
89+
slog.F("meow", slog.ForceJSON(complex(10, 10))),
9090
), `{
9191
"meow": {
9292
"error": [
9393
{
9494
"msg": "failed to marshal to JSON",
95-
"fun": "cdr.dev/slog.encode",
96-
"loc": "`+mapTestFile+`:121"
95+
"fun": "cdr.dev/slog.encodeJSON",
96+
"loc": "`+mapTestFile+`:147"
9797
},
98-
"json: unsupported type: func(*testing.T, string) string"
98+
"json: unsupported type: complex128"
9999
],
100-
"type": "func(*testing.T, string) string",
101-
"value": "`+fmt.Sprint(interface{}(indentJSON))+`"
100+
"type": "complex128",
101+
"value": "(10+10i)"
102102
}
103103
}`)
104104
})
@@ -145,6 +145,24 @@ func TestMap(t *testing.T) {
145145
}`)
146146
})
147147

148+
t.Run("array", func(t *testing.T) {
149+
t.Parallel()
150+
151+
test(t, slog.M(
152+
slog.F("meow", [3]string{
153+
"1",
154+
"2",
155+
"3",
156+
}),
157+
), `{
158+
"meow": [
159+
"1",
160+
"2",
161+
"3"
162+
]
163+
}`)
164+
})
165+
148166
t.Run("forceJSON", func(t *testing.T) {
149167
t.Parallel()
150168

@@ -174,6 +192,54 @@ func TestMap(t *testing.T) {
174192
"slice": null
175193
}`)
176194
})
195+
196+
t.Run("nil", func(t *testing.T) {
197+
t.Parallel()
198+
199+
test(t, slog.M(
200+
slog.F("val", nil),
201+
), `{
202+
"val": null
203+
}`)
204+
})
205+
206+
t.Run("json.Marshaler", func(t *testing.T) {
207+
t.Parallel()
208+
209+
test(t, slog.M(
210+
slog.F("val", time.Date(2000, 02, 05, 4, 4, 4, 0, time.UTC)),
211+
), `{
212+
"val": "2000-02-05T04:04:04Z"
213+
}`)
214+
})
215+
216+
t.Run("complex", func(t *testing.T) {
217+
t.Parallel()
218+
219+
test(t, slog.M(
220+
slog.F("val", complex(10, 10)),
221+
), `{
222+
"val": "(10+10i)"
223+
}`)
224+
})
225+
226+
t.Run("privateStruct", func(t *testing.T) {
227+
t.Parallel()
228+
229+
test(t, slog.M(
230+
slog.F("val", struct {
231+
meow string
232+
bar int
233+
far uint
234+
}{
235+
meow: "hi",
236+
bar: 23,
237+
far: 600,
238+
}),
239+
), `{
240+
"val": "{meow:hi bar:23 far:600}"
241+
}`)
242+
})
177243
}
178244

179245
type meow struct {

0 commit comments

Comments
 (0)