Merge pull request #143 from unistack-org/logger_unwrap

WIP: logger/unwrap: add unwrap method
This commit is contained in:
Василий Толстов 2022-10-11 00:33:25 +03:00 committed by GitHub
commit 416bb313fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 581 additions and 0 deletions

535
logger/unwrap/unwrap.go Normal file
View 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
}

View 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())))
}