Skip to content

Commit 42c0254

Browse files
committed
feat: Add recovery middleware for GRPC server
1 parent bcfe1b2 commit 42c0254

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

pkg/interceptors/recovery.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package interceptors
2+
3+
import (
4+
"context"
5+
stdlog "log"
6+
"runtime/debug"
7+
"time"
8+
9+
"github.com/getsentry/sentry-go"
10+
grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
11+
"google.golang.org/grpc"
12+
"google.golang.org/grpc/codes"
13+
"google.golang.org/grpc/status"
14+
15+
sdkloggercontext "github.com/scribd/go-sdk/pkg/context/logger"
16+
)
17+
18+
// RecoveryUnaryServerInterceptor returns a unary server interceptor that recovers from panics,
19+
// sends a sentry event, log in fatal level and halts the service.
20+
// IMPORTANT: This interceptor should be the last one in the interceptor chain.
21+
func RecoveryUnaryServerInterceptor() grpc.UnaryServerInterceptor {
22+
return grpcrecovery.UnaryServerInterceptor(recoveryOption...)
23+
}
24+
25+
// RecoveryStreamServerInterceptor returns a streaming server interceptor that recovers from panics,
26+
// sends a sentry event, log in fatal level and halts the service.
27+
// IMPORTANT: This interceptor should be the last one in the interceptor chain.
28+
func RecoveryStreamServerInterceptor() grpc.StreamServerInterceptor {
29+
return grpcrecovery.StreamServerInterceptor(recoveryOption...)
30+
}
31+
32+
var recoveryOption = []grpcrecovery.Option{
33+
grpcrecovery.WithRecoveryHandlerContext(func(ctx context.Context, rec interface{}) (err error) {
34+
sentry.CurrentHub().Recover(rec)
35+
sentry.Flush(time.Second * 5)
36+
37+
l, err := sdkloggercontext.Extract(ctx)
38+
if err != nil {
39+
debug.PrintStack()
40+
stdlog.Printf("logger not found in context: %v\n", err)
41+
stdlog.Fatalf("grpc: panic error: %v", rec)
42+
}
43+
44+
l.Fatalf("panic error: %v", rec)
45+
return status.Errorf(codes.Internal, "")
46+
}),
47+
}

pkg/middleware/recovery.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package middleware
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"runtime/debug"
7+
"time"
8+
9+
"github.com/getsentry/sentry-go"
10+
11+
sdkloggercontext "github.com/scribd/go-sdk/pkg/context/logger"
12+
)
13+
14+
// RecoveryMiddleware is a middleware that recovers from panics and logs them.
15+
type RecoveryMiddleware struct{}
16+
17+
// NewRecoveryMiddleware is a constructor used to build a RecoveryMiddleware.
18+
// IMPORTANT: This middleware should be the last one in the middleware chain.
19+
func NewRecoveryMiddleware() RecoveryMiddleware {
20+
return RecoveryMiddleware{}
21+
}
22+
23+
// Handler implements the middlewares.Handlerer interface: it returns a
24+
// http.Handler to be mounted as middleware. The Handler recovers from a panic,
25+
// sends a sentry event, sends fatal error log and halts the service.
26+
func (rm RecoveryMiddleware) Handler(next http.Handler) http.Handler {
27+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28+
defer func() {
29+
if rec := recover(); rec != nil {
30+
sentry.CurrentHub().Recover(rec)
31+
sentry.Flush(time.Second * 5)
32+
33+
l, err := sdkloggercontext.Extract(r.Context())
34+
if err != nil {
35+
debug.PrintStack()
36+
log.Printf("logger not found in context: %v\n", err)
37+
log.Fatalf("http: panic serving URI %s: %v", r.URL.RequestURI(), rec)
38+
}
39+
40+
l.Fatalf("http: panic serving URI %s: %v", r.URL.RequestURI(), rec)
41+
}
42+
}()
43+
44+
next.ServeHTTP(w, r)
45+
})
46+
}

0 commit comments

Comments
 (0)