Skip to content

Commit

Permalink
feat: Add recovery middleware for GRPC server
Browse files Browse the repository at this point in the history
  • Loading branch information
laynax committed Jun 19, 2024
1 parent bcfe1b2 commit 42c0254
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 0 deletions.
47 changes: 47 additions & 0 deletions pkg/interceptors/recovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package interceptors

import (
"context"
stdlog "log"
"runtime/debug"
"time"

"github.com/getsentry/sentry-go"
grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

sdkloggercontext "github.com/scribd/go-sdk/pkg/context/logger"
)

// RecoveryUnaryServerInterceptor returns a unary server interceptor that recovers from panics,
// sends a sentry event, log in fatal level and halts the service.
// IMPORTANT: This interceptor should be the last one in the interceptor chain.
func RecoveryUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return grpcrecovery.UnaryServerInterceptor(recoveryOption...)
}

// RecoveryStreamServerInterceptor returns a streaming server interceptor that recovers from panics,
// sends a sentry event, log in fatal level and halts the service.
// IMPORTANT: This interceptor should be the last one in the interceptor chain.
func RecoveryStreamServerInterceptor() grpc.StreamServerInterceptor {
return grpcrecovery.StreamServerInterceptor(recoveryOption...)
}

var recoveryOption = []grpcrecovery.Option{
grpcrecovery.WithRecoveryHandlerContext(func(ctx context.Context, rec interface{}) (err error) {
sentry.CurrentHub().Recover(rec)
sentry.Flush(time.Second * 5)

l, err := sdkloggercontext.Extract(ctx)
if err != nil {
debug.PrintStack()
stdlog.Printf("logger not found in context: %v\n", err)
stdlog.Fatalf("grpc: panic error: %v", rec)
}

l.Fatalf("panic error: %v", rec)
return status.Errorf(codes.Internal, "")
}),
}
46 changes: 46 additions & 0 deletions pkg/middleware/recovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package middleware

import (
"log"
"net/http"
"runtime/debug"
"time"

"github.com/getsentry/sentry-go"

sdkloggercontext "github.com/scribd/go-sdk/pkg/context/logger"
)

// RecoveryMiddleware is a middleware that recovers from panics and logs them.
type RecoveryMiddleware struct{}

// NewRecoveryMiddleware is a constructor used to build a RecoveryMiddleware.
// IMPORTANT: This middleware should be the last one in the middleware chain.
func NewRecoveryMiddleware() RecoveryMiddleware {
return RecoveryMiddleware{}
}

// Handler implements the middlewares.Handlerer interface: it returns a
// http.Handler to be mounted as middleware. The Handler recovers from a panic,
// sends a sentry event, sends fatal error log and halts the service.
func (rm RecoveryMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
sentry.CurrentHub().Recover(rec)
sentry.Flush(time.Second * 5)

l, err := sdkloggercontext.Extract(r.Context())
if err != nil {
debug.PrintStack()
log.Printf("logger not found in context: %v\n", err)
log.Fatalf("http: panic serving URI %s: %v", r.URL.RequestURI(), rec)
}

l.Fatalf("http: panic serving URI %s: %v", r.URL.RequestURI(), rec)
}
}()

next.ServeHTTP(w, r)
})
}

0 comments on commit 42c0254

Please sign in to comment.