1

写在前面的话

最近在开发Go项目,发现Go语言本身存在的error并没有像触发panic时显示详细的调试信息。对于复杂的系统而言,这会让我们开发者需要一定的时间才能定位到错误。所以我们基本Go本身的error封装了一个可以快速定位错误工具包。下面让我们来看看这个工具包是怎么实现的。

设计思想

  1. 工具包提供ErrErrf两个完善来登记错误,其用法分别类似于fmt.Printfmt.Printf的使用方式,在参数的定义中添加了innerError参数来实现错误的传递,其值可为nil.
  2. 对外提供StackTrace函数,方便使用者获取错误产生的堆栈信息。
  3. 通过SetConfig方式实现对错误信息显示的配置,如在生产环境不需要打印堆栈信息。通过响应的配置即可关闭。

实现思路

工具包中主要是对堆栈信息的处理,就是如何才能定位到error的产生位置,通过查看官方文档,我们发现通过runtime包中的CallersCallersFrames函数可以获取当前函数调用的堆栈信息。并且通过Skip参数和自定义过滤条件即可拿到error的产生位置。具体的实现过程如下:

对外提供的2个函数的实现

type Error struct {
    err     error
    msg     string
    fullMsg string
    stackTrace
}

func Errf(err error, format string, args ...interface{}) *Error {
    e := &Error{
        err: err,
        msg: fmt.Sprintf(format, args),
    }
    e.fullMsg = e.genFullMsg()
    return e
}

func Err(err error, args ...interface{}) *Error {
    e := &Error{
        err: err,
        msg: fmt.Sprint(args),
    }
    e.fullMsg = e.genFullMsg()
    return e
}

堆栈信息的实现

type stackTrace struct {
    // stack info
    data    string
    callers []uintptr
}

func (err *Error) getStackTrace() string {
    if strings.TrimSpace(err.data) == "" {
        return err.genStackTrace(5)
    }
    return err.data
}

func (err *Error) StackTrace() string {
    return err.data
}

func (err *Error) genStackTrace(skip int) string {
    if config.isPrintStack == 1 {
        var buffer bytes.Buffer
        buffer.WriteString("StackTrace:\n")
        var st [64]uintptr
        n := runtime.Callers(skip, st[:])
        err.callers = st[:n]
        frames := runtime.CallersFrames(err.callers)
        for {
            frame, ok := frames.Next()
            if !ok {
                break
            }
            if !strings.Contains(frame.File, "runtime/") {
                buffer.WriteString(fmt.Sprintf("%s\n\t%s:%d\n", 
                frame.Func.Name(), frame.File, frame.Line))
            }
        }
        err.data = buffer.String()
        return err.data
    }
    return ""
}

错误信息显示配置的实现

const (
    //print stack info
    PRINTSTACK = 1
)

// global error config object
var config *errConfig = &errConfig{}

// error config
type errConfig struct {
    isPrintStack uint32
}

//set error config info
func SetConfig(conf byte) {
    if (conf & PRINTSTACK) != 0 {
        atomic.CompareAndSwapUint32(&config.isPrintStack, 0, 1)
    }
}

测试

func init() {
    errors.SetConfig(errors.PRINTSTACK)
}

func main() {
    a := func() {
        err := errors.Err(nil, "this is an inner error")
        fmt.Print(err.StackTrace())
        b := errors.Errf(err, "this is a %s message", "test").Error()
        fmt.Println(b)
    }
    a()
}

错误信息如下所示
图片描述

结尾

该工具包只是对error信息和堆栈信息的封装,还存在含多不足的地方。如果各位有好的意见。欢迎指点。


Vitamin
16 声望0 粉丝