错误处理

APIs 响应错误时可以直接使用 errors 包中的 New 方法来声明一个 error,也可以直接通过 proto 预定义定义错误码,然后通过 proto-gen-go 生成帮助代码,直接返回 error。

在errors包中,错误模型主要跟 gRPC 状态码一致,并且 Error 实现了 GRPCStatus() 接口, 实现了 grpc 和 http 错误码的转换, 业务原因通过 ErrorInfo 返回:

  1. {
  2. // 错误码,跟 http-status 一致,并且在 grpc 中可以转换成 grpc-status
  3. "code": 500,
  4. // 错误原因,定义为业务判定错误码
  5. "reason": "USER_NOT_FOUND",
  6. // 错误信息,为用户可读的信息,可作为用户提示内容
  7. "message": "invalid argument error",
  8. // 错误元信息,为错误添加附加可扩展信息
  9. "metadata": {
  10. "foo": "bar"
  11. }
  12. }

安装工具

  1. # 如果电脑中没有protoc-gen-go需要先安装
  2. # go install google.golang.org/protobuf/cmd/protoc-gen-go
  3. go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2

错误定义

api/helloworld/v1/helloworld.proto

  1. syntax = "proto3";
  2. // 定义包名
  3. package api.kratos.v1;
  4. import "errors/errors.proto";
  5. // 多语言特定包名,用于源代码引用
  6. option go_package = "kratos/api/helloworld;helloworld";
  7. option java_multiple_files = true;
  8. option java_package = "api.helloworld";
  9. enum ErrorReason {
  10. // 设置缺省错误码
  11. option (errors.default_code) = 500;
  12. // 为某个枚举单独设置错误码
  13. USER_NOT_FOUND = 0 [(errors.code) = 404];
  14. CONTENT_MISSING = 1 [(errors.code) = 400];
  15. }

注意事项:

  • 当枚举组没有配置缺省错误码时, 当前枚举组的没有配置错误码的枚举值会被忽略
  • 当整个枚举组都没配置错误码时,当前枚举组会被忽略
  • 错误码的取值范围应该在 0 < code <= 600 之间, 超出范围将抛出异常

错误生成

通过 proto 生成对应的代码:

  1. protoc --proto_path=. \
  2. --proto_path=./third_party \
  3. --go_out=paths=source_relative:. \
  4. --go-errors_out=paths=source_relative:. \
  5. $(API_PROTO_FILES)

或者在项目根目录使用Makefile指令

  1. make errors

执行成功之后,会在 api/helloworld 目录下生成 helloworld_errors.pb.go 文件,代码如下:

  1. package helloworld
  2. import (
  3. fmt "fmt"
  4. errors "github.com/go-kratos/kratos/v2/errors"
  5. )
  6. // This is a compile-time assertion to ensure that this generated file
  7. // is compatible with the kratos package it is being compiled against.
  8. const _ = errors.SupportPackageIsVersion1
  9. func IsUserNotFound(err error) bool {
  10. if err == nil {
  11. return false
  12. }
  13. e := errors.FromError(err)
  14. return e.Reason == ErrorReason_USER_NOT_FOUND.String() && e.Code == 404
  15. }
  16. func ErrorUserNotFound(format string, args ...interface{}) *errors.Error {
  17. return errors.New(404, ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...))
  18. }
  19. func IsContentMissing(err error) bool {
  20. if err == nil {
  21. return false
  22. }
  23. e := errors.FromError(err)
  24. return e.Reason == ErrorReason_CONTENT_MISSING.String() && e.Code == 400
  25. }
  26. func ErrorContentMissing(format string, args ...interface{}) *errors.Error {
  27. return errors.New(400, ErrorReason_CONTENT_MISSING.String(), fmt.Sprintf(format, args...))
  28. }

使用方式

响应错误

当业务逻辑中需要响应错误时,可以通过使用 kratos errors 包中的 New 方法来响应错误, 或者可以通过proto定义,然后通过 protoc-gen-go-error 工具生成帮助代码来响应错误

  1. // 通过 errors.New() 响应错误
  2. errors.New(500, "USER_NAME_EMPTY", "user name is empty")
  3. // 通过 proto 生成的代码响应错误,并且包名应替换为自己生成代码后的 package name
  4. api.ErrorUserNotFound("user %s not found", "kratos")
  5. // 传递metadata
  6. err := errors.New(500, "USER_NAME_EMPTY", "user name is empty")
  7. err = err.WithMetadata(map[string]string{
  8. "foo": "bar",
  9. })

错误断言

  1. // 引入 helloworld 包
  2. import "kratos/api/helloworld"
  3. err := wrong()
  4. // 通过 errors.Is() 断言
  5. if errors.Is(err,errors.BadRequest("USER_NAME_EMPTY","")) {
  6. // do something
  7. }
  8. // 通过判断 *Error.Reason 和 *Error.Code
  9. e := errors.FromError(err)
  10. if e.Reason == "USER_NAME_EMPTY" && e.Code == 500 {
  11. // do something
  12. }
  13. // 通过 proto 生成的代码断言错误,并且包名应替换为自己生成代码后的 package name(此处对应上面生成的 helloworld 包,调用定义的方法)
  14. if helloworld.IsUserNotFound(err) {
  15. // do something
  16. })