Merge pull request #78 from Neur0toxine/sentry-extended-support

easy interaction with sentry in gin handlers
This commit is contained in:
Pavel 2024-09-25 14:39:07 +03:00 committed by GitHub
commit e278ba58cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 221 additions and 2 deletions

63
core/middleware/sentry.go Normal file
View File

@ -0,0 +1,63 @@
package middleware
import (
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
)
var ginContextSentryKey = "sentry"
type Sentry interface {
CaptureException(c *gin.Context, exception error)
CaptureMessage(c *gin.Context, message string)
CaptureEvent(c *gin.Context, event *sentry.Event)
}
func InjectSentry(sentry Sentry) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set(ginContextSentryKey, sentry)
}
}
func GetSentry(c *gin.Context) (Sentry, bool) {
sentryValue, ok := c.Get(ginContextSentryKey)
if !ok {
return nil, false
}
obj, ok := sentryValue.(Sentry)
if !ok || obj == nil {
return nil, false
}
return obj, true
}
func MustGetSentry(c *gin.Context) Sentry {
if obj, ok := GetSentry(c); ok && obj != nil {
return obj
}
panic("obj not found in context")
}
func CaptureException(c *gin.Context, exception error) {
obj, found := GetSentry(c)
if !found {
return
}
obj.CaptureException(c, exception)
}
func CaptureEvent(c *gin.Context, event *sentry.Event) {
obj, found := GetSentry(c)
if !found {
return
}
obj.CaptureEvent(c, event)
}
func CaptureMessage(c *gin.Context, message string) {
obj, found := GetSentry(c)
if !found {
return
}
obj.CaptureMessage(c, message)
}

View File

@ -0,0 +1,105 @@
package middleware
import (
"errors"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"testing"
)
type SentryMiddlewaresTestSuite struct {
suite.Suite
}
func TestSentryMiddlewares(t *testing.T) {
suite.Run(t, new(SentryMiddlewaresTestSuite))
}
func (s *SentryMiddlewaresTestSuite) ctx(mock Sentry) *gin.Context {
ctx := &gin.Context{}
InjectSentry(mock)(ctx)
return ctx
}
func (s *SentryMiddlewaresTestSuite) TestGetSentry_Empty() {
item, found := GetSentry(&gin.Context{})
s.Assert().False(found)
s.Assert().Nil(item)
item, found = GetSentry(&gin.Context{
Keys: map[string]interface{}{
ginContextSentryKey: &gin.Engine{},
},
})
s.Assert().False(found)
s.Assert().Nil(item)
}
func (s *SentryMiddlewaresTestSuite) TestMustGetSentry_Empty() {
s.Assert().Panics(func() {
MustGetSentry(&gin.Context{})
})
}
func (s *SentryMiddlewaresTestSuite) TestGetSentry_Success() {
item, found := GetSentry(&gin.Context{
Keys: map[string]interface{}{
ginContextSentryKey: &sentryMock{},
},
})
s.Assert().True(found)
s.Assert().NotNil(item)
}
func (s *SentryMiddlewaresTestSuite) TestMustGetSentry_Success() {
s.Assert().NotPanics(func() {
item := MustGetSentry(&gin.Context{
Keys: map[string]interface{}{
ginContextSentryKey: &sentryMock{},
},
})
s.Assert().NotNil(item)
})
}
func (s *SentryMiddlewaresTestSuite) TestCaptureException() {
err := errors.New("test error")
item := &sentryMock{}
item.On("CaptureException", mock.AnythingOfType("*gin.Context"), err).Return()
CaptureException(s.ctx(item), err)
item.AssertExpectations(s.T())
}
func (s *SentryMiddlewaresTestSuite) TestCaptureMessage() {
msg := "test error"
item := &sentryMock{}
item.On("CaptureMessage", mock.AnythingOfType("*gin.Context"), msg).Return()
CaptureMessage(s.ctx(item), msg)
item.AssertExpectations(s.T())
}
func (s *SentryMiddlewaresTestSuite) TestCaptureEvent() {
event := &sentry.Event{EventID: "1"}
item := &sentryMock{}
item.On("CaptureEvent", mock.AnythingOfType("*gin.Context"), event).Return()
CaptureEvent(s.ctx(item), event)
item.AssertExpectations(s.T())
}
type sentryMock struct {
mock.Mock
}
func (s *sentryMock) CaptureException(c *gin.Context, exception error) {
s.Called(c, exception)
}
func (s *sentryMock) CaptureMessage(c *gin.Context, message string) {
s.Called(c, message)
}
func (s *sentryMock) CaptureEvent(c *gin.Context, event *sentry.Event) {
s.Called(c, event)
}

