logger/unwrap: add unwrap method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
parent
60194fb42e
commit
ec43cfea6b
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())))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user