openacid / slim

Surprisingly space efficient trie in Golang(11 bits/key; 100 ns/get).

Home Page:https://openacid.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

error struct proposal

drmingdrmer opened this issue · comments

定义一个描述错误的数据格式, 用来实现golang在运行时的错误传递, 目标:

  • 设计数据结构的前提假设:

    • 模块的用户, 90% 只判断有没有err, 不判断err是什么.
    • 每个模块定义自己本层的错误, 但提供接口允许用户在需要时拿到最底层的错误(例如errno等).
    • 避免直接向上传递错误(上层模块拿到底层错误用处不大, 例如创建block时只得到一个permission deny的errno, 无法有效的定位到到底是哪个文件/目录出现了问题).
    • error的设计目标是为了简化查错, 提供完整的日志, 方便统计分析, 出错时的整条调用链的检查.
  • 可以用于API层的错误描述, 一般API处理时收到错误并需要将错误返回给客户端, 要求错误有:

    • 具体唯一确定的error code, 便于客户端判断
    • 人类可读的error message.
    • 发生错误的相关的东西是什么.
  • Message通过Code和Resource生成出来. 只提供一个Message接口用来输出message.

  • 希望这个模块可以作为go的errors包的无修改替代品, 和 https://github.com/pkg/errors 的无修改替代品.

  • 提供一个方便的接口让用户直接取得最底层的错误.

  • 记录引发错误的底层错误是什么. 类似在存储服务中, 一个底层的IO错误导致API失败, 如果能在日志中记录引发错误的错误, 可以方便定位问题.

    有一类上层错误由几个下层错误引起, 例如多数派写中, nw = 5,3, 这时写失败3个后端会引起整个API调用失败, 这时需要记录多个下层错误.

  • error 结构里可选的带有stacktrace 信息,方便打印日志(参考了 https://github.com/pkg/errors )

type Error struct {
  Code       string  // error code
  Cause    []error   // 0 or seveal error that cause this error.
  Resource   string  // optional
  *stack             // optional traceback info of this error
}


// 实现系统error interface
func (e *Error) Error() string // 直接返回Code的string

// 兼容 https://github.com/pkg/errors 的接口:
func (e *Error) Cause() string // 返回第一个cause
// 其他接口没差别不列出了


// 扩展的接口
func (e *Error) AllCause() string // 返回所有cause的slice
func (e *Error) RootCause() string // 返回最初的cause
func (e *Error) AllRootCause() string // 返回所有的最底层的cause; cause的树的叶子节点.
func (e *Error) Message() string  // 给人看的, 通过Code和Resource拼装起来.

例子🌰: s2, group not found的一个可能的错误信息

以s2中场景为例, 描述下如何表示一个具体的错误,
假设一个错误是group没读取到.
而引发这个错误的是通过dbproxy读取group信息失败引发的.
而读dbproxy时重试了2次都失败了, 一次是mysql被置位readonly, 一次是socket读取超时:

Code: GroupNotFound
Resource: "group_id://123"
Cause: 
    - Code: DBProxyReadError
      Resource: "dbproxy://127.0.0.1:3303"
      Cause:
        - Code: MysqlReadonly
          Resource: "mysql://192.168.2.2:3306"
        - Code: InvalidResponse
          Resource: "mysql://192.168.9.9:3306"
          Cause:
              - Code: SocketReadTimeout
                Resource: "tcp4://192.168.9.9:3306"

例子🌰: ec, block build error的一个可能的错误信息

Code: BlockBuildError
Resource: "block://aabbccddeeff"
Cause:
    - Code: NeedleWriteError
      Resource: "needle://3cp/foo/bar.jpg"
      Cause:
          - Code: FSIsReadonly
            Resource: "file:///ecdrives/bbb" # schema of local file url in browser
            Cause:
                - <a native fs error> # may be an error with errno.

感觉直接定义成两个类型是不是会更通用一些?
一个是接口无关的slim的error类型;一个是aws的错误类型,包含Code,Message和Resource。

而且感觉要通用一点的话,slim自己的错误类型继承标准error会好一些,自定义的error可以作为标准error,给引用slim的其他项目使用。类似:

package main

import (
    "errors"
    "fmt"
)

type MyError struct {
    error
    ExtraInfo string
}

func (err *MyError) Error() string {
    return fmt.Sprintf("msg: %s, extra: %s", err.error, err.ExtraInfo)
}

//func ToError(err MyError) error {
//}

func New(err error, extra string) *MyError {
    stdErr := err
    switch err.(type) {
    case *MyError:
        fmt.Println("arg err is instance of MyError")
        stdErr = err.(*MyError).error
    default:
        fmt.Println("arg err is instance of standard error")
    }

    return &MyError{
        error:     stdErr,
        ExtraInfo: extra,
    }
}

func main() {
    err := errors.New("this is standard error")

    myErr := New(err, "extra error info")

    // fmt.Printf("lsl-debug: type: %T\n", err)
    // fmt.Printf("lsl-debug: type: %T\n", myErr)

    // fmt.Println(err)
    // fmt.Println(myErr)

    // MyError as standard error
    myErr2 := New(myErr, "extra error again")
    fmt.Println(myErr2)
}

输出:

 ☛  go run test.go
arg err is instance of standard error
arg err is instance of MyError
msg: this is standard error, extra: extra error again

跟树龙bb完, 做点调整, 各位在看下求完善, 修改更新到最上面的issue描述了:

  • 设计数据结构的前提假设:

    • 模块的用户, 90% 只判断有没有err, 不判断err是什么.
    • 每个模块定义自己本层的错误, 但提供接口允许用户在需要时拿到最底层的错误(例如errno等).
    • 避免直接向上传递错误(上层模块拿到底层错误用处不大, 例如创建block时只得到一个permission deny的errno, 无法有效的定位到到底是哪个文件/目录出现了问题).
    • error的设计目标是为了简化查错, 提供完整的日志, 方便统计分析, 出错时的整条调用链的检查.
  • 去掉Message属性, 必要时可以通过Code和Resource生成出来. 只提供一个Message接口用来输出message.

  • 完善下例子, 增加resource的示例, 再加1个fs相关的例子.

  • 希望这个模块可以作为go的errors包的无修改替代品, 和 https://github.com/pkg/errors 的无修改替代品.

  • 提供一个方便的接口让用户直接取得最底层的错误.

都没问题额...我关了啊...fork了 pkg/errors 到 openacid/errors. issue描述放过去了