sentry.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package grpcsentry
  2. import (
  3. "context"
  4. "fmt"
  5. "kpt-grpc-demo/util/xerr"
  6. "github.com/getsentry/sentry-go"
  7. grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
  8. ctxTag "github.com/grpc-ecosystem/go-grpc-middleware/tags"
  9. "google.golang.org/grpc"
  10. "google.golang.org/grpc/codes"
  11. "google.golang.org/grpc/status"
  12. )
  13. var (
  14. defaultOptions = &options{
  15. reportDecider: defaultReportDecider,
  16. rePanic: false,
  17. }
  18. )
  19. type options struct {
  20. reportDecider func(err error) bool
  21. rePanic bool
  22. }
  23. func evaluateServerOpt(opts []Option) *options {
  24. optCopy := &options{}
  25. *optCopy = *defaultOptions
  26. for _, o := range opts {
  27. o(optCopy)
  28. }
  29. return optCopy
  30. }
  31. type Option func(*options)
  32. // WithCodes customizes the function for mapping errors to error codes.
  33. func WithReportDecider(f func(err error) bool) Option {
  34. return func(o *options) {
  35. o.reportDecider = f
  36. }
  37. }
  38. func WithRePanic(v bool) Option {
  39. return func(o *options) {
  40. o.rePanic = v
  41. }
  42. }
  43. func defaultReportDecider(err error) bool {
  44. _, isCustom := xerr.IsCustomError(err)
  45. if isCustom {
  46. return false
  47. }
  48. code := status.Code(err)
  49. switch code {
  50. case codes.Unknown, codes.Unimplemented, codes.Internal, codes.DeadlineExceeded, codes.DataLoss:
  51. return true
  52. default:
  53. return false
  54. }
  55. }
  56. // WithUnaryServerHandler intercept unary grpc handler and report error to sentry
  57. func WithUnaryServerHandler(opts ...Option) grpc.UnaryServerInterceptor {
  58. o := evaluateServerOpt(opts)
  59. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  60. var hub *sentry.Hub
  61. hub, ctx = newHubForCall(ctx, info.FullMethod)
  62. hub.Scope().SetExtra("request", req)
  63. // Recover and capture panic
  64. defer func(ctx context.Context) {
  65. if rval := recover(); rval != nil {
  66. capturePanicWithContext(ctx, rval)
  67. if o.rePanic {
  68. panic(rval)
  69. }
  70. err = status.Error(codes.Internal, fmt.Sprint(rval))
  71. }
  72. }(ctx)
  73. resp, err = handler(ctx, req)
  74. if o.reportDecider(err) {
  75. reportError(ctx, err)
  76. }
  77. return resp, err
  78. }
  79. }
  80. // WithStreamServerHandler intercept stream grpc handler and report error to sentry
  81. func WithStreamServerHandler(opts ...Option) grpc.StreamServerInterceptor {
  82. o := evaluateServerOpt(opts)
  83. return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
  84. _, newCtx := newHubForCall(stream.Context(), info.FullMethod)
  85. wrappedStream := grpcMiddleware.WrapServerStream(stream)
  86. wrappedStream.WrappedContext = newCtx
  87. stream = wrappedStream
  88. // Recover and capture panic
  89. defer func(ctx context.Context) {
  90. if rval := recover(); rval != nil {
  91. capturePanicWithContext(ctx, rval)
  92. if o.rePanic {
  93. panic(rval)
  94. }
  95. err = status.Error(codes.Internal, fmt.Sprint(rval))
  96. }
  97. }(stream.Context())
  98. err = handler(srv, stream)
  99. if o.reportDecider(err) {
  100. reportError(stream.Context(), err)
  101. }
  102. return err
  103. }
  104. }
  105. // report if err != nil
  106. func reportError(ctx context.Context, err error) {
  107. errCode := status.Code(err)
  108. hub := sentry.GetHubFromContext(ctx)
  109. if hub == nil {
  110. return
  111. }
  112. sentryExtras := ctxTag.Extract(ctx).Values()
  113. sentryTags := make(map[string]string)
  114. sentryTags["grpc.code"] = errCode.String()
  115. hub.ConfigureScope(func(scope *sentry.Scope) {
  116. scope.SetTags(sentryTags)
  117. scope.SetExtras(sentryExtras)
  118. })
  119. hub.CaptureException(err)
  120. }
  121. func capturePanicWithContext(ctx context.Context, err interface{}) {
  122. hub := sentry.GetHubFromContext(ctx)
  123. if hub == nil {
  124. return
  125. }
  126. hub.ConfigureScope(func(scope *sentry.Scope) {
  127. scope.SetExtras(ctxTag.Extract(ctx).Values())
  128. })
  129. _ = hub.RecoverWithContext(ctx, err)
  130. }
  131. func newHubForCall(ctx context.Context, fullMethodString string) (*sentry.Hub, context.Context) {
  132. hub := sentry.CurrentHub().Clone()
  133. hub.ConfigureScope(func(scope *sentry.Scope) {
  134. scope.SetTag("grpc_method", fullMethodString)
  135. })
  136. newCtx := sentry.SetHubOnContext(ctx, hub)
  137. return hub, newCtx
  138. }