diff --git a/go.mod b/go.mod index e7e646c..d87e859 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/99designs/gqlgen v0.17.34 github.com/spf13/cobra v1.7.0 github.com/vektah/gqlparser/v2 v2.5.6 + go.opencensus.io v0.24.0 ) require ( @@ -45,7 +46,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect github.com/zemirco/memorystore v0.0.0-20160308183530-ecd57e5134f6 // indirect - go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.1 // indirect go.uber.org/multierr v1.6.0 // indirect diff --git a/module.go b/module.go index 367ec7d..6d95fe6 100644 --- a/module.go +++ b/module.go @@ -37,21 +37,25 @@ func (*Module) Configure(injector *dingo.Injector) { type routes struct { exec graphql.ExecutableSchema reverseRouter web.ReverseRouter - origins flamingoConfig.Slice - introspectionEnabled bool - uploadMaxSize int64 operationMiddlewares []graphql.OperationMiddleware + + // configs + origins flamingoConfig.Slice + openCensusTracingEnabled bool + introspectionEnabled bool + uploadMaxSize int64 } // Inject executable schema func (r *routes) Inject( reverseRouter web.ReverseRouter, config *struct { - Exec graphql.ExecutableSchema `inject:",optional"` - OperationMiddlewares []graphql.OperationMiddleware `inject:",optional"` - Origins flamingoConfig.Slice `inject:"config:graphql.cors.origins"` - IntrospectionEnabled bool `inject:"config:graphql.introspectionEnabled,optional"` - UploadMaxSize int64 `inject:"config:graphql.multipartForm.uploadMaxSize,optional"` + Exec graphql.ExecutableSchema `inject:",optional"` + OperationMiddlewares []graphql.OperationMiddleware `inject:",optional"` + Origins flamingoConfig.Slice `inject:"config:graphql.cors.origins"` + IntrospectionEnabled bool `inject:"config:graphql.introspectionEnabled,optional"` + OpenCensusTracingEnabled bool `inject:"config:graphql.openCensusTracingEnabled,optional"` + UploadMaxSize int64 `inject:"config:graphql.multipartForm.uploadMaxSize,optional"` }, ) { r.reverseRouter = reverseRouter @@ -61,6 +65,7 @@ func (r *routes) Inject( r.introspectionEnabled = config.IntrospectionEnabled r.uploadMaxSize = config.UploadMaxSize r.operationMiddlewares = config.OperationMiddlewares + r.openCensusTracingEnabled = config.OpenCensusTracingEnabled } } @@ -96,6 +101,10 @@ func (r *routes) Routes(registry *web.RouterRegistry) { Cache: lru.New(100), }) + if r.openCensusTracingEnabled { + srv.Use(&OpenCensusTracingExtension{}) + } + return srv }(r.exec) @@ -122,6 +131,7 @@ func (m *Module) CueConfig() string { return ` graphql: { introspectionEnabled: bool | *false + openCensusTracingEnabled: bool | *true multipartForm: { uploadMaxSize: (int | *1.5M) & > 0 } diff --git a/opencensus_tracing.go b/opencensus_tracing.go new file mode 100644 index 0000000..0be4b73 --- /dev/null +++ b/opencensus_tracing.go @@ -0,0 +1,52 @@ +package graphql + +import ( + "context" + "fmt" + + "github.com/99designs/gqlgen/graphql" + "go.opencensus.io/trace" +) + +var _ graphql.HandlerExtension = &OpenCensusTracingExtension{} +var _ graphql.ResponseInterceptor = &OpenCensusTracingExtension{} +var _ graphql.FieldInterceptor = &OpenCensusTracingExtension{} +var _ graphql.OperationInterceptor = &OpenCensusTracingExtension{} + +type OpenCensusTracingExtension struct{} + +func (o *OpenCensusTracingExtension) ExtensionName() string { + return "OpenCensusTracing" +} + +func (o *OpenCensusTracingExtension) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { + fieldContext := graphql.GetFieldContext(ctx) + + // for now, we only trace resolvers + if !fieldContext.IsResolver { + return next(ctx) + } + + spanName := fmt.Sprintf("graphql/resolver/%s", fieldContext.Field.Name) + if fieldContext.Object != "Query" && fieldContext.Object != "Mutation" { + // enrich information if the resolver is registered on a graphql subtype, e.g. resolver that resolves `sortedList` on type `Users_Result` + spanName = fmt.Sprintf("graphql/resolver/%s/%s", fieldContext.Object, fieldContext.Field.Name) + } + + ctx, span := trace.StartSpan(ctx, spanName) + defer span.End() + + return next(ctx) +} + +func (o *OpenCensusTracingExtension) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { + return next(ctx) +} + +func (o *OpenCensusTracingExtension) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + return next(ctx) +} + +func (o *OpenCensusTracingExtension) Validate(_ graphql.ExecutableSchema) error { + return nil +}