mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-22 05:06:04 +03:00
Merge pull request #78 from Neur0toxine/sentry-extended-support
easy interaction with sentry in gin handlers
This commit is contained in:
commit
e278ba58cd
63
core/middleware/sentry.go
Normal file
63
core/middleware/sentry.go
Normal 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)
|
||||||
|
}
|
105
core/middleware/sentry_test.go
Normal file
105
core/middleware/sentry_test.go
Normal 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)
|
||||||
|
}
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user