Go语言中发生panic时如何捕获并记录gRPC请求信息
解决gRPC双向流服务Panic时记录请求内容的问题
要实现panic时记录请求(尤其是Messages内容),需要基于go-grpc-middleware实现流式服务拦截器,同时自定义流包装器捕获并保存收到的请求数据,最后在panic恢复时提取这些数据日志。
步骤1:自定义请求捕获的流包装器
重写ServerStream的RecvMsg方法,每次收到客户端请求时保存一份副本,避免后续修改影响日志记录:
import ( "log" "runtime/debug" "google.golang.org/grpc" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" pb "github.com/path/to/go-package" // 替换为你的proto生成代码路径 ) // requestCapturingStream 包装原生ServerStream,存储收到的所有请求 type requestCapturingStream struct { grpc.ServerStream receivedRequests []*pb.TheRequest } // RecvMsg 重写RecvMsg,捕获并保存每个请求 func (s *requestCapturingStream) RecvMsg(m interface{}) error { if req, ok := m.(*pb.TheRequest); ok { // 深拷贝请求对象,防止原数据被业务逻辑修改 copiedReq := &pb.TheRequest{} *copiedReq = *req s.receivedRequests = append(s.receivedRequests, copiedReq) } return s.ServerStream.RecvMsg(m) }
步骤2:实现Panic恢复拦截器
在拦截器中通过defer recover()捕获panic,同时取出保存的请求数据进行日志记录:
// PanicRecoveryInterceptor 流式服务的panic恢复拦截器,负责记录请求信息 func PanicRecoveryInterceptor() grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { // 初始化自定义流包装器 capturingStream := &requestCapturingStream{ServerStream: ss} // 捕获panic并输出日志 defer func() { if r := recover(); r != nil { // 记录panic详情和调用栈 log.Printf("Panic触发: %v\n调用栈:\n%s", r, string(debug.Stack())) // 遍历所有收到的请求,根据oneof类型提取内容 for idx, req := range capturingStream.receivedRequests { switch payload := req.Payload.(type) { case *pb.TheRequest_Messages: log.Printf("收到的请求#%d - Messages内容: %v", idx+1, payload.Messages.Messages) case *pb.TheRequest_Config: log.Printf("收到的请求#%d - Config内容: %+v", idx+1, payload.Config) } } } }() // 调用实际业务处理逻辑 return handler(srv, capturingStream) } }
步骤3:注册拦截器到gRPC服务
创建gRPC服务器时,将自定义拦截器通过grpc_middleware.ChainStreamServer注册进去:
// NewGRPCServer 创建带拦截器的gRPC服务器 func NewGRPCServer() *grpc.Server { return grpc.NewServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( PanicRecoveryInterceptor(), // 可添加其他需要的流式拦截器 )), // 若有一元RPC服务,可添加grpc.UnaryInterceptor(...)配置一元拦截器 ) }
关键注意事项
- 必须使用
grpc.StreamServerInterceptor:你的服务是双向流式RPC,一元拦截器无法处理流式请求 - 深拷贝请求:确保日志记录的是请求接收时的原始状态,避免业务逻辑修改请求后日志失真
- 日志适配:可替换标准库
log为结构化日志库(如zap、logrus),提升日志可读性和可分析性
内容的提问来源于stack exchange,提问作者ardianbsyla
相关产品推荐
相关产品推荐

