Skip to content

Commit

Permalink
Remove Value interface and ForceJSON
Browse files Browse the repository at this point in the history
Realized we can replace ForceJSON now that we only marshal
structs as JSON if they have a json struct tag and json.Marshaller
can be used in place of Value as well so its not necessary.
  • Loading branch information
nhooyr committed Dec 16, 2019
1 parent a13a242 commit d9f1cd9
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 162 deletions.
30 changes: 0 additions & 30 deletions example_forcejson_test.go

This file was deleted.

32 changes: 0 additions & 32 deletions example_value_test.go

This file was deleted.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
cloud.google.com/go v0.43.0
github.com/alecthomas/chroma v0.6.6
github.com/fatih/color v1.7.0
github.com/google/go-cmp v0.3.2-0.20191216165815-3838af334ff4
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.2-0.20191216165815-3838af334ff4 h1:OQgDOEA9XvIORxecFMQtjbuB+C726/UOre43n3HgLsU=
github.com/google/go-cmp v0.3.2-0.20191216165815-3838af334ff4/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down
8 changes: 5 additions & 3 deletions internal/assert/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import (
"reflect"
"testing"

"github.com/kylelemons/godebug/pretty"
"github.com/google/go-cmp/cmp"
)

// Equal asserts exp == act.
func Equal(t testing.TB, name string, exp, act interface{}) {
t.Helper()
if !reflect.DeepEqual(exp, act) {
if diff := cmp.Diff(exp, act, cmp.Exporter(func(r reflect.Type) bool {
return true
})); diff != "" {
t.Fatalf(`unexpected %v: diff:
%v`, name, pretty.Compare(exp, act))
%v`, name, diff)
}
}

Expand Down
111 changes: 50 additions & 61 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import (
// Map represents an ordered map of fields.
type Map []Field

// SlogValue implements Value.
func (m Map) SlogValue() interface{} {
return ForceJSON(m)
}

var _ json.Marshaler = Map(nil)

// MarshalJSON implements json.Marshaler.
Expand All @@ -27,23 +22,20 @@ var _ json.Marshaler = Map(nil)
//
// Every field value is encoded with the following process:
//
// 1. slog.Value is checked to allow any type to replace its representation for logging.
//
// 2. json.Marshaller is handled.
// 1. json.Marshaller is handled.
//
// 2. xerrors.Formatter is handled.
//
// 3. Protobufs are encoded with json.Marshal.
// 3. structs that have a field with a json tag are encoded with json.Marshal.
//
// 4. error and fmt.Stringer are used if possible.
// 4. error and fmt.Stringer is handled.
//
// 5. slices and arrays go through the encode function for every element.
//
// 6. If the value is a struct without exported fields or a type that
// cannot be encoded with json.Marshal (like channels) then
// fmt.Sprintf("%+v") is used.
// 6. If the value cannot be encoded directly with json.Marshal (like channels)
// then fmt.Sprintf("%+v") is used.
//
// 8. json.Marshal(v) is used for all other values.
// 7. json.Marshal(v) is used for all other values.
func (m Map) MarshalJSON() ([]byte, error) {
b := &bytes.Buffer{}
b.WriteByte('{')
Expand All @@ -62,19 +54,6 @@ func (m Map) MarshalJSON() ([]byte, error) {
return b.Bytes(), nil
}

// ForceJSON ensures the value is logged via json.Marshal.
//
// Use it when implementing SlogValue to ensure a structured
// representation of a struct if you know it's capable even
// when it implements fmt.Stringer or error.
func ForceJSON(v interface{}) interface{} {
return jsonVal{v: v}
}

type jsonVal struct {
v interface{}
}

func marshalList(rv reflect.Value) []byte {
b := &bytes.Buffer{}
b.WriteByte('[')
Expand All @@ -93,51 +72,61 @@ func marshalList(rv reflect.Value) []byte {

func encode(v interface{}) []byte {
switch v := v.(type) {
case Value:
return encode(v.SlogValue())
case json.Marshaler:
return encodeJSON(v)
case jsonVal:
return encodeJSON(v.v)
case xerrors.Formatter:
return encode(errorChain(v))
case interface {
ProtoMessage()
}:
return encode(ForceJSON(v))
}

rv := reflect.Indirect(reflect.ValueOf(v))
if !rv.IsValid() {
return encodeJSON(v)
}

if rv.Kind() == reflect.Struct {
b, ok := encodeStruct(rv)
if ok {
return b
}
}

switch v.(type) {
case error, fmt.Stringer:
return encode(fmt.Sprint(v))
default:
rv := reflect.Indirect(reflect.ValueOf(v))
if !rv.IsValid() {
return encodeJSON(v)
}
}

switch rv.Type().Kind() {
case reflect.Slice:
if !rv.IsNil() {
return marshalList(rv)
}
case reflect.Array:
switch rv.Type().Kind() {
case reflect.Slice:
if !rv.IsNil() {
return marshalList(rv)
case reflect.Struct:
typ := rv.Type()
for i := 0; i < rv.NumField(); i++ {
// Found an exported field.
if typ.Field(i).PkgPath == "" {
return encodeJSON(v)
}
}

return encodeJSON(fmt.Sprintf("%+v", v))
case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func:
// These types cannot be directly encoded with json.Marshal.
// See https://golang.org/pkg/encoding/json/#Marshal
return encodeJSON(fmt.Sprintf("%+v", v))
}
case reflect.Array:
return marshalList(rv)
case reflect.Struct, reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func:
// These types cannot be directly encoded with json.Marshal.
// See https://golang.org/pkg/encoding/json/#Marshal
return encodeJSON(fmt.Sprintf("%+v", v))
}

return encodeJSON(v)
return encodeJSON(v)
}

func encodeStruct(rv reflect.Value) ([]byte, bool) {
if rv.Kind() != reflect.Struct {
return nil, false
}

if rv.Kind() == reflect.Struct {
for i := 0; i < rv.NumField(); i++ {
ft := rv.Type().Field(i)
// Found a field with a json tag.
if ft.Tag.Get("json") != "" {
return encodeJSON(rv.Interface()), true
}
}
}

return nil, false
}

func encodeJSON(v interface{}) []byte {
Expand Down
38 changes: 10 additions & 28 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,18 @@ func TestMap(t *testing.T) {
mapTestFile := strings.Replace(mapTestFile, "_test", "", 1)

test(t, slog.M(
slog.F("meow", slog.ForceJSON(complex(10, 10))),
slog.F("meow", complexJSON(complex(10, 10))),
), `{
"meow": {
"error": [
{
"msg": "failed to marshal to JSON",
"fun": "cdr.dev/slog.encodeJSON",
"loc": "`+mapTestFile+`:147"
"loc": "`+mapTestFile+`:136"
},
"json: unsupported type: complex128"
"json: error calling MarshalJSON for type slog_test.complexJSON: json: unsupported type: complex128"
],
"type": "complex128",
"type": "slog_test.complexJSON",
"value": "(10+10i)"
}
}`)
Expand Down Expand Up @@ -163,26 +163,6 @@ func TestMap(t *testing.T) {
}`)
})

t.Run("forceJSON", func(t *testing.T) {
t.Parallel()

test(t, slog.M(
slog.F("error", slog.ForceJSON(io.EOF)),
), `{
"error": {}
}`)
})

t.Run("value", func(t *testing.T) {
t.Parallel()

test(t, slog.M(
slog.F("error", meow{1}),
), `{
"error": "xdxd"
}`)
})

t.Run("nilSlice", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -246,10 +226,6 @@ type meow struct {
a int
}

func (m meow) SlogValue() interface{} {
return "xdxd"
}

func indentJSON(t *testing.T, j string) string {
b := &bytes.Buffer{}
err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4))
Expand All @@ -263,3 +239,9 @@ func marshalJSON(t *testing.T, m slog.Map) string {
assert.Success(t, "marshal map to JSON", err)
return indentJSON(t, string(actb))
}

type complexJSON complex128

func (c complexJSON) MarshalJSON() ([]byte, error) {
return json.Marshal(complex128(c))
}
8 changes: 0 additions & 8 deletions slog.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,6 @@ func M(fs ...Field) Map {
return fs
}

// Value represents a log value.
//
// Implement SlogValue in your own types to override
// the value encoded when logging.
type Value interface {
SlogValue() interface{}
}

// Error is the standard key used for logging a Go error value.
func Error(err error) Field {
return F("error", err)
Expand Down

0 comments on commit d9f1cd9

Please sign in to comment.