improve package gerror; add support for gerror formating output in glog

This commit is contained in:
john
2019-06-30 22:21:08 +08:00
parent 10102f2db9
commit 6302789c41
9 changed files with 251 additions and 155 deletions

View File

@ -8,47 +8,10 @@
package gerror
import (
"bytes"
"fmt"
"io"
"runtime"
"strings"
"github.com/gogf/gf/g/util/gconv"
"github.com/pkg/errors"
)
// stacker is an interface for errors.StackTrace.
type stacker interface {
StackTrace() errors.StackTrace
}
// stacker is an interface for errors.Cause.
type causer interface {
Cause() error
}
// stackError is custom error for additional features.
type stackError struct {
error
*stack
}
const (
gFILTER_KEY = "/g/errors/gerror/gerror.go"
)
var (
// goRootForFilter is used for stack filtering purpose.
goRootForFilter = runtime.GOROOT()
)
func init() {
if goRootForFilter != "" {
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
}
}
// New returns an error that formats as the given value.
func New(value interface{}) error {
if value == nil {
@ -62,107 +25,47 @@ func NewText(text string) error {
if text == "" {
return nil
}
return &stackError{
err,
callers(),
return &Error{
stack: callers(),
text: text,
}
}
// Wrap wraps error with text.
// It returns nil if given err is nil.
func Wrap(err error, text string) error {
if err == nil {
return nil
}
return &stackError{errors.Wrap(err, text)}
return &Error{
error: err,
stack: callers(),
text: text,
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
// It returns nil if given err is nil.
func Wrapf(err error, format string, args ...interface{}) error {
return &stackError{errors.Wrapf(err, format, args...)}
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
return &stackError{errors.Cause(err)}
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %v, %s : Print the error string;
// %+v, %+s : Print the error stack list;
func (err *stackError) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
switch {
case s.Flag('+'):
io.WriteString(s, Stack(err.error))
default:
io.WriteString(s, err.Error())
}
if err == nil {
return nil
}
return &Error{
error: err,
stack: callers(),
text: fmt.Sprintf(format, args...),
}
}
func (err *stackError) Cause() error {
return err.error
}
// Stack returns the stack callers as string.
// It returns am empty string id the <err> does not support stacks.
// It returns an empty string id the <err> does not support stacks.
func Stack(err error) string {
if err == nil {
return ""
}
if _, ok := err.(causer); !ok {
return ""
}
index := 1
buffer := bytes.NewBuffer(nil)
for err != nil {
cause, ok := err.(causer)
if !ok {
if err, ok := err.(stacker); ok {
buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err))
index++
formatSubStack(err, buffer)
}
break
}
if err, ok := err.(stacker); ok {
buffer.WriteString(fmt.Sprintf("%d.\t%v\n", index, err))
index++
formatSubStack(err, buffer)
}
err = cause.Cause()
}
return buffer.String()
}
// formatSubStack formats the stack for error.
func formatSubStack(err stacker, buffer *bytes.Buffer) {
index := 1
for _, f := range err.StackTrace() {
if fn := runtime.FuncForPC(uintptr(f) - 1); fn != nil {
file, line := fn.FileLine(uintptr(f) - 1)
if strings.Contains(file, gFILTER_KEY) {
continue
}
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
buffer.WriteString(fmt.Sprintf("\t%d).\t%s\n\t\t%s:%d\n", index, fn.Name(), file, line))
index++
}
if e, ok := err.(*Error); !ok {
return e.Stack()
}
return ""
}

View File

@ -0,0 +1,122 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gerror
import (
"bytes"
"fmt"
"io"
"runtime"
"strings"
)
// Error is custom error for additional features.
type Error struct {
error error // Wrapped error.
stack stack // Stack array, which records the stack information when this error is created or wrapped.
text string // Error text, which is created by New* functions.
}
const (
gFILTER_KEY = "/g/errors/gerror/gerror"
)
var (
// goRootForFilter is used for stack filtering purpose.
goRootForFilter = runtime.GOROOT()
)
func init() {
if goRootForFilter != "" {
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
}
}
// Error implements the interface of Error, it returns the error as string.
func (err *Error) Error() string {
if err.text != "" {
if err.error != nil {
return err.text + ": " + err.error.Error()
}
return err.text
}
return err.error.Error()
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %v, %s : Print the error string;
// %-v, %-s : Print current error string;
// %+s : Print full stack error list;
// %+v : Print the error string and full stack error list;
func (err *Error) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
switch {
case s.Flag('-'):
if err.text != "" {
io.WriteString(s, err.text)
} else {
io.WriteString(s, err.Error())
}
case s.Flag('+'):
if verb == 's' {
io.WriteString(s, err.Stack())
} else {
io.WriteString(s, err.Error()+"\n"+err.Stack())
}
default:
io.WriteString(s, err.Error())
}
}
}
// Stack returns the stack callers as string.
// It returns an empty string id the <err> does not support stacks.
func (err *Error) Stack() string {
if err == nil {
return ""
}
loop := err
index := 1
buffer := bytes.NewBuffer(nil)
for loop != nil {
buffer.WriteString(fmt.Sprintf("%d.\t%-v\n", index, loop))
index++
formatSubStack(loop.stack, buffer)
if loop.error != nil {
if e, ok := loop.error.(*Error); ok {
loop = e
} else {
buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, loop.error.Error()))
index++
break
}
} else {
break
}
}
return buffer.String()
}
// formatSubStack formats the stack for error.
func formatSubStack(st stack, buffer *bytes.Buffer) {
index := 1
for _, p := range st {
if fn := runtime.FuncForPC(p - 1); fn != nil {
file, line := fn.FileLine(p - 1)
if strings.Contains(file, gFILTER_KEY) {
continue
}
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
buffer.WriteString(fmt.Sprintf("\t%d).\t%s\n\t\t%s:%d\n", index, fn.Name(), file, line))
index++
}
}
}

View File

@ -1,3 +1,9 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gerror
import "runtime"
@ -5,10 +11,12 @@ import "runtime"
// stack represents a stack of program counters.
type stack []uintptr
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
const (
gMAX_STACK_DEPTH = 32
)
func callers() stack {
var pcs [gMAX_STACK_DEPTH]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
return pcs[0:n]
}

View File

@ -7,6 +7,8 @@
package gerror_test
import (
"errors"
"fmt"
"testing"
"github.com/gogf/gf/g/errors/gerror"
@ -30,10 +32,76 @@ func Test_Nil(t *testing.T) {
func Test_Wrap(t *testing.T) {
gtest.Case(t, func() {
err := gerror.New("1")
err = gerror.Wrap(err, "func2 error")
err = gerror.Wrap(err, "func3 error")
err := errors.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
gtest.Assert(err.Error(), "func3 error: func2 error: 1")
gtest.Assert(err.Error(), "3: 2: 1")
})
gtest.Case(t, func() {
err := gerror.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
gtest.Assert(err.Error(), "3: 2: 1")
})
}
func Test_Format(t *testing.T) {
gtest.Case(t, func() {
err := errors.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
gtest.Assert(fmt.Sprintf("%s", err), "3: 2: 1")
gtest.Assert(fmt.Sprintf("%v", err), "3: 2: 1")
})
gtest.Case(t, func() {
err := gerror.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
gtest.Assert(fmt.Sprintf("%s", err), "3: 2: 1")
gtest.Assert(fmt.Sprintf("%v", err), "3: 2: 1")
})
gtest.Case(t, func() {
err := gerror.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
gtest.Assert(fmt.Sprintf("%-s", err), "3")
gtest.Assert(fmt.Sprintf("%-v", err), "3")
})
}
func Test_Stack(t *testing.T) {
gtest.Case(t, func() {
err := errors.New("1")
gtest.Assert(fmt.Sprintf("%+v", err), "1")
})
gtest.Case(t, func() {
err := errors.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
//fmt.Printf("%+v", err)
})
gtest.Case(t, func() {
err := gerror.New("1")
gtest.AssertNE(fmt.Sprintf("%+v", err), "1")
//fmt.Printf("%+v", err)
})
gtest.Case(t, func() {
err := gerror.New("1")
err = gerror.Wrap(err, "2")
err = gerror.Wrap(err, "3")
gtest.AssertNE(err, nil)
//fmt.Printf("%+v", err)
})
}

View File

@ -257,10 +257,15 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) {
buffer.WriteString(l.prefix + " ")
}
}
// Convert value to string.
tempStr := ""
valueStr := ""
for _, v := range value {
tempStr = gconv.String(v)
if err, ok := v.(error); ok {
tempStr = fmt.Sprintf("%+v", err)
} else {
tempStr = gconv.String(v)
}
if len(valueStr) > 0 {
if valueStr[len(valueStr)-1] == '\n' {
if tempStr[0] == '\n' {

View File

@ -1,31 +1,25 @@
package main
import (
"errors"
"fmt"
"github.com/pkg/errors"
"github.com/gogf/gf/g/errors/gerror"
)
func Test1() error {
func Error1() error {
return errors.New("test")
}
func Error2() error {
return gerror.New("test")
}
func Test2() error {
return gerror.Wrap(Test1(), "error test1")
}
type stackTracer interface {
StackTrace() errors.StackTrace
}
func main() {
err := Test2()
fmt.Printf("%s\n", err)
fmt.Printf("%v\n", err)
fmt.Printf("%+v\n", err)
fmt.Println(gerror.Stack(err))
return
err1 := Error1()
err2 := Error2()
fmt.Printf("%s, %-s, %+s\n", err1, err1, err1)
fmt.Printf("%v, %-v, %+v\n", err1, err1, err1)
fmt.Printf("%s, %-s, %+s\n", err2, err2, err2)
fmt.Printf("%v, %-v, %+v\n", err2, err2, err2)
}

View File

@ -19,8 +19,8 @@ func ReadConfig() error {
}
func main() {
//glog.Println(OpenConfig())
glog.Printf("unexpected error: %+s", ReadConfig())
//glog.Errorf("unexpected error: %+s", OpenConfig())
err := ReadConfig()
glog.Printf("%s\n%+s", err, err)
glog.Printf("%+v", err)
glog.Error(err)
}

4
go.mod
View File

@ -1,3 +1 @@
module github.com/gogf/gf
require github.com/pkg/errors v0.8.1
module github.com/gogf/gf

2
go.sum
View File

@ -1,2 +0,0 @@
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=