如何在Golang中设计和处理服务错误_Golang微服务中的错误处理设计与实践

1次阅读

go微服务错误需显式分类处理:BusinessError含Code/Message/httpStatus,SystemError含TraceID/cause/操作建议;统一映射gRPC与HTTP错误;包装错误用%w但生产环境须脱敏;所有错误传播必须响应context超时与cancel。

如何在Golang中设计和处理服务错误_Golang微服务中的错误处理设计与实践

Go 语言没有异常机制,error 是一等公民,微服务中错误必须显式传递、分类、携带上下文,否则链路追踪失效、重试逻辑混乱、日志无法定位根因。

用自定义 error 类型区分业务错误与系统错误

直接返回 errors.Newfmt.Errorf 会导致下游无法判断错误性质——是该重试(如网络超时)、跳过(如用户参数非法),还是告警(如数据库连接中断)。应定义两个基础类型:

  • BusinessError:含 Code(如 "USER_NOT_FOUND")、HTTPStatus、可透传给前端Message
  • SystemError:含 TraceID、原始错误(cause)、建议操作("retry_after_1s"

示例:

type BusinessError Struct {     Code        string     Message     string     HTTPStatus  int } func (e *BusinessError) Error() string { return e.Message }

调用方用 errors.As 判断类型,而非字符串匹配。

在 gRPC 和 HTTP 层统一错误映射

同一错误在不同协议层需转为对应语义:gRPC 要设 status.Codestatus.Message,HTTP 要写入响应体和状态码。别在每个 handler 里重复写 switch

  • 定义全局映射表:map[error]struct{ code codes.Code; httpStatus int }
  • 中间件/拦截器中统一处理:grpc.UnaryServerInterceptor 捕获返回的 error,查表转成 status.Error
  • HTTP handler 中用 http.Error 或自定义响应结构,避免裸写 w.WriteHeader(500)

漏掉映射会导致 gRPC 客户端收到 Unknown 状态码,HTTP 前端看到 500 却不知是参数错还是服务崩了。

立即学习go语言免费学习笔记(深入)”;

错误链中保留原始 cause,但禁止暴露敏感信息

fmt.Errorf("failed to fetch user: %w", err) 包装错误可保留调用链,但生产环境必须过滤:os.Getenv("ENV") == "prod" 时清空 %w 的 message,只留 code 和 traceID。

  • 日志记录时用 log.Error(err, "user service failed")(假设用 zap),它会自动展开 %w
  • 序列化到响应体前,调用 err.(Interface{ Unwrap() error }).Unwrap() 获取最底层错误,检查是否含密码、Token、路径等字段
  • 所有外发错误必须经过 Sanitize() 方法,哪怕只是把 os.PathErrorPath 字段置空

Context 超时与 cancel 必须触发可终止的错误传播

微服务依赖多,一个 context.WithTimeout 被忽略,会导致 goroutine 泄漏、下游持续等待。错误必须携带 context.Canceledcontext.DeadlineExceeded 标识。

  • 调用下游前检查:if err := ctx.Err(); err != nil { return err }
  • select + channel 操作必须包含 ctx.Done() 分支,并返回 ctx.Err()
  • 数据库查询用 db.QueryContext(ctx, ...) 而非 db.Query(...);HTTP client 用 client.Do(req.WithContext(ctx))

没做这点,超时后上游已放弃,你的服务还在等 mysql 返回,错误日志里只有 "context deadline exceeded",但根本看不出卡在哪一层。

text=ZqhQzanResources