Skip to content

Commit

Permalink
chore: intercept and log panics in wrap handlers, then re-panic (#1602)
Browse files Browse the repository at this point in the history
Fixes #1587 by creating a new grpc Interceptor to log panics in each
wrap handler, then re-panic.
  • Loading branch information
safeer authored May 31, 2024
1 parent 3b19d66 commit 3351d23
Showing 1 changed file with 48 additions and 2 deletions.
50 changes: 48 additions & 2 deletions internal/rpc/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"runtime/debug"

"connectrpc.com/connect"
"connectrpc.com/otelconnect"
Expand Down Expand Up @@ -79,7 +80,7 @@ func WithRequestName(ctx context.Context, key model.RequestKey) context.Context
}

func DefaultClientOptions(level log.Level) []connect.ClientOption {
interceptors := []connect.Interceptor{MetadataInterceptor(log.Debug), otelInterceptor()}
interceptors := []connect.Interceptor{PanicInterceptor(), MetadataInterceptor(log.Debug), otelInterceptor()}
if ftl.Version != "dev" {
interceptors = append(interceptors, versionInterceptor{})
}
Expand All @@ -90,7 +91,7 @@ func DefaultClientOptions(level log.Level) []connect.ClientOption {
}

func DefaultHandlerOptions() []connect.HandlerOption {
interceptors := []connect.Interceptor{MetadataInterceptor(log.Debug), otelInterceptor()}
interceptors := []connect.Interceptor{PanicInterceptor(), MetadataInterceptor(log.Debug), otelInterceptor()}
if ftl.Version != "dev" {
interceptors = append(interceptors, versionInterceptor{})
}
Expand All @@ -105,6 +106,51 @@ func otelInterceptor() connect.Interceptor {
return otel
}

// PanicInterceptor intercepts panics and logs them.
func PanicInterceptor() connect.Interceptor {
return &panicInterceptor{}
}

type panicInterceptor struct{}

// Intercept and log any panics, then re-panic. Defer calls to this function to
// trap panics in the calling function.
func handlePanic(ctx context.Context) {
logger := log.FromContext(ctx)
if r := recover(); r != nil {
var err error
if rerr, ok := r.(error); ok {
err = rerr
} else {
err = fmt.Errorf("%v", r)
}
stack := string(debug.Stack())
logger.Errorf(err, "panic in RPC: %s", stack)
panic(err)
}
}

func (*panicInterceptor) WrapStreamingClient(req connect.StreamingClientFunc) connect.StreamingClientFunc {
return func(ctx context.Context, s connect.Spec) connect.StreamingClientConn {
defer handlePanic(ctx)
return req(ctx, s)
}
}

func (*panicInterceptor) WrapStreamingHandler(req connect.StreamingHandlerFunc) connect.StreamingHandlerFunc {
return func(ctx context.Context, s connect.StreamingHandlerConn) error {
defer handlePanic(ctx)
return req(ctx, s)
}
}

func (*panicInterceptor) WrapUnary(uf connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
defer handlePanic(ctx)
return uf(ctx, req)
}
}

// MetadataInterceptor propagates FTL metadata through servers and clients.
//
// "errorLevel" is the level at which errors will be logged
Expand Down

0 comments on commit 3351d23

Please sign in to comment.