Implementing Interceptors
1. Creating Unary Server Interceptor
Example: Logging unary server interceptor
func LoggingUnary(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
start := time.Now()
res, err := handler(ctx, req)
log.Printf("%s %v code=%s", info.FullMethod, time.Since(start), status.Code(err))
return res, err
}
| Param | Purpose |
|---|---|
info.FullMethod | "/pkg.Service/Method" |
handler | Call to invoke next interceptor / handler |
2. Creating Stream Server Interceptor
Example: Stream interceptor
func StreamLog(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo,
handler grpc.StreamHandler) error {
start := time.Now()
err := handler(srv, ss)
log.Printf("stream %s %v", info.FullMethod, time.Since(start))
return err
}
3. Creating Unary Client Interceptor
Example: Auth-injecting client interceptor
func AuthUnary(token string) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply any,
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
return invoker(ctx, method, req, reply, cc, opts...)
}
}
4. Creating Stream Client Interceptor
| Signature | Detail |
|---|---|
StreamClientInterceptor | Wraps stream creation; can wrap returned ClientStream |
| Use | Per-message logging, metrics on Send/Recv |
5. Chaining Interceptors
Example: Chain on server & client
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(Recovery, Logging, Auth, Validation),
grpc.ChainStreamInterceptor(StreamRecovery, StreamLog),
)
conn, _ := grpc.NewClient(addr,
grpc.WithChainUnaryInterceptor(Retry, Tracing, AuthUnary(token)),
)
| Order | Detail |
|---|---|
| First listed | Outermost — runs first on inbound, last on return |
| Recovery first | Catches panics from all inner interceptors |
6. Implementing Logging Interceptor
| Field | Source |
|---|---|
| method | info.FullMethod |
| duration | time.Since(start) |
| status code | status.Code(err) |
| request id | From incoming metadata |
7. Implementing Auth Interceptor
Example: Token validation interceptor
func AuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
if isPublic(info.FullMethod) { return handler(ctx, req) }
md, _ := metadata.FromIncomingContext(ctx)
tok := strings.TrimPrefix(first(md, "authorization"), "Bearer ")
claims, err := jwt.Parse(tok, key)
if err != nil { return nil, status.Error(codes.Unauthenticated, "invalid token") }
return handler(context.WithValue(ctx, ctxKey{}, claims), req)
}
8. Implementing Recovery Interceptor
Example: Panic recovery
func Recovery(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (res any, err error) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic %s: %v\n%s", info.FullMethod, r, debug.Stack())
err = status.Error(codes.Internal, "internal error")
}
}()
return handler(ctx, req)
}
| Tip | Detail |
|---|---|
| Place outermost | Catches panics from other interceptors + handler |
| Library | github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery |
9. Modifying Request and Response
| Concern | Detail |
|---|---|
| Avoid mutation | Generated messages are not designed for in-place edits |
| Wrap response | Replace fields via cloned message |
| Stream wrappers | Embed grpc.ServerStream and override SendMsg/RecvMsg |
10. Registering Interceptors
| Side | API |
|---|---|
| Server unary | grpc.UnaryInterceptor / ChainUnaryInterceptor |
| Server stream | grpc.StreamInterceptor / ChainStreamInterceptor |
| Client unary | grpc.WithUnaryInterceptor / WithChainUnaryInterceptor |
| Client stream | grpc.WithStreamInterceptor / WithChainStreamInterceptor |