WIP: logger/unwrap: add unwrap method #143
							
								
								
									
										535
									
								
								logger/unwrap/unwrap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								logger/unwrap/unwrap.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,535 @@ | |||||||
|  | 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("<nil>") | ||||||
|  | 	circularShortBytes = []byte("<shown>") | ||||||
|  | 	invalidAngleBytes  = []byte("<invalid>") | ||||||
|  | 	openBracketBytes   = []byte("[") | ||||||
|  | 	closeBracketBytes  = []byte("]") | ||||||
|  | 	percentBytes       = []byte("%") | ||||||
|  | 	precisionBytes     = []byte(".") | ||||||
|  | 	openAngleBytes     = []byte("<") | ||||||
|  | 	closeAngleBytes    = []byte(">") | ||||||
|  | 	openMapBytes       = []byte("{") | ||||||
|  | 	closeMapBytes      = []byte("}") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type unwrap struct { | ||||||
|  | 	val            interface{} | ||||||
|  | 	s              fmt.State | ||||||
|  | 	depth          int | ||||||
|  | 	pointers       map[uintptr]int | ||||||
|  | 	opts           *Options | ||||||
|  | 	ignoreNextType bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Options struct { | ||||||
|  | 	Codec         codec.Codec | ||||||
|  | 	Indent        string | ||||||
|  | 	UnwrapMethods bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewOptions(opts ...Option) Options { | ||||||
|  | 	options := Options{ | ||||||
|  | 		Indent:        " ", | ||||||
|  | 		UnwrapMethods: false, | ||||||
|  | 	} | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&options) | ||||||
|  | 	} | ||||||
|  | 	return options | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Option func(*Options) | ||||||
|  |  | ||||||
|  | func UnwrapIndent(f string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Indent = f | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UnwrapMethods(b bool) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.UnwrapMethods = b | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UnwrapCodec(c codec.Codec) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Codec = c | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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. | ||||||
|  | 	if kind == reflect.Ptr { | ||||||
|  | 		f.formatPtr(v) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// get type information unless already handled elsewhere. | ||||||
|  | 	if !f.ignoreNextType && f.s.Flag('#') { | ||||||
|  | 		if v.Type().Kind() != reflect.Map { | ||||||
|  | 			_, _ = f.s.Write(openParenBytes) | ||||||
|  | 		} | ||||||
|  | 		_, _ = f.s.Write([]byte(v.Type().String())) | ||||||
|  | 		if v.Type().Kind() != reflect.Map { | ||||||
|  | 			_, _ = f.s.Write(closeParenBytes) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	f.ignoreNextType = false | ||||||
|  |  | ||||||
|  | 	// Call Stringer/error interfaces if they exist and the handle methods | ||||||
|  | 	// flag is enabled. | ||||||
|  | 	if !f.opts.UnwrapMethods { | ||||||
|  | 		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(openBracketBytes) | ||||||
|  | 		f.depth++ | ||||||
|  | 		numEntries := v.Len() | ||||||
|  | 		for i := 0; i < numEntries; i++ { | ||||||
|  | 			if i > 0 { | ||||||
|  | 				_, _ = f.s.Write(spaceBytes) | ||||||
|  | 			} | ||||||
|  | 			f.ignoreNextType = true | ||||||
|  | 			f.format(f.unpackValue(v.Index(i))) | ||||||
|  | 		} | ||||||
|  | 		f.depth-- | ||||||
|  | 		_, _ = f.s.Write(closeBracketBytes) | ||||||
|  | 	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() | ||||||
|  | 		_, _ = f.s.Write(openBraceBytes) | ||||||
|  | 		f.depth++ | ||||||
|  | 		vt := v.Type() | ||||||
|  | 		for i := 0; i < numFields; i++ { | ||||||
|  | 			if i > 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) | ||||||
|  | 			} | ||||||
|  | 			f.format(f.unpackValue(v.Field(i))) | ||||||
|  | 		} | ||||||
|  | 		f.depth-- | ||||||
|  | 		_, _ = 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 | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								logger/unwrap/unwrap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								logger/unwrap/unwrap_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | package unwrap | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.unistack.org/micro/v3/codec" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestUnwrap(t *testing.T) { | ||||||
|  | 	string1 := "string1" | ||||||
|  | 	string2 := "string2" | ||||||
|  |  | ||||||
|  | 	type val1 struct { | ||||||
|  | 		mp  map[string]string | ||||||
|  | 		val *val1 | ||||||
|  | 		str *string | ||||||
|  | 		ar  []*string | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v1 := &val1{ar: []*string{&string1, &string2}, str: &string1, val: &val1{str: &string2}, mp: map[string]string{"key": "val"}} | ||||||
|  |  | ||||||
|  | 	t.Logf("output: %#v", Unwrap(v1)) | ||||||
|  |  | ||||||
|  | 	type val2 struct { | ||||||
|  | 		mp  map[string]string | ||||||
|  | 		val *val2 | ||||||
|  | 		str string | ||||||
|  | 		ar  []string | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v2 := &val2{ar: []string{string1, string2}, str: string1, val: &val2{str: string2}, mp: map[string]string{"key": "val"}} | ||||||
|  |  | ||||||
|  | 	t.Logf("output: %#v", v2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUnwrapCodec(t *testing.T) { | ||||||
|  | 	type val struct { | ||||||
|  | 		MP  map[string]string `json:"mp"` | ||||||
|  | 		STR string            `json:"str"` | ||||||
|  | 		AR  []string          `json:"ar"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v1 := &val{AR: []string{"string1", "string2"}, STR: "string", MP: map[string]string{"key": "val"}} | ||||||
|  |  | ||||||
|  | 	t.Logf("output: %#v", Unwrap(v1, UnwrapCodec(codec.NewCodec()))) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user