grpc拦截器需传符合unaryserverinterceptor签名的函数值,日志应白名单提取字段避免敏感信息泄露,鉴权须返回带用户信息的新context,监控耗时用time.since保障单调性。

gRPC拦截器为什么不能直接用UnaryServerInterceptor函数名当参数
因为UnaryServerInterceptor是类型定义,不是可调用函数。传错会导致编译报错:cannot use xxx (type func(context.Context, interface {}, ...interface {}) (interface {}, error)) as type grpc.UnaryServerInterceptor。
实际要传的是符合该类型的函数值,签名必须严格匹配:func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)。
- 常见错误:把日志逻辑写成普通函数但没返回
handler(ctx, req),导致请求被静默截断 - 正确写法里,
handler必须且只能调用一次,否则可能重复处理或 panic - 如果想跳过后续拦截器或终止请求,直接 return 错误,不要调用
handler
Go gRPC日志拦截器怎么避免打印二进制或敏感字段
直接fmt.Printf("%+v", req)会触发 protobuf 结构体的默认 Stringer,可能输出大量 base64 编码的bytes字段,甚至泄露 token、密码等。
推荐做法是白名单式字段提取,而不是全量序列化:
立即学习“go语言免费学习笔记(深入)”;
- 用
proto.MessageName(req)获取消息类型名,比reflect.TypeOf(req).String()更稳定 - 对已知结构体(如
*pb.LoginRequest)做类型断言后只取req.GetUsername()这类安全字段 - 对未知请求,用
protojson.MarshalOptions{EmitUnpopulated: false}.Marshal(req)生成紧凑 JSON,再截断长度(比如前 512 字节)
gRPC鉴权拦截器里context.Context携带用户信息的坑
很多人在拦截器里解析 token 后,用context.WithValue(ctx, key, user)往下传,结果下游 handler 拿不到——因为没把新 context 返回给handler。
关键点在于:拦截器返回的ctx必须是传给handler的那个:
- 错误写法:
ctx = context.WithValue(ctx, userKey, user); handler(ctx, req)—— 这里ctx没被返回,下游拿不到 - 正确写法:
newCtx := context.WithValue(ctx, userKey, user); return handler(newCtx, req) - 更安全的做法:定义导出的
type User struct{...}和func FromContext(ctx context.Context) (*User, bool),避免 magic key 和类型断言失败
监控拦截器统计耗时为什么用time.Since比time.Now().Sub更可靠
因为time.Now()在高并发下可能因系统时钟调整(NTP 跳变)产生负值或异常大值,而time.Since(start)底层调用runtime.nanotime(),基于单调时钟,不受系统时间回拨影响。
实操建议:
- 在拦截器开头记 start:
start := time.Now() - 在 defer 或 return 前算耗时:
duration := time.Since(start) - 上报前加判断:
if duration > 500*time.Millisecond { log.Warn("slow RPC", "method", info.FullMethod, "duration", duration) } - 注意:不要在 defer 里直接用
time.Since(start)然后传给异步上报,因为 defer 执行时机不可控,可能被 GC 干扰










