package unwrap import ( "bytes" "fmt" "io" "reflect" "strconv" "strings" "go.unistack.org/micro/v3/codec" ) const sf = "0-+# " var hexDigits = "0123456789abcdef" var ( panicBytes = []byte("(PANIC=") plusBytes = []byte("+") iBytes = []byte("i") trueBytes = []byte("true") falseBytes = []byte("false") interfaceBytes = []byte("(interface {})") openBraceBytes = []byte("{") closeBraceBytes = []byte("}") asteriskBytes = []byte("*") ampBytes = []byte("&") colonBytes = []byte(":") openParenBytes = []byte("(") closeParenBytes = []byte(")") spaceBytes = []byte(" ") commaBytes = []byte(",") pointerChainBytes = []byte("->") nilAngleBytes = []byte("") circularShortBytes = []byte("") invalidAngleBytes = []byte("") filteredBytes = []byte("") openBracketBytes = []byte("[") closeBracketBytes = []byte("]") percentBytes = []byte("%") precisionBytes = []byte(".") openAngleBytes = []byte("<") closeAngleBytes = []byte(">") openMapBytes = []byte("{") closeMapBytes = []byte("}") ) type unwrap struct { val interface{} s fmt.State pointers map[uintptr]int opts *Options depth int ignoreNextType bool takeAll bool protoWrapperType bool sqlWrapperType bool } // Options struct type Options struct { Codec codec.Codec Indent string Methods bool Tagged bool } // NewOptions creates new Options struct via provided args func NewOptions(opts ...Option) Options { options := Options{ Indent: " ", Methods: false, } for _, o := range opts { o(&options) } return options } // Option func signature type Option func(*Options) // Indent option specify indent level func Indent(f string) Option { return func(o *Options) { o.Indent = f } } // Methods option toggles fmt.Stringer methods func Methods(b bool) Option { return func(o *Options) { o.Methods = b } } // Codec option automatic marshal arg via specified codec and write it to log func Codec(c codec.Codec) Option { return func(o *Options) { o.Codec = c } } // Tagged option toggles output only logger:"take" fields func Tagged(b bool) Option { return func(o *Options) { o.Tagged = b } } func Unwrap(val interface{}, opts ...Option) *unwrap { options := NewOptions(opts...) return &unwrap{val: val, opts: &options, pointers: make(map[uintptr]int)} } func (f *unwrap) unpackValue(v reflect.Value) reflect.Value { if v.Kind() == reflect.Interface { f.ignoreNextType = false if !v.IsNil() { v = v.Elem() } } return v } // formatPtr handles formatting of pointers by indirecting them as necessary. func (f *unwrap) 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) 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) } } // Keep list of all dereferenced pointers to possibly show later. pointerChain := make([]uintptr, 0) // Figure out how many levels of indirection there are by derferencing // pointers and unpacking interfaces down the chain while detecting circular // references. nilFound := false cycleFound := false indirects := 0 ve := v for ve.Kind() == reflect.Ptr { if ve.IsNil() { nilFound = true break } indirects++ addr := ve.Pointer() pointerChain = append(pointerChain, addr) if pd, ok := f.pointers[addr]; ok && pd < f.depth { cycleFound = true indirects-- break } f.pointers[addr] = f.depth ve = ve.Elem() if ve.Kind() == reflect.Interface { if ve.IsNil() { nilFound = true break } ve = ve.Elem() } } // Display type or indirection level depending on flags. if showTypes && !f.ignoreNextType { if f.depth > 0 { _, _ = f.s.Write(openParenBytes) } if f.depth > 0 { _, _ = f.s.Write(bytes.Repeat(asteriskBytes, indirects)) } else { _, _ = f.s.Write(bytes.Repeat(ampBytes, indirects)) } _, _ = f.s.Write([]byte(ve.Type().String())) if f.depth > 0 { _, _ = f.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) } // Display pointer information depending on flags. if f.s.Flag('+') && (len(pointerChain) > 0) { _, _ = f.s.Write(openParenBytes) for i, addr := range pointerChain { if i > 0 { _, _ = f.s.Write(pointerChainBytes) } getHexPtr(f.s, addr) } _, _ = f.s.Write(closeParenBytes) } // Display dereferenced value. switch { case nilFound: _, _ = f.s.Write(nilAngleBytes) case cycleFound: _, _ = f.s.Write(circularShortBytes) default: f.ignoreNextType = true f.format(ve) } } // format is the main workhorse for providing the Formatter interface. It // 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()) if err != nil { _, _ = f.s.Write(invalidAngleBytes) return } _, _ = f.s.Write(buf) return } // Handle invalid reflect values immediately. kind := v.Kind() if kind == reflect.Invalid { _, _ = f.s.Write(invalidAngleBytes) return } // Handle pointers specially. switch kind { case reflect.Ptr: if !v.IsZero() { if strings.HasPrefix(reflect.Indirect(v).Type().String(), "wrapperspb.") { f.protoWrapperType = true } else if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") { f.sqlWrapperType = true } } f.formatPtr(v) return case reflect.Struct: if !v.IsZero() { if strings.HasPrefix(reflect.Indirect(v).Type().String(), "sql.Null") { f.sqlWrapperType = true } } } // get type information unless already handled elsewhere. if !f.ignoreNextType && f.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) } if v.Kind() != reflect.String { _, _ = f.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) } } f.ignoreNextType = false // Call Stringer/error interfaces if they exist and the handle methods // flag is enabled. if f.opts.Methods { if (kind != reflect.Invalid) && (kind != reflect.Interface) { if handled := handleMethods(f.opts, f.s, v); handled { return } } } switch kind { case reflect.Invalid: _, _ = f.s.Write(invalidAngleBytes) case reflect.Bool: getBool(f.s, v.Bool()) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: getInt(f.s, v.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: getUint(f.s, v.Uint(), 10) case reflect.Float32: getFloat(f.s, v.Float(), 32) case reflect.Float64: getFloat(f.s, v.Float(), 64) case reflect.Complex64: getComplex(f.s, v.Complex(), 32) case reflect.Complex128: getComplex(f.s, v.Complex(), 64) case reflect.Slice: if v.IsNil() { _, _ = f.s.Write(nilAngleBytes) break } fallthrough case reflect.Array: _, _ = f.s.Write(openBraceBytes) f.depth++ numEntries := v.Len() for i := 0; i < numEntries; i++ { if i > 0 { _, _ = f.s.Write(commaBytes) _, _ = f.s.Write(spaceBytes) } f.ignoreNextType = true f.format(f.unpackValue(v.Index(i))) } f.depth-- _, _ = f.s.Write(closeBraceBytes) case reflect.String: _, _ = f.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) } case reflect.Ptr: // Do nothing. We should never get here since pointers have already // been handled above. case reflect.Map: // nil maps should be indicated as different than empty maps if v.IsNil() { _, _ = f.s.Write(nilAngleBytes) break } _, _ = f.s.Write(openMapBytes) f.depth++ keys := v.MapKeys() for i, key := range keys { if i > 0 { _, _ = f.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))) } f.depth-- _, _ = f.s.Write(closeMapBytes) case reflect.Struct: numFields := v.NumField() numWritten := 0 _, _ = f.s.Write(openBraceBytes) f.depth++ vt := v.Type() prevSkip := false for i := 0; i < numFields; i++ { f.takeAll = false if f.protoWrapperType && !vt.Field(i).IsExported() { prevSkip = true continue } else if f.sqlWrapperType && vt.Field(i).Name == "Valid" { prevSkip = true continue } sv, ok := vt.Field(i).Tag.Lookup("logger") switch { case ok: switch sv { case "omit": prevSkip = true continue case "take": break } case f.takeAll: break case !ok && f.opts.Tagged: prevSkip = true continue } if prevSkip { prevSkip = false } if numWritten > 0 { _, _ = f.s.Write(commaBytes) _, _ = f.s.Write(spaceBytes) } vtf := vt.Field(i) if f.s.Flag('+') || f.s.Flag('#') { _, _ = f.s.Write([]byte(vtf.Name)) _, _ = f.s.Write(colonBytes) } unpackValue := f.unpackValue(v.Field(i)) f.takeAll = f.checkTakeAll(unpackValue) f.format(unpackValue) numWritten++ } f.depth-- if numWritten == 0 && f.depth < 0 { _, _ = f.s.Write(filteredBytes) } _, _ = f.s.Write(closeBraceBytes) case reflect.Uintptr: getHexPtr(f.s, uintptr(v.Uint())) case reflect.UnsafePointer, reflect.Chan, reflect.Func: getHexPtr(f.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() if v.CanInterface() { _, _ = fmt.Fprintf(f.s, format, v.Interface()) } else { _, _ = fmt.Fprintf(f.s, format, v.String()) } } } func (f *unwrap) Format(s fmt.State, verb rune) { f.s = s // Use standard formatting for verbs that are not v. if verb != 'v' { format := f.constructOrigFormat(verb) _, _ = fmt.Fprintf(s, format, f.val) return } if f.val == nil { if s.Flag('#') { _, _ = s.Write(interfaceBytes) } _, _ = s.Write(nilAngleBytes) return } f.format(reflect.ValueOf(f.val)) } // handle special methods like error.Error() or fmt.Stringer interface func handleMethods(_ *Options, w io.Writer, v reflect.Value) (handled bool) { if !v.CanInterface() { // not our case return false } if !v.CanAddr() { // not our case return false } if v.CanAddr() { v = v.Addr() } // Is it an error or Stringer? switch iface := v.Interface().(type) { case error: defer catchPanic(w, v) _, _ = w.Write([]byte(iface.Error())) return true case fmt.Stringer: defer catchPanic(w, v) _, _ = w.Write([]byte(iface.String())) return true } return false } // getBool outputs a boolean value as true or false to Writer w. func getBool(w io.Writer, val bool) { if val { _, _ = w.Write(trueBytes) } else { _, _ = w.Write(falseBytes) } } // getInt outputs a signed integer value to Writer w. func getInt(w io.Writer, val int64, base int) { _, _ = w.Write([]byte(strconv.FormatInt(val, base))) } // getUint outputs an unsigned integer value to Writer w. func getUint(w io.Writer, val uint64, base int) { _, _ = w.Write([]byte(strconv.FormatUint(val, base))) } // getFloat outputs a floating point value using the specified precision, // which is expected to be 32 or 64bit, to Writer w. func getFloat(w io.Writer, val float64, precision int) { _, _ = w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) } // getComplex outputs a complex value using the specified float precision // for the real and imaginary parts to Writer w. func getComplex(w io.Writer, c complex128, floatPrecision int) { r := real(c) _, _ = w.Write(openParenBytes) _, _ = w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) i := imag(c) if i >= 0 { _, _ = w.Write(plusBytes) } _, _ = w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) _, _ = w.Write(iBytes) _, _ = w.Write(closeParenBytes) } // getHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' // prefix to Writer w. func getHexPtr(w io.Writer, p uintptr) { // Null pointer. num := uint64(p) if num == 0 { _, _ = w.Write(nilAngleBytes) return } // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix buf := make([]byte, 18) // It's simpler to construct the hex string right to left. base := uint64(16) i := len(buf) - 1 for num >= base { buf[i] = hexDigits[num%base] num /= base i-- } buf[i] = hexDigits[num] // Add '0x' prefix. i-- buf[i] = 'x' i-- buf[i] = '0' // Strip unused leading bytes. buf = buf[i:] _, _ = w.Write(buf) } func catchPanic(w io.Writer, _ reflect.Value) { if err := recover(); err != nil { _, _ = w.Write(panicBytes) _, _ = fmt.Fprintf(w, "%v", err) _, _ = w.Write(closeParenBytes) } } func (f *unwrap) buildDefaultFormat() (format string) { buf := bytes.NewBuffer(percentBytes) for _, flag := range sf { if f.s.Flag(int(flag)) { _, _ = buf.WriteRune(flag) } } _, _ = buf.WriteRune('v') format = buf.String() return format } func (f *unwrap) constructOrigFormat(verb rune) (format string) { buf := bytes.NewBuffer(percentBytes) for _, flag := range sf { if f.s.Flag(int(flag)) { _, _ = buf.WriteRune(flag) } } if width, ok := f.s.Width(); ok { _, _ = buf.WriteString(strconv.Itoa(width)) } if precision, ok := f.s.Precision(); ok { _, _ = buf.Write(precisionBytes) _, _ = buf.WriteString(strconv.Itoa(precision)) } _, _ = buf.WriteRune(verb) format = buf.String() return format } func (f *unwrap) checkTakeAll(v reflect.Value) bool { takeAll := true switch v.Kind() { case reflect.Struct: break case reflect.Pointer: v = v.Elem() if v.Kind() != reflect.Struct { return true } default: return true } vt := v.Type() for i := 0; i < v.NumField(); i++ { sv, ok := vt.Field(i).Tag.Lookup("logger") if ok && sv == "take" { return false } takeAll = f.checkTakeAll(v.Field(i)) } return takeAll }