github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/logger/struct.go (about) 1 package logger 2 3 import ( 4 "fmt" 5 "go/token" 6 "reflect" 7 "time" 8 ) 9 10 //nolint:gochecknoglobals 11 var fmtStringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() 12 13 // structToFields maps fields of a passed structure to Fields. It returns empty fields 14 // for a non-structure arguments. 15 func structToFields(i interface{}, namePrefix string) (fields Fields) { 16 defer func() { 17 if r := recover(); r != nil { 18 fields = Fields{"struct_to_fields_panic": r, "type": fmt.Sprintf("%T", i)} 19 } 20 }() 21 22 v := deref(reflect.ValueOf(i)) 23 if v.Kind() != reflect.Struct { 24 return nil 25 } 26 t := v.Type() 27 28 fieldCount := v.NumField() 29 fields = make(Fields, fieldCount) 30 for i := 0; i < fieldCount; i++ { 31 name, skip := getName(t.Field(i), namePrefix) 32 if skip { 33 continue 34 } 35 36 var value interface{} 37 38 f := v.Field(i) 39 40 // Check whether the field implements 'fmt.Stringer' before 'deref' call 41 // because 'String' method can be declared with a pointer receiver. 42 if f.Type().Implements(fmtStringer) && (f.Kind() != reflect.Ptr || !f.IsNil()) { 43 value = f.Interface().(fmt.Stringer).String() 44 } 45 46 f = deref(f) 47 switch f := f.Interface().(type) { //nolint:gocritic 48 case time.Time: 49 value = f.Format(time.RFC3339) 50 } 51 52 if value != nil { 53 fields[name] = value 54 continue 55 } 56 57 // Fallback to basic types 58 switch f.Kind() { 59 case 60 reflect.Bool, 61 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 62 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 63 reflect.Float32, reflect.Float64, 64 reflect.Complex64, reflect.Complex128, 65 reflect.String, 66 reflect.Array: 67 68 fields[name] = f.Interface() 69 70 case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface: 71 var value interface{} = "<nil>" 72 if !f.IsNil() { 73 value = f.Interface() 74 } 75 fields[name] = value 76 77 case reflect.Struct: 78 for k, v := range structToFields(f.Interface(), "") { 79 fields[name+"."+k] = v 80 } 81 } 82 } 83 return fields 84 } 85 86 func getName(f reflect.StructField, namePrefix string) (name string, skip bool) { 87 if !token.IsExported(f.Name) { 88 return "", true 89 } 90 name = f.Name 91 if v := f.Tag.Get("json"); v != "" { 92 name = v 93 } 94 if namePrefix != "" { 95 name = namePrefix + "." + name 96 } 97 return name, false 98 } 99 100 // deref returns a pointer's value, traversing as many levels as needed 101 // until Value's kind is not reflect.Ptr or Value.IsNil returns true. 102 func deref(v reflect.Value) reflect.Value { 103 for v.Kind() == reflect.Ptr && !v.IsNil() { 104 v = v.Elem() 105 } 106 return v 107 }