View File

@ -126,7 +126,24 @@ func (s *Sentry) CaptureException(c *gin.Context, exception error) {
hub.CaptureException(exception) hub.CaptureException(exception)
return return
} }
_ = c.Error(exception) }
// CaptureMessage and send it to Sentry.
func (s *Sentry) CaptureMessage(c *gin.Context, message string) {
if hub := sentrygin.GetHubFromContext(c); hub != nil {
s.setScopeTags(c, hub.Scope())
hub.CaptureMessage(message)
return
}
}
// CaptureEvent and send it to Sentry.
func (s *Sentry) CaptureEvent(c *gin.Context, event *sentry.Event) {
if hub := sentrygin.GetHubFromContext(c); hub != nil {
s.setScopeTags(c, hub.Scope())
hub.CaptureEvent(event)
return
}
} }
// SentryMiddlewares contain all the middlewares required to process errors and panics and send them to the Sentry. // SentryMiddlewares contain all the middlewares required to process errors and panics and send them to the Sentry.
@ -201,12 +218,14 @@ func (s *Sentry) exceptionCaptureMiddleware() gin.HandlerFunc { // nolint:gocogn
for _, err := range publicErrors { for _, err := range publicErrors {
messages[index] = err.Error() messages[index] = err.Error()
s.CaptureException(c, err) s.CaptureException(c, err)
_ = c.Error(err)
l.Error(err.Error()) l.Error(err.Error())
index++ index++
} }
for _, err := range privateErrors { for _, err := range privateErrors {
s.CaptureException(c, err) s.CaptureException(c, err)
_ = c.Error(err)
l.Error(err.Error()) l.Error(err.Error())
} }

View File

@ -256,7 +256,7 @@ func (s *SentryTest) TestSentry_CaptureException_Error() {
s.sentry.CaptureException(ctx, errors.New("test error")) s.sentry.CaptureException(ctx, errors.New("test error"))
s.Require().Nil(transport.lastEvent) s.Require().Nil(transport.lastEvent)
s.Require().Len(ctx.Errors, 1) s.Require().Len(ctx.Errors, 0)
} }
func (s *SentryTest) TestSentry_CaptureException() { func (s *SentryTest) TestSentry_CaptureException() {
@ -275,6 +275,38 @@ func (s *SentryTest) TestSentry_CaptureException() {
s.Assert().NotNil(transport.lastEvent.Exception[1].Stacktrace) s.Assert().NotNil(transport.lastEvent.Exception[1].Stacktrace)
} }
func (s *SentryTest) TestSentry_CaptureEvent_Nil() {
defer func() {
s.Assert().Nil(recover())
}()
s.sentry.CaptureEvent(&gin.Context{}, nil)
}
func (s *SentryTest) TestSentry_CaptureEvent_Error() {
ctx, transport := s.ginCtxMock()
ctx.Keys = make(map[string]interface{})
s.sentry.CaptureEvent(ctx, &sentry.Event{})
s.Require().Nil(transport.lastEvent)
s.Require().Len(ctx.Errors, 0)
}
func (s *SentryTest) TestSentry_CaptureMessage_Empty() {
defer func() {
s.Assert().Nil(recover())
}()
s.sentry.CaptureMessage(&gin.Context{}, "")
}
func (s *SentryTest) TestSentry_CaptureMessage_Error() {
ctx, transport := s.ginCtxMock()
ctx.Keys = make(map[string]interface{})
s.sentry.CaptureMessage(ctx, "")
s.Require().Nil(transport.lastEvent)
s.Require().Len(ctx.Errors, 0)
}
func (s *SentryTest) TestSentry_obtainErrorLogger_Existing() { func (s *SentryTest) TestSentry_obtainErrorLogger_Existing() {
ctx, _ := s.ginCtxMock() ctx, _ := s.ginCtxMock()
log := testutil.NewBufferedLogger().ForHandler("component").ForConnection("conn").ForAccount("acc") log := testutil.NewBufferedLogger().ForHandler("component").ForConnection("conn").ForAccount("acc")