做了这么多年项目,倾听了太多开发人员对API接口返回错误码的吐槽,收到了太多用户对系统中云里雾里错误信息的埋怨。我在这几年实践中尝试过一些的解决方案,今天就做一下总结。

错误码应考虑因素

  1. 给用户展示的信息,友好、温馨、易懂的措辞
  2. 给客户端开发者排查定位的信息,包含准确细节,如exception信息、stacktrace等,方便程序定位和处理
  3. 错误码详细信息,如:错误码url展示网页,包括错误定义、产生原因、解决办法等
  4. 给服务端开发者排查定位的信息,如错误唯一id,服务端开发人员可以根据此id定位到该次请求的所有相关log。

在具体实践中,建议使用HTTP Status为主,错误码code为辅的设计方案,以下是我曾尝试过的三种错误码规范。

自定义规范

一般会使用一个枚举类来统一定义系统中所有的错误码,然后自定义错误处理,例如github的规范

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Headers:
    Content-Type: application/json;charset=UTF-8
    X-RateLimit-Limit: 3000
    X-RateLimit-Reset: 1503162176432
    X-RateLimit-Remaining: 0
{
    "message": "Message title",
    "errors": [
        {
            "code": "rate_limit_exceeded",
            "message": "Too Many Requests. API rate limit exceeded",
            "document": "https://developer.github.com/v3/gists/"
        }
    ]
}

Spring规范

在spring中,只需要抛出异常,spring会自动转换为如下格式

1
2
3
4
5
6
7
{
    "timestamp": "2018-11-31T03:43:50.881+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "No message available",
    "path": "/v3/test/1"
}

Problem+json规范

简介

采用problem+json格式我们可以让错误输出更具有描述性,可以让API消费者更好进行错误处理,其核心是如下几个字段:

1
2
3
4
5
type: 提供一个描述问题的连接(required)
title: 对错误做一个简短的描述(required)
status: HTTP status code(required)
detail: 详细的人可理解的错误信息(optional)
instance: 返回错误产生的URL, 绝对地址(optional)

相关规范和实现:

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Response Body:
{
    "type": "https://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "instance": "/account/12345/msgs/abc",
    "balance": 30,
    "accounts": ["/account/12345",
                 "/account/67890"]
}

推荐实践

错误码最大问题不在规范,而是在执行,也就是说一开始错误码定义都是非常美好,但是经过一段时间的开发后,错误码总是在往坏的方向不断累积。所以对于一个小的团队,建议直接采用Problem/Problem+json规范,这样团队成员在执行上,不会太排斥。

由于Problem/Problem+json规范中唯一定位一个错误的是type,没有code,可以做一下小的改进或约束,比如将title当错误码code,另外对于其他字段比如时间戳、服务端错误日志定位等可以使用自定义扩展字段。