From 798b2a34ba7891597ff4f8a9013a9a744390f462 Mon Sep 17 00:00:00 2001 From: RaviAnand Mohabir Date: Tue, 25 Jun 2024 11:24:07 +0200 Subject: [PATCH] feat: :sparkles: add sentry tracer extension with operation reporting and exception capturing --- extensions/sentry/tracer.go | 95 +++++++++++++++++++++++++++++++++++++ go.mod | 16 +++++++ go.sum | 34 +++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 extensions/sentry/tracer.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/extensions/sentry/tracer.go b/extensions/sentry/tracer.go new file mode 100644 index 0000000..c4321f4 --- /dev/null +++ b/extensions/sentry/tracer.go @@ -0,0 +1,95 @@ +package extensions + +import ( + "context" + + "github.com/99designs/gqlgen/graphql" + "github.com/getsentry/sentry-go" +) + +var ( + sentrySdkIdentifier = "sentry.go.gqlgen" +) + +var _ interface { + graphql.HandlerExtension + graphql.ResponseInterceptor + graphql.FieldInterceptor +} = Sentry{} + +type Sentry struct{} + +func (Sentry) ExtensionName() string { + return "Sentry" +} + +func (Sentry) Validate(graphql.ExecutableSchema) error { + return nil +} + +func (r Sentry) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + hub := sentry.GetHubFromContext(ctx) + if hub == nil { + hub = sentry.CurrentHub().Clone() + ctx = sentry.SetHubOnContext(ctx, hub) + } + + if client := hub.Client(); client != nil { + client.SetSDKIdentifier(sentrySdkIdentifier) + } + + rc := graphql.GetOperationContext(ctx) + + span := sentry.StartTransaction( + ctx, + operationName(rc), + sentry.WithOpName("gql"), + sentry.ContinueFromHeaders( + rc.Headers.Get(sentry.SentryTraceHeader), + rc.Headers.Get(sentry.SentryBaggageHeader), + ), + ) + defer span.Finish() + + span.SetData("request.query", rc.RawQuery) + + res := next(span.Context()) + + if len(res.Errors) != 0 { + sentry.CaptureException(res.Errors) + } + + return res +} + +func (t Sentry) InterceptField(ctx context.Context, next graphql.Resolver) (interface{}, error) { + fc := graphql.GetFieldContext(ctx) + + span := sentry.StartSpan(ctx, "resolver") + defer span.Finish() + + if fc.Field.ObjectDefinition != nil { + span.Description = fc.Field.ObjectDefinition.Name + "." + fc.Field.Name + span.SetData("resolver.object", fc.Field.ObjectDefinition.Name) + } + + span.SetData("resolver.path", fc.Path().String()) + span.SetData("resolver.field", fc.Field.Name) + span.SetData("resolver.alias", fc.Field.Alias) + + return next(span.Context()) +} + +func operationName(rc *graphql.OperationContext) string { + requestName := "nameless-operation" + if rc.Doc == nil || len(rc.Doc.Operations) == 0 { + return requestName + } + + op := rc.Doc.Operations[0] + if op.Name != "" { + requestName = op.Name + } + + return requestName +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fb804e1 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module gitea.dikurium.ch/InnoPeak/gqlgen-contrib + +go 1.21.5 + +require ( + github.com/99designs/gqlgen v0.17.49 + github.com/getsentry/sentry-go v0.28.1 +) + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/vektah/gqlparser/v2 v2.5.16 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3766960 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= +github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= +github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= +github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=