如何为Go-gRPC Server注入默认Context(如日志实例)
在Go gRPC中注入全局默认Context的方案
Go gRPC框架确实没有提供直接为所有服务处理器注入全局默认context.Context的快捷配置(比如WithContext选项、NewServerWithContext构造方法这类),拦截器是目前最合理且简便的实现方式,下面具体拆解你的疑问:
1. 拦截器是标准实现路径
不管是一元还是流式gRPC调用,都可以通过拦截器将全局上下文(比如携带logr实例的Context)和请求自带的Context合并,传递给后续的服务处理器:
一元调用拦截器示例
import ( "context" "google.golang.org/grpc" "github.com/go-logr/logr" ) // 全局默认Context,预注入logr实例 var globalCtx = logr.NewContext(context.Background(), logr.New()) func unaryInjectInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // 合并全局Context与请求Context,保留请求自带的元数据等信息 mergedCtx := logr.NewContext(ctx, logr.FromContext(globalCtx)) return handler(mergedCtx, req) }
流式调用拦截器与ServerStream包装
对于流式调用,请求的Context绑定在grpc.ServerStream实例上,必须通过包装ServerStream来替换Context,这一步确实需要自定义结构体:
// 自定义ServerStream包装结构体 type wrappedStream struct { grpc.ServerStream ctx context.Context } // 重写Context方法,返回合并后的上下文 func (w *wrappedStream) Context() context.Context { return w.ctx } func streamInjectInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { mergedCtx := logr.NewContext(ss.Context(), logr.FromContext(globalCtx)) wrapped := &wrappedStream{ss, mergedCtx} return handler(srv, wrapped) }
2. 为什么必须包装ServerStream?
gRPC流式调用中,服务处理器获取Context的唯一方式是调用ServerStream.Context(),这个方法返回的是初始化时绑定的请求Context。要注入全局内容,必须替换这个返回值,所以必须实现自定义的ServerStream包装类,重写Context()方法返回合并后的上下文——这是gRPC流式扩展的标准做法,没有更简便的替代方式。
3. 关于logr的中间件
logr本身确实没有提供现成的gRPC注入中间件,但基于上面的拦截器逻辑,你可以很容易封装成通用的logr注入中间件,比如:
func LogrInjectInterceptor(logger logr.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return handler(logr.NewContext(ctx, logger), req) } } // 流式版本同理 func LogrStreamInjectInterceptor(logger logr.Logger) grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { wrapped := &wrappedStream{ ServerStream: ss, ctx: logr.NewContext(ss.Context(), logger), } return handler(srv, wrapped) } }
最终使用方式
创建gRPC Server时注册拦截器即可:
server := grpc.NewServer( grpc.UnaryInterceptor(LogrInjectInterceptor(globalLogger)), grpc.StreamInterceptor(LogrStreamInjectInterceptor(globalLogger)), )
内容的提问来源于stack exchange,提问作者Craig Ringer
相关产品推荐
相关产品推荐

