go中panic不recover会导致协程直接退出,http/grpc请求断连且无响应,必须在每个入口(如handler、grpc拦截器、子goroutine)显式用defer+recover捕获并记录堆栈,否则服务失联。

panic 不 recover 就真挂了
Go 里 panic 不是“报错”,是协程直接退出。HTTP handler 或 gRPC 方法里一旦触发(比如 nil 解引用、slice 越界),当前请求就断连,客户端收不到任何有效响应,日志里可能只有一行 http: panic serving 或干脆没记录。这不是服务降级,是失联。
必须在每个入口处用 defer + recover 拦住:
- HTTP 服务:注册中间件,包裹所有
http.HandlerFunc或 Gin 的*gin.Context - gRPC 服务:必须同时配置
recovery.UnaryServerInterceptor和recovery.StreamServerInterceptor,漏掉流式方法照样崩 - 子 goroutine(如异步任务、定时器回调):每个启动点都要自己包一层
recover,recover对别的 goroutine 无效
返回 error 不能直接 fmt.Errorf
业务逻辑里主动返回错误,别用 fmt.Errorf("xxx") —— 它不带状态码、不兼容 HTTP/gRPC 标准,前端或下游无法结构化解析。
应该封装成可携带元信息的类型:
- 定义
AppError结构体,含Code(业务码)、Status(HTTP 状态)、Message(用户提示) - 工厂函数统一生成:
NewBadRequest(4001, "用户名格式错误")、NewInternalError(5001, "DB 连接失败") - 用
%w包装底层 error:fmt.Errorf("failed to fetch user: %w", err),保留调用链供日志追溯
gRPC 错误透传必须解包再重封
A 服务调用 B 服务,B 返回 status.Error(codes.InvalidArgument, "..."),A 如果直接 return err 给上游,错误码会变成 codes.Unknown 或 codes.Internal —— 因为 gRPC 序列化后原始类型丢失,A 没做解包处理。
正确做法是:
- 用
status.FromError(err)解包,拿到*status.Status - 检查
st.Code(),按需透传或转换:status.Errorf(st.Code(), st.Message()) - 需要附带结构化详情(如字段校验失败),必须用
status.WithDetails()+errdetails.BadRequest等 protobuf message,不是拼字符串 - 切忌在
status.Errorf消息里塞敏感信息(token、密码),它会被打到日志和客户端
中间件里 recover 后别 log.Fatal
写 recover 中间件时,常见错误是:recover 捕获 panic 后,又在 defer 里 log.Fatal 或二次 panic,等于把拦截器自己干掉了。
真正该做的事只有三件:
- 调用
debug.Stack()获取完整堆栈,写入结构化日志(带上request_id、path、method) - 返回标准化响应:
c.JSON(http.StatusInternalServerError, ErrorResponse{...})或status.Errorf(codes.Internal, ...) - 调用
c.Abort()(Gin)或确保 response 写完不再走后续 handler
最易被忽略的是:recover 只对当前 goroutine 生效,而 HTTP handler 里 spawn 的 goroutine 如果 panic,依然会逃逸。所以凡是显式启 goroutine 的地方,都得自己加 recover —— 没捷径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











