Compare commits

...

11 Commits

Author SHA1 Message Date
7c29afba0b Merge pull request #182 from unistack-org/timeDuration
config/default: handle time.Duration
2023-02-07 06:50:42 +03:00
8159b9d233 config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:48:36 +03:00
45cdac5c29 config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:48:12 +03:00
98db0dc8bc config/default: handle time.Duration
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-07 06:47:46 +03:00
453d2232bd Merge pull request #181 from unistack-org/unwrap
logger/unwrap: check nested in case of Tagged
2023-02-06 22:38:42 +03:00
9b387312da logger/unwrap: check nested in case of Tagged
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 22:36:24 +03:00
84024f7713 Merge pull request #180 from unistack-org/logger-wrapper
logger/unwrap: support sql and proto wrapper types
2023-02-06 18:55:39 +03:00
5a554f9f0c fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 18:53:27 +03:00
9c33cbc8e2 logger/unwrap: support sql and proto wrapper types
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-06 18:50:00 +03:00
848fe1c0d4 Merge pull request #179 from unistack-org/logger-unwrap
logger/unwrap: fix unwrap format
2023-02-03 23:36:47 +03:00
6cbf23fec5 logger/unwrap: fix unwrap format
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-02-03 23:33:24 +03:00
6 changed files with 300 additions and 132 deletions

View File

@@ -5,9 +5,11 @@ import (
"reflect"
"strconv"
"strings"
"time"
"github.com/imdario/mergo"
rutil "go.unistack.org/micro/v3/util/reflect"
mtime "go.unistack.org/micro/v3/util/time"
)
type defaultConfig struct {
@@ -75,6 +77,7 @@ func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) {
return nil
}
switch value.Kind() {
case reflect.Map:
t := value.Type()
@@ -151,11 +154,26 @@ func fillValue(value reflect.Value, val string) error {
}
value.Set(reflect.ValueOf(int32(v)))
case reflect.Int64:
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
switch {
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "time":
v, err := time.ParseDuration(val)
if err != nil {
return err
}
value.Set(reflect.ValueOf(v))
case value.Type().String() == "time.Duration" && value.Type().PkgPath() == "go.unistack.org/micro/v3/util/time":
v, err := mtime.ParseDuration(val)
if err != nil {
return err
}
value.SetInt(int64(v))
default:
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
value.Set(reflect.ValueOf(v))
}
value.Set(reflect.ValueOf(v))
case reflect.Uint:
v, err := strconv.ParseUint(val, 10, 0)
if err != nil {

View File

@@ -4,15 +4,19 @@ import (
"context"
"fmt"
"testing"
"time"
"go.unistack.org/micro/v3/config"
mtime "go.unistack.org/micro/v3/util/time"
)
type cfg struct {
StringValue string `default:"string_value"`
IgnoreValue string `json:"-"`
StructValue *cfgStructValue
IntValue int `default:"99"`
StringValue string `default:"string_value"`
IgnoreValue string `json:"-"`
StructValue *cfgStructValue
IntValue int `default:"99"`
DurationValue time.Duration `default:"10s"`
MDurationValue mtime.Duration `default:"10s"`
}
type cfgStructValue struct {

View File

@@ -46,13 +46,16 @@ var (
closeMapBytes = []byte("}")
)
type unwrap struct {
val interface{}
s fmt.State
pointers map[uintptr]int
opts *Options
depth int
ignoreNextType bool
type Wrapper struct {
val interface{}
s fmt.State
pointers map[uintptr]int
opts *Options
depth int
ignoreNextType bool
takeAll map[int]bool
protoWrapperType bool
sqlWrapperType bool
}
// Options struct
@@ -106,14 +109,14 @@ func Tagged(b bool) Option {
}
}
func Unwrap(val interface{}, opts ...Option) *unwrap {
func Unwrap(val interface{}, opts ...Option) *Wrapper {
options := NewOptions(opts...)
return &unwrap{val: val, opts: &options, pointers: make(map[uintptr]int)}
return &Wrapper{val: val, opts: &options, pointers: make(map[uintptr]int), takeAll: make(map[int]bool)}
}
func (f *unwrap) unpackValue(v reflect.Value) reflect.Value {
func (w *Wrapper) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
w.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
@@ -122,19 +125,19 @@ func (f *unwrap) unpackValue(v reflect.Value) reflect.Value {
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *unwrap) formatPtr(v reflect.Value) {
func (w *Wrapper) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.s.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
_, _ = f.s.Write(nilAngleBytes)
showTypes := w.s.Flag('#')
if v.IsNil() && (!showTypes || w.ignoreNextType) {
_, _ = w.s.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
for k, depth := range w.pointers {
if depth >= w.depth {
delete(w.pointers, k)
}
}
@@ -156,12 +159,12 @@ func (f *unwrap) formatPtr(v reflect.Value) {
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
if pd, ok := w.pointers[addr]; ok && pd < w.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
w.pointers[addr] = w.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
@@ -174,49 +177,49 @@ func (f *unwrap) formatPtr(v reflect.Value) {
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
if f.depth > 0 {
_, _ = f.s.Write(openParenBytes)
if showTypes && !w.ignoreNextType {
if w.depth > 0 {
_, _ = w.s.Write(openParenBytes)
}
if f.depth > 0 {
_, _ = f.s.Write(bytes.Repeat(asteriskBytes, indirects))
if w.depth > 0 {
_, _ = w.s.Write(bytes.Repeat(asteriskBytes, indirects))
} else {
_, _ = f.s.Write(bytes.Repeat(ampBytes, indirects))
_, _ = w.s.Write(bytes.Repeat(ampBytes, indirects))
}
_, _ = f.s.Write([]byte(ve.Type().String()))
if f.depth > 0 {
_, _ = f.s.Write(closeParenBytes)
_, _ = w.s.Write([]byte(ve.Type().String()))
if w.depth > 0 {
_, _ = w.s.Write(closeParenBytes)
}
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
_, _ = f.s.Write(openAngleBytes)
_, _ = f.s.Write([]byte(strings.Repeat("*", indirects)))
_, _ = f.s.Write(closeAngleBytes)
_, _ = w.s.Write(openAngleBytes)
_, _ = w.s.Write([]byte(strings.Repeat("*", indirects)))
_, _ = w.s.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.s.Flag('+') && (len(pointerChain) > 0) {
_, _ = f.s.Write(openParenBytes)
if w.s.Flag('+') && (len(pointerChain) > 0) {
_, _ = w.s.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
_, _ = f.s.Write(pointerChainBytes)
_, _ = w.s.Write(pointerChainBytes)
}
getHexPtr(f.s, addr)
getHexPtr(w.s, addr)
}
_, _ = f.s.Write(closeParenBytes)
_, _ = w.s.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
_, _ = f.s.Write(nilAngleBytes)
_, _ = w.s.Write(nilAngleBytes)
case cycleFound:
_, _ = f.s.Write(circularShortBytes)
_, _ = w.s.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
w.ignoreNextType = true
w.format(ve)
}
}
@@ -224,54 +227,72 @@ func (f *unwrap) formatPtr(v reflect.Value) {
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *unwrap) format(v reflect.Value) {
if f.opts.Codec != nil {
buf, err := f.opts.Codec.Marshal(v.Interface())
func (w *Wrapper) format(v reflect.Value) {
if w.opts.Codec != nil {
buf, err := w.opts.Codec.Marshal(v.Interface())
if err != nil {
_, _ = f.s.Write(invalidAngleBytes)
_, _ = w.s.Write(invalidAngleBytes)
return
}
_, _ = f.s.Write(buf)
_, _ = w.s.Write(buf)
return
}
if w.opts.Tagged {
w.checkTakeAll(v, 1)
}
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
_, _ = f.s.Write(invalidAngleBytes)
_, _ = w.s.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
switch kind {
case reflect.Ptr:
if !v.IsZero() {
if strings.HasPrefix(reflect.Indirect(v).Type().String(), "wrapperspb.") {
w.protoWrapperType = true
} else if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") {
w.sqlWrapperType = true
}
}
w.formatPtr(v)
return
case reflect.Struct:
if !v.IsZero() {
if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") {
w.sqlWrapperType = true
}
}
}
// get type information unless already handled elsewhere.
if !f.ignoreNextType && f.s.Flag('#') {
if !w.ignoreNextType && w.s.Flag('#') {
if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice {
_, _ = f.s.Write(openParenBytes)
_, _ = w.s.Write(openParenBytes)
}
if v.Kind() != reflect.String {
_, _ = f.s.Write([]byte(v.Type().String()))
_, _ = w.s.Write([]byte(v.Type().String()))
}
if v.Type().Kind() != reflect.Map &&
v.Type().Kind() != reflect.String &&
v.Type().Kind() != reflect.Array &&
v.Type().Kind() != reflect.Slice {
_, _ = f.s.Write(closeParenBytes)
_, _ = w.s.Write(closeParenBytes)
}
}
f.ignoreNextType = false
w.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if f.opts.Methods {
if w.opts.Methods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.opts, f.s, v); handled {
if handled := handleMethods(w.opts, w.s, v); handled {
return
}
}
@@ -279,48 +300,48 @@ func (f *unwrap) format(v reflect.Value) {
switch kind {
case reflect.Invalid:
_, _ = f.s.Write(invalidAngleBytes)
_, _ = w.s.Write(invalidAngleBytes)
case reflect.Bool:
getBool(f.s, v.Bool())
getBool(w.s, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
getInt(f.s, v.Int(), 10)
getInt(w.s, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
getUint(f.s, v.Uint(), 10)
getUint(w.s, v.Uint(), 10)
case reflect.Float32:
getFloat(f.s, v.Float(), 32)
getFloat(w.s, v.Float(), 32)
case reflect.Float64:
getFloat(f.s, v.Float(), 64)
getFloat(w.s, v.Float(), 64)
case reflect.Complex64:
getComplex(f.s, v.Complex(), 32)
getComplex(w.s, v.Complex(), 32)
case reflect.Complex128:
getComplex(f.s, v.Complex(), 64)
getComplex(w.s, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
_, _ = w.s.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
_, _ = f.s.Write(openBraceBytes)
f.depth++
_, _ = w.s.Write(openBraceBytes)
w.depth++
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
_, _ = f.s.Write(commaBytes)
_, _ = f.s.Write(spaceBytes)
_, _ = w.s.Write(commaBytes)
_, _ = w.s.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
w.ignoreNextType = true
w.format(w.unpackValue(v.Index(i)))
}
f.depth--
_, _ = f.s.Write(closeBraceBytes)
w.depth--
_, _ = w.s.Write(closeBraceBytes)
case reflect.String:
_, _ = f.s.Write([]byte(`"` + v.String() + `"`))
_, _ = w.s.Write([]byte(`"` + v.String() + `"`))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
_, _ = w.s.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
@@ -328,34 +349,45 @@ func (f *unwrap) format(v reflect.Value) {
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
_, _ = f.s.Write(nilAngleBytes)
_, _ = w.s.Write(nilAngleBytes)
break
}
_, _ = f.s.Write(openMapBytes)
f.depth++
_, _ = w.s.Write(openMapBytes)
w.depth++
keys := v.MapKeys()
for i, key := range keys {
if i > 0 {
_, _ = f.s.Write(spaceBytes)
_, _ = w.s.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
_, _ = f.s.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
w.ignoreNextType = true
w.format(w.unpackValue(key))
_, _ = w.s.Write(colonBytes)
w.ignoreNextType = true
w.format(w.unpackValue(v.MapIndex(key)))
}
f.depth--
_, _ = f.s.Write(closeMapBytes)
w.depth--
_, _ = w.s.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
numWritten := 0
_, _ = f.s.Write(openBraceBytes)
f.depth++
_, _ = w.s.Write(openBraceBytes)
w.depth++
vt := v.Type()
prevSkip := false
for i := 0; i < numFields; i++ {
if w.protoWrapperType && !vt.Field(i).IsExported() {
prevSkip = true
continue
} else if w.sqlWrapperType && vt.Field(i).Name == "Valid" {
prevSkip = true
continue
}
sv, ok := vt.Field(i).Tag.Lookup("logger")
if ok {
switch {
case ok:
switch sv {
case "omit":
prevSkip = true
@@ -363,57 +395,65 @@ func (f *unwrap) format(v reflect.Value) {
case "take":
break
}
} else if f.opts.Tagged {
case w.takeAll[w.depth]:
break
case !ok && w.opts.Tagged:
prevSkip = true
continue
}
if i > 0 && !prevSkip {
_, _ = f.s.Write(commaBytes)
_, _ = f.s.Write(spaceBytes)
}
if prevSkip {
prevSkip = false
}
vtf := vt.Field(i)
if f.s.Flag('+') || f.s.Flag('#') {
_, _ = f.s.Write([]byte(vtf.Name))
_, _ = f.s.Write(colonBytes)
if numWritten > 0 {
_, _ = w.s.Write(commaBytes)
_, _ = w.s.Write(spaceBytes)
}
f.format(f.unpackValue(v.Field(i)))
vt := vt.Field(i)
if w.s.Flag('+') || w.s.Flag('#') {
_, _ = w.s.Write([]byte(vt.Name))
_, _ = w.s.Write(colonBytes)
}
unpackValue := w.unpackValue(v.Field(i))
w.checkTakeAll(unpackValue, w.depth)
w.format(unpackValue)
numWritten++
}
f.depth--
if numWritten == 0 && f.depth < 0 {
_, _ = f.s.Write(filteredBytes)
w.depth--
if numWritten == 0 && w.depth < 0 {
_, _ = w.s.Write(filteredBytes)
}
_, _ = f.s.Write(closeBraceBytes)
_, _ = w.s.Write(closeBraceBytes)
case reflect.Uintptr:
getHexPtr(f.s, uintptr(v.Uint()))
getHexPtr(w.s, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
getHexPtr(f.s, v.Pointer())
getHexPtr(w.s, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
format := w.buildDefaultFormat()
if v.CanInterface() {
_, _ = fmt.Fprintf(f.s, format, v.Interface())
_, _ = fmt.Fprintf(w.s, format, v.Interface())
} else {
_, _ = fmt.Fprintf(f.s, format, v.String())
_, _ = fmt.Fprintf(w.s, format, v.String())
}
}
}
func (f *unwrap) Format(s fmt.State, verb rune) {
f.s = s
func (w *Wrapper) Format(s fmt.State, verb rune) {
w.s = s
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
_, _ = fmt.Fprintf(s, format, f.val)
format := w.constructOrigFormat(verb)
_, _ = fmt.Fprintf(s, format, w.val)
return
}
if f.val == nil {
if w.val == nil {
if s.Flag('#') {
_, _ = s.Write(interfaceBytes)
}
@@ -421,7 +461,7 @@ func (f *unwrap) Format(s fmt.State, verb rune) {
return
}
f.format(reflect.ValueOf(f.val))
w.format(reflect.ValueOf(w.val))
}
// handle special methods like error.Error() or fmt.Stringer interface
@@ -537,11 +577,11 @@ func catchPanic(w io.Writer, _ reflect.Value) {
}
}
func (f *unwrap) buildDefaultFormat() (format string) {
func (w *Wrapper) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf {
if f.s.Flag(int(flag)) {
if w.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag)
}
}
@@ -552,26 +592,57 @@ func (f *unwrap) buildDefaultFormat() (format string) {
return format
}
func (f *unwrap) constructOrigFormat(verb rune) (format string) {
func (w *Wrapper) constructOrigFormat(verb rune) string {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range sf {
if f.s.Flag(int(flag)) {
if w.s.Flag(int(flag)) {
_, _ = buf.WriteRune(flag)
}
}
if width, ok := f.s.Width(); ok {
if width, ok := w.s.Width(); ok {
_, _ = buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.s.Precision(); ok {
if precision, ok := w.s.Precision(); ok {
_, _ = buf.Write(precisionBytes)
_, _ = buf.WriteString(strconv.Itoa(precision))
}
_, _ = buf.WriteRune(verb)
format = buf.String()
return format
return buf.String()
}
func (w *Wrapper) checkTakeAll(v reflect.Value, depth int) {
if _, ok := w.takeAll[depth]; ok {
return
}
if !v.IsValid() || v.IsZero() {
return
}
switch v.Kind() {
case reflect.Struct:
break
case reflect.Ptr:
v = v.Elem()
if v.Kind() != reflect.Struct {
w.takeAll[depth] = true
return
}
default:
w.takeAll[depth] = true
return
}
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
sv, ok := vt.Field(i).Tag.Lookup("logger")
if ok && sv == "take" {
w.takeAll[depth] = false
}
w.checkTakeAll(v.Field(i), depth+1)
}
}

View File

@@ -86,12 +86,12 @@ func TestTaggedNested(t *testing.T) {
unk string
}
type str struct {
val *val `logger:"take"`
key string `logger:"omit"`
val *val `logger:"take"`
}
var iface interface{}
v := &str{key: "omit", val: &val{key: "test", val: "omit", unk: "unk"}}
v := &str{val: &val{key: "test", unk: "unk"}}
iface = v
buf := fmt.Sprintf("%#v", Unwrap(iface, Tagged(true)))
if strings.Compare(buf, `&unwrap.str{val:(*unwrap.val){key:"test"}}`) != 0 {

48
util/time/duration.go Normal file
View File

@@ -0,0 +1,48 @@
package time
import (
"fmt"
"time"
)
type Duration int64
func ParseDuration(s string) (time.Duration, error) {
if s == "" {
return 0, fmt.Errorf(`time: invalid duration "` + s + `"`)
}
//var sb strings.Builder
/*
for i, r := range s {
switch r {
case 'd':
n, err := strconv.Atoi(s[idx:i])
if err != nil {
return 0, errors.New("time: invalid duration " + s)
}
s[idx:i] = fmt.Sprintf("%d", n*24)
default:
sb.WriteRune(r)
}
}
*/
var td time.Duration
var err error
switch s[len(s)-1] {
case 's', 'm', 'h':
td, err = time.ParseDuration(s)
case 'd':
if td, err = time.ParseDuration(s[:len(s)-1] + "h"); err == nil {
td *= 24
}
case 'y':
if td, err = time.ParseDuration(s[:len(s)-1] + "h"); err == nil {
year := time.Date(time.Now().Year(), time.December, 31, 0, 0, 0, 0, time.Local)
days := year.YearDay()
td *= 24 * time.Duration(days)
}
}
return td, err
}

View File

@@ -0,0 +1,27 @@
package time
import (
"testing"
"time"
)
func TestParseDuration(t *testing.T) {
var td time.Duration
var err error
t.Skip()
td, err = ParseDuration("14d4h")
if err != nil {
t.Fatalf("ParseDuration error: %v", err)
}
if td.String() != "336h0m0s" {
t.Fatalf("ParseDuration 14d != 336h0m0s : %s", td.String())
}
td, err = ParseDuration("1y")
if err != nil {
t.Fatalf("ParseDuration error: %v", err)
}
if td.String() != "8760h0m0s" {
t.Fatalf("ParseDuration 1y != 8760h0m0s : %s", td.String())
}
}