diff --git a/text.go b/text.go index 8d54916..fdf5d28 100644 --- a/text.go +++ b/text.go @@ -3,6 +3,7 @@ package log import ( "fmt" "io" + "reflect" "strings" "sync" "time" @@ -149,6 +150,90 @@ var needsQuotingSet = [utf8.RuneSelf]bool{ '=': true, } +func dereferenceValue(v any) any { + if v == nil { + return v + } + currValue := reflect.ValueOf(v) + switch currValue.Kind() { + case reflect.Map: + return derefMap(currValue) + case reflect.Slice: + return derefSlice(currValue) + default: + return v + } +} + +func derefMap(rv reflect.Value) any { + if rv.Len() == 0 { + return rv.Interface() + } + + mapType := rv.Type() + valueType := mapType.Elem() + + if valueType.Kind() != reflect.Pointer { + return rv.Interface() + } + + if valueType.Elem().Kind() != reflect.Struct { + return rv.Interface() + } + + newMapType := reflect.MapOf( + mapType.Key(), + valueType.Elem()) + + newMap := reflect.MakeMap(newMapType) + + iter := rv.MapRange() + for iter.Next() { + key := iter.Key() + value := iter.Value() + + if value.IsNil() { + continue + } + + derefValue := value.Elem() + newMap.SetMapIndex(key, derefValue) + } + + return newMap.Interface() +} + +func derefSlice(rv reflect.Value) any { + if rv.Len() == 0 { + return rv.Interface() + } + + elemType := rv.Type().Elem() + if elemType.Kind() != reflect.Pointer { + return rv.Interface() + } + + if elemType.Elem().Kind() != reflect.Struct { + return rv.Interface() + } + + derefType := elemType.Elem() + newSlice := reflect.MakeSlice(reflect.SliceOf(derefType), 0, rv.Len()) + + for i := 0; i < rv.Len(); i++ { + elem := rv.Index(i) + + if elem.IsNil() { + continue + } + + derefElem := elem.Elem() + newSlice = reflect.Append(newSlice, derefElem) + } + + return newSlice.Interface() +} + func init() { for i := 0; i < utf8.RuneSelf; i++ { r := rune(i) @@ -220,7 +305,8 @@ func (l *Logger) textFormatter(keyvals ...any) { sep = st.Separator.Renderer(l.re).Render(sep) indentSep = st.Separator.Renderer(l.re).Render(indentSep) key := fmt.Sprint(keyvals[i]) - val := fmt.Sprintf("%+v", keyvals[i+1]) + derefVal := dereferenceValue(keyvals[i+1]) + val := fmt.Sprintf("%+v", derefVal) raw := val == "" if raw { val = `""` diff --git a/text_test.go b/text_test.go index b289c22..f093a5a 100644 --- a/text_test.go +++ b/text_test.go @@ -199,6 +199,41 @@ func TestTextLogger(t *testing.T) { kvs: []any{"key1", map[string]string{"foo": "bar", "baz": "qux"}}, f: logger.Error, }, + { + name: "slice of pointers to structs", + expected: "ERRO info key1=\"[{foo:bar} {foo:baz}]\"\n", + msg: "info", + kvs: []any{"key1", []*struct{ foo string }{{foo: "bar"}, {foo: "baz"}}}, + f: logger.Error, + }, + { + name: "map with pointer values to structs", + expected: "ERRO info key1=\"map[key1:{foo:bar} key2:{foo:baz}]\"\n", + msg: "info", + kvs: []any{"key1", map[string]*struct{ foo string }{"key1": {foo: "bar"}, "key2": {foo: "baz"}}}, + f: logger.Error, + }, + { + name: "slice with nil pointer", + expected: "ERRO info key1=[]\n", + msg: "info", + kvs: []any{"key1", []*struct{ foo string }{nil}}, + f: logger.Error, + }, + { + name: "empty slice of pointers", + expected: "ERRO info key1=[]\n", + msg: "info", + kvs: []any{"key1", []*struct{ foo string }{}}, + f: logger.Error, + }, + { + name: "empty map with pointer values", + expected: "ERRO info key1=map[]\n", + msg: "info", + kvs: []any{"key1", map[string]*struct{ foo string }{}}, + f: logger.Error, + }, } for _, c := range cases { buf.Reset()