错误码设计

对于HTTP请求难免会出现错误,对于一个发生错误的请求,我们希望能够通过错误信息快速发现错误出现的原因。

我们期望错误码有以下功能:

  1. 业务Code码标识。HTTP虽然有状态码,但是有限并且都是与HTTP Transport相关的,我们期望能够有与业务相关的Code码。
  2. 对内对外有不同的错误信息。对外要提供用户良好的错误提示还要避免暴露敏感信息,对内提供要提供便于定位错误的提示信息。

错误码设计建议

首先,无论请求成功还是失败,返回的数据格式都是固定的、规范的。

请求出错的时候,能够通过HTTP的状态码感知到错误,然后通过业务码判断是哪类错误。

请求错误返回的信息通常包括3类:业务Code码、错误信息、参考文档(可选)。错误信息是可以直接展示给用户的安全信息;还要有内部更详细的信息,以便debug,错误信息要尽量简洁。

业务Code码设计

Code码用纯数字表示,分为不同部分表示不同的服务、模块。 举个例子:对于错误码100101。分为三个部分:

  • 10:服务。
  • 01:某个服务下的某个模块。
  • 01:模块下的错误码序号,每个模块有100个错误码0~99。

也就是说,我们可以根据错误码来确定是哪一个服务下的哪一个模块发生了什么错误。

至于每个模块100个错误码序号是否够用,一般来说肯定够用,不够用的话首先应该考虑是不是模块应该再次划分。

设置HTTP状态码

对于HTTP请求,自有状态码来表示HTTP请求是否成功。通常也是通过感知HTTP是否出错之后,再根据业务Code码来判断具体的业务错误。

所以需要根据业务Code码来设置对应的HTTP状态码。虽然HTTP状态码也有很多,但是我们尽量只使用大类和常见错误来减少映射。

HTTP状态码分为五大类:

  1. 1XX - (指示信息)表示请求已经接收,继续处理
  2. 2XX - 请求成功
  3. 3XX - 请求重定向
  4. 4XX - 客户端出错
  5. 5XX - 服务端出错

基本上只使用三个大类的状态码:

  • 200:表示请求成功
  • 400:表示客户端出错
  • 500:表示服务端出错 有必要的话,可以加上常见错误:
  • 401:认证失败
  • 403:授权失败
  • 404:资源找不到

错误包设计

设计一个错误包来实现前面说讲的错误码。 首先一个优秀的错误包,应该具有以下功能:

  1. 错误堆栈。能够知道错误发生的堆栈信息,以便定位错误。
  2. 支持不同的打印格式。根据需要打印不同丰富度的错误信息。
  3. 支持Wrap/Unwrap。能够嵌套包装错误还要能够解包错误
  4. 包含IS方法。判断某个错误是否是指定的错误。
  5. 支持AS函数。将一个error转为另一个error,因为错误支持嵌套,所以不能简单地使用类型断言进行转换。
  6. 支持格式化创建和非格式化创建。

实现

基于github.com/pkg/errors包进行开发,完全兼容github.com/pkg/errors

首先,新增一个错误类型(实现了Error方法),记录了错误码、错误堆栈、cause和具体的错误信息。

type withCode struct {
    err   error // error 错误
    code  int // 业务错误码
    cause error // cause error
    *stack // 错误堆栈
}

这里错误堆栈的处理可以利用runtime获得,可以参考获取栈帧信息。 部分代码如下:

type stack []uintptr
 
func callers() *stack {
    const depth = 32
    var pcs [depth]uintptr
    n := runtime.Callers(3, pcs[:])
    var st stack = pcs[:n]
    return &st
}

withCode错误记录我们需要的错误信息,但是我们最终是需要展示给用户的,还要与HTTP状态码进行映射,于是引入一个Coder作为错误码详细信息的接口

type Coder interface {
	// HTTP状态码
	HTTPStatus() int
	// 用户查看的错误信息
	String() string
	// 返回详细文档
	Reference() string
	// 业务码
	Code() int
}

withCode实现了func (w *withCode)Format(s fmt.State, verb rune)方法来打印不同格式的错误信息。

格式占位符格式描述
%s直接可以返回给用户的错误信息
%v%s的别名
%-v打印调用栈、错误码、展示给用户的错误信息、展示给研发的错误信息(只展示错误链中最后一个错误)
%+v打印调用栈、错误码、展示给用户的错误信息、展示给研发的错误信息(展示错误链中全部错误)
%#-vJSON格式打印调用栈、错误码、展示给用户的错误信息、展示给研发的错误信息(只展示错误链中最后一个错误)
%#+vJSON格式打印调用栈、错误码、展示给用户的错误信息、展示给研发的错误信息(展示错误链中全部错误)