123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- package grpcsentry
- import (
- "context"
- "fmt"
- "kpt-grpc-demo/util/xerr"
- "github.com/getsentry/sentry-go"
- grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
- ctxTag "github.com/grpc-ecosystem/go-grpc-middleware/tags"
- "google.golang.org/grpc"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/status"
- )
- var (
- defaultOptions = &options{
- reportDecider: defaultReportDecider,
- rePanic: false,
- }
- )
- type options struct {
- reportDecider func(err error) bool
- rePanic bool
- }
- func evaluateServerOpt(opts []Option) *options {
- optCopy := &options{}
- *optCopy = *defaultOptions
- for _, o := range opts {
- o(optCopy)
- }
- return optCopy
- }
- type Option func(*options)
- // WithCodes customizes the function for mapping errors to error codes.
- func WithReportDecider(f func(err error) bool) Option {
- return func(o *options) {
- o.reportDecider = f
- }
- }
- func WithRePanic(v bool) Option {
- return func(o *options) {
- o.rePanic = v
- }
- }
- func defaultReportDecider(err error) bool {
- _, isCustom := xerr.IsCustomError(err)
- if isCustom {
- return false
- }
- code := status.Code(err)
- switch code {
- case codes.Unknown, codes.Unimplemented, codes.Internal, codes.DeadlineExceeded, codes.DataLoss:
- return true
- default:
- return false
- }
- }
- // WithUnaryServerHandler intercept unary grpc handler and report error to sentry
- func WithUnaryServerHandler(opts ...Option) grpc.UnaryServerInterceptor {
- o := evaluateServerOpt(opts)
- return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
- var hub *sentry.Hub
- hub, ctx = newHubForCall(ctx, info.FullMethod)
- hub.Scope().SetExtra("request", req)
- // Recover and capture panic
- defer func(ctx context.Context) {
- if rval := recover(); rval != nil {
- capturePanicWithContext(ctx, rval)
- if o.rePanic {
- panic(rval)
- }
- err = status.Error(codes.Internal, fmt.Sprint(rval))
- }
- }(ctx)
- resp, err = handler(ctx, req)
- if o.reportDecider(err) {
- reportError(ctx, err)
- }
- return resp, err
- }
- }
- // WithStreamServerHandler intercept stream grpc handler and report error to sentry
- func WithStreamServerHandler(opts ...Option) grpc.StreamServerInterceptor {
- o := evaluateServerOpt(opts)
- return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
- _, newCtx := newHubForCall(stream.Context(), info.FullMethod)
- wrappedStream := grpcMiddleware.WrapServerStream(stream)
- wrappedStream.WrappedContext = newCtx
- stream = wrappedStream
- // Recover and capture panic
- defer func(ctx context.Context) {
- if rval := recover(); rval != nil {
- capturePanicWithContext(ctx, rval)
- if o.rePanic {
- panic(rval)
- }
- err = status.Error(codes.Internal, fmt.Sprint(rval))
- }
- }(stream.Context())
- err = handler(srv, stream)
- if o.reportDecider(err) {
- reportError(stream.Context(), err)
- }
- return err
- }
- }
- // report if err != nil
- func reportError(ctx context.Context, err error) {
- errCode := status.Code(err)
- hub := sentry.GetHubFromContext(ctx)
- if hub == nil {
- return
- }
- sentryExtras := ctxTag.Extract(ctx).Values()
- sentryTags := make(map[string]string)
- sentryTags["grpc.code"] = errCode.String()
- hub.ConfigureScope(func(scope *sentry.Scope) {
- scope.SetTags(sentryTags)
- scope.SetExtras(sentryExtras)
- })
- hub.CaptureException(err)
- }
- func capturePanicWithContext(ctx context.Context, err interface{}) {
- hub := sentry.GetHubFromContext(ctx)
- if hub == nil {
- return
- }
- hub.ConfigureScope(func(scope *sentry.Scope) {
- scope.SetExtras(ctxTag.Extract(ctx).Values())
- })
- _ = hub.RecoverWithContext(ctx, err)
- }
- func newHubForCall(ctx context.Context, fullMethodString string) (*sentry.Hub, context.Context) {
- hub := sentry.CurrentHub().Clone()
- hub.ConfigureScope(func(scope *sentry.Scope) {
- scope.SetTag("grpc_method", fullMethodString)
- })
- newCtx := sentry.SetHubOnContext(ctx, hub)
- return hub, newCtx
- }
|