Skip to content

Commit

Permalink
Add context to validation tracing
Browse files Browse the repository at this point in the history
Context is needed for tracing to access the current span, in order to
 add tags to it, or create child spans. As presently defined (without a
 context), this cannot be done: new spans could be created, but they
 would not be associated with the existing request trace.

OpenTracingTracer implements the new interface (it never implemented the
 old one). Via this 'extension interface', the tracer configured (or the
 default tracer) will be used as the validation tracer if:

 * The tracer implements the (optional) interface; and
 * A validation tracer isn't provided using the deprecated option

What this means is that the deprecated option is _preferred_ as an
 override. This allows users to migrate in a non-breaking, non-behaviour
 changing way, until such time as they intentionally remove the use of
 the deprecated option. For those who are currently using the default
 tracer, and not supplying a validation tracer, validation will be traced
 immediately with no change required to configuration options.
  • Loading branch information
dackroyd committed Feb 19, 2021
1 parent beb923f commit 1a55b96
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 9 deletions.
32 changes: 24 additions & 8 deletions graphql.go
Expand Up @@ -25,16 +25,23 @@ import (
// resolver, then the schema can not be executed, but it may be inspected (e.g. with ToJSON).
func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (*Schema, error) {
s := &Schema{
schema: schema.New(),
maxParallelism: 10,
tracer: trace.OpenTracingTracer{},
validationTracer: trace.NoopValidationTracer{},
logger: &log.DefaultLogger{},
schema: schema.New(),
maxParallelism: 10,
tracer: trace.OpenTracingTracer{},
logger: &log.DefaultLogger{},
}
for _, opt := range opts {
opt(s)
}

if s.validationTracer == nil {
if tracer, ok := s.tracer.(trace.ValidationTracerContext); ok {
s.validationTracer = tracer
} else {
s.validationTracer = &validationBridgingTracer{tracer: trace.NoopValidationTracer{}}
}
}

if err := s.schema.Parse(schemaString, s.useStringDescriptions); err != nil {
return nil, err
}
Expand Down Expand Up @@ -68,7 +75,7 @@ type Schema struct {
maxDepth int
maxParallelism int
tracer trace.Tracer
validationTracer trace.ValidationTracer
validationTracer trace.ValidationTracerContext
logger log.Logger
useStringDescriptions bool
disableIntrospection bool
Expand Down Expand Up @@ -117,9 +124,10 @@ func Tracer(tracer trace.Tracer) SchemaOpt {
}

// ValidationTracer is used to trace validation errors. It defaults to trace.NoopValidationTracer.
// Deprecated: context is needed to support tracing correctly. Use a Tracer which implements trace.ValidationTracerContext.
func ValidationTracer(tracer trace.ValidationTracer) SchemaOpt {
return func(s *Schema) {
s.validationTracer = tracer
s.validationTracer = &validationBridgingTracer{tracer: tracer}
}
}

Expand Down Expand Up @@ -186,7 +194,7 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str
return &Response{Errors: []*errors.QueryError{qErr}}
}

validationFinish := s.validationTracer.TraceValidation()
validationFinish := s.validationTracer.TraceValidation(ctx)
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
validationFinish(errs)
if len(errs) != 0 {
Expand Down Expand Up @@ -272,6 +280,14 @@ func (s *Schema) validateSchema() error {
return nil
}

type validationBridgingTracer struct {
tracer trace.ValidationTracer
}

func (t *validationBridgingTracer) TraceValidation(context.Context) trace.TraceValidationFinishFunc {
return t.tracer.TraceValidation()
}

func validateRootOp(s *schema.Schema, name string, mandatory bool) error {
t, ok := s.EntryPoints[name]
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion subscriptions.go
Expand Up @@ -36,7 +36,7 @@ func (s *Schema) subscribe(ctx context.Context, queryString string, operationNam
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{qErr}})
}

validationFinish := s.validationTracer.TraceValidation()
validationFinish := s.validationTracer.TraceValidation(ctx)
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
validationFinish(errs)
if len(errs) != 0 {
Expand Down
16 changes: 16 additions & 0 deletions trace/trace.go
Expand Up @@ -67,6 +67,22 @@ func (OpenTracingTracer) TraceField(ctx context.Context, label, typeName, fieldN
}
}

func (OpenTracingTracer) TraceValidation(ctx context.Context) TraceValidationFinishFunc {
span, _ := opentracing.StartSpanFromContext(ctx, "Validate Query")

return func(errs []*errors.QueryError) {
if len(errs) > 0 {
msg := errs[0].Error()
if len(errs) > 1 {
msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1)
}
ext.Error.Set(span, true)
span.SetTag("graphql.error", msg)
}
span.Finish()
}
}

func noop(*errors.QueryError) {}

type NoopTracer struct{}
Expand Down
8 changes: 8 additions & 0 deletions trace/validation_trace.go
@@ -1,17 +1,25 @@
package trace

import (
"context"

"github.com/graph-gophers/graphql-go/errors"
)

type TraceValidationFinishFunc = TraceQueryFinishFunc

// Deprecated: use ValidationTracerContext.
type ValidationTracer interface {
TraceValidation() TraceValidationFinishFunc
}

type ValidationTracerContext interface {
TraceValidation(ctx context.Context) TraceValidationFinishFunc
}

type NoopValidationTracer struct{}

// Deprecated: use a Tracer which implements ValidationTracerContext.
func (NoopValidationTracer) TraceValidation() TraceValidationFinishFunc {
return func(errs []*errors.QueryError) {}
}

0 comments on commit 1a55b96

Please sign in to comment.