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 }