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  }