Skip to content

Opt-in tracing support #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 60 additions & 40 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type ExecuteParams struct {
// Context may be provided to pass application-specific per-request
// information to resolve functions.
Context context.Context

// Tracer ...
Tracer Tracer
}

func Execute(p ExecuteParams) (result *Result) {
Expand All @@ -30,6 +33,10 @@ func Execute(p ExecuteParams) (result *Result) {
ctx = context.Background()
}

if p.Tracer == nil {
p.Tracer = NoopTracer{}
}

resultChannel := make(chan *Result)
result = &Result{}

Expand All @@ -51,6 +58,7 @@ func Execute(p ExecuteParams) (result *Result) {
Args: p.Args,
Result: result,
Context: p.Context,
Tracer: p.Tracer,
})

if err != nil {
Expand Down Expand Up @@ -82,16 +90,18 @@ type buildExecutionCtxParams struct {
Args map[string]interface{}
Result *Result
Context context.Context
Tracer Tracer
}

type executionContext struct {
Schema Schema
Context context.Context
Errors []gqlerrors.FormattedError
Fragments map[string]ast.Definition
Root interface{}
Operation ast.Definition
Root interface{}
Schema Schema
Tracer Tracer
VariableValues map[string]interface{}
Errors []gqlerrors.FormattedError
Context context.Context
}

func buildExecutionContext(p buildExecutionCtxParams) (*executionContext, error) {
Expand Down Expand Up @@ -137,6 +147,7 @@ func buildExecutionContext(p buildExecutionCtxParams) (*executionContext, error)
eCtx.Operation = operation
eCtx.VariableValues = variableValues
eCtx.Context = p.Context
eCtx.Tracer = p.Tracer
return eCtx, nil
}

Expand Down Expand Up @@ -239,7 +250,7 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
finalResults := make(map[string]interface{}, len(p.Fields))
for responseName, fieldASTs := range p.Fields {
fieldPath := p.Path.WithKey(responseName)
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath)
resolved, state := resolveField(p.ExecutionContext.Context, p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath)
if state.hasNoFieldDefs {
continue
}
Expand All @@ -255,7 +266,7 @@ func executeFieldsSerially(p executeFieldsParams) *Result {

// Implements the "Evaluating selection sets" section of the spec for "read" mode.
func executeFields(p executeFieldsParams) *Result {
finalResults := executeSubFields(p)
finalResults := executeSubFields(p.ExecutionContext.Context, p)

dethunkMapWithBreadthFirstTraversal(finalResults)

Expand All @@ -265,7 +276,7 @@ func executeFields(p executeFieldsParams) *Result {
}
}

func executeSubFields(p executeFieldsParams) map[string]interface{} {
func executeSubFields(ctx context.Context, p executeFieldsParams) map[string]interface{} {
if p.Source == nil {
p.Source = map[string]interface{}{}
}
Expand All @@ -276,7 +287,9 @@ func executeSubFields(p executeFieldsParams) map[string]interface{} {
finalResults := make(map[string]interface{}, len(p.Fields))
for responseName, fieldASTs := range p.Fields {
fieldPath := p.Path.WithKey(responseName)
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath)

resolved, state := resolveField(ctx, p.ExecutionContext, p.ParentType, p.Source,
fieldASTs, fieldPath)
if state.hasNoFieldDefs {
continue
}
Expand Down Expand Up @@ -572,17 +585,7 @@ func handleFieldError(r interface{}, fieldNodes []ast.Node, path *ResponsePath,
// figures out the value that the field returns by calling its resolve function,
// then calls completeValue to complete promises, serialize scalars, or execute
// the sub-selection-set for objects.
func resolveField(eCtx *executionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field, path *ResponsePath) (result interface{}, resultState resolveFieldResultState) {
// catch panic from resolveFn
var returnType Output
defer func() (interface{}, resolveFieldResultState) {
if r := recover(); r != nil {
handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx)
return result, resultState
}
return result, resultState
}()

func resolveField(ctx context.Context, eCtx *executionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field, path *ResponsePath) (result interface{}, resultState resolveFieldResultState) {
fieldAST := fieldASTs[0]
fieldName := ""
if fieldAST.Name != nil {
Expand All @@ -594,7 +597,22 @@ func resolveField(eCtx *executionContext, parentType *Object, source interface{}
resultState.hasNoFieldDefs = true
return nil, resultState
}
returnType = fieldDef.Type
returnType := fieldDef.Type

traceCtx, finishTraceField := eCtx.Tracer.TraceField(ctx, fieldName,
returnType.Name())

// catch panic from resolveFn
defer func() (interface{}, resolveFieldResultState) {
if r := recover(); r != nil {
handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx)
finishTraceField(eCtx.Errors)
return result, resultState
}
finishTraceField([]gqlerrors.FormattedError{})
return result, resultState
}()

resolveFn := fieldDef.Resolve
if resolveFn == nil {
resolveFn = DefaultResolveFn
Expand Down Expand Up @@ -631,11 +649,13 @@ func resolveField(eCtx *executionContext, parentType *Object, source interface{}
panic(resolveFnError)
}

completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, path, result)
completed := completeValueCatchingError(traceCtx, eCtx, returnType, fieldASTs,
info, path, result)

return completed, resultState
}

func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) {
func completeValueCatchingError(ctx context.Context, eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) {
// catch panic
defer func() interface{} {
if r := recover(); r != nil {
Expand All @@ -646,26 +666,26 @@ func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldAS
}()

if returnType, ok := returnType.(*NonNull); ok {
completed := completeValue(eCtx, returnType, fieldASTs, info, path, result)
completed := completeValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
return completed
}
completed = completeValue(eCtx, returnType, fieldASTs, info, path, result)
completed = completeValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
return completed
}

func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {
func completeValue(ctx context.Context, eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {

resultVal := reflect.ValueOf(result)
if resultVal.IsValid() && resultVal.Kind() == reflect.Func {
return func() interface{} {
return completeThunkValueCatchingError(eCtx, returnType, fieldASTs, info, path, result)
return completeThunkValueCatchingError(ctx, eCtx, returnType, fieldASTs, info, path, result)
}
}

// If field type is NonNull, complete for inner type, and throw field error
// if result is null.
if returnType, ok := returnType.(*NonNull); ok {
completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, path, result)
completed := completeValue(ctx, eCtx, returnType.OfType, fieldASTs, info, path, result)
if completed == nil {
err := NewLocatedErrorWithPath(
fmt.Sprintf("Cannot return null for non-nullable field %v.%v.", info.ParentType, info.FieldName),
Expand All @@ -684,7 +704,7 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie

// If field type is List, complete each item in the list with the inner type
if returnType, ok := returnType.(*List); ok {
return completeListValue(eCtx, returnType, fieldASTs, info, path, result)
return completeListValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
}

// If field type is a leaf type, Scalar or Enum, serialize to a valid value,
Expand All @@ -699,15 +719,15 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
// If field type is an abstract type, Interface or Union, determine the
// runtime Object type and complete for that type.
if returnType, ok := returnType.(*Union); ok {
return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result)
return completeAbstractValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
}
if returnType, ok := returnType.(*Interface); ok {
return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result)
return completeAbstractValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
}

// If field type is Object, execute and complete all sub-selections.
if returnType, ok := returnType.(*Object); ok {
return completeObjectValue(eCtx, returnType, fieldASTs, info, path, result)
return completeObjectValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
}

// Not reachable. All possible output types have been considered.
Expand All @@ -720,7 +740,7 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
return nil
}

func completeThunkValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) {
func completeThunkValueCatchingError(ctx context.Context, eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) (completed interface{}) {

// catch any panic invoked from the propertyFn (thunk)
defer func() {
Expand All @@ -742,17 +762,17 @@ func completeThunkValueCatchingError(eCtx *executionContext, returnType Type, fi
result = fnResult

if returnType, ok := returnType.(*NonNull); ok {
completed := completeValue(eCtx, returnType, fieldASTs, info, path, result)
completed := completeValue(ctx, eCtx, returnType, fieldASTs, info, path, result)
return completed
}
completed = completeValue(eCtx, returnType, fieldASTs, info, path, result)
completed = completeValue(ctx, eCtx, returnType, fieldASTs, info, path, result)

return completed
}

// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
// of that value, then completing based on that type.
func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {
func completeAbstractValue(ctx context.Context, eCtx *executionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {

var runtimeType *Object

Expand Down Expand Up @@ -785,11 +805,11 @@ func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldAST
))
}

return completeObjectValue(eCtx, runtimeType, fieldASTs, info, path, result)
return completeObjectValue(ctx, eCtx, runtimeType, fieldASTs, info, path, result)
}

// completeObjectValue complete an Object value by executing all sub-selections.
func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {
func completeObjectValue(ctx context.Context, eCtx *executionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {

// If there is an isTypeOf predicate function, call it with the
// current result. If isTypeOf returns false, then raise an error rather
Expand Down Expand Up @@ -833,7 +853,7 @@ func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs [
Fields: subFieldASTs,
Path: path,
}
return executeSubFields(executeFieldsParams)
return executeSubFields(ctx, executeFieldsParams)
}

// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.
Expand All @@ -846,7 +866,7 @@ func completeLeafValue(returnType Leaf, result interface{}) interface{} {
}

// completeListValue complete a list value by completing each item in the list with the inner type
func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {
func completeListValue(ctx context.Context, eCtx *executionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, path *ResponsePath, result interface{}) interface{} {
resultVal := reflect.ValueOf(result)
if resultVal.Kind() == reflect.Ptr {
resultVal = resultVal.Elem()
Expand All @@ -869,7 +889,7 @@ func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*as
for i := 0; i < resultVal.Len(); i++ {
val := resultVal.Index(i).Interface()
fieldPath := path.WithKey(i)
completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, fieldPath, val)
completedItem := completeValueCatchingError(ctx, eCtx, itemType, fieldASTs, info, fieldPath, val)
completedResults = append(completedResults, completedItem)
}
return completedResults
Expand Down
18 changes: 16 additions & 2 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type Params struct {
// Context may be provided to pass application-specific per-request
// information to resolve functions.
Context context.Context

// Tracer ...
Tracer Tracer
}

func Do(p Params) *Result {
Expand All @@ -52,12 +55,23 @@ func Do(p Params) *Result {
}
}

return Execute(ExecuteParams{
if p.Tracer == nil {
p.Tracer = NoopTracer{}
}

ctx, traceFinish := p.Tracer.TraceQuery(p.Context, p.RequestString, p.OperationName)

result := Execute(ExecuteParams{
Schema: p.Schema,
Root: p.RootObject,
AST: AST,
OperationName: p.OperationName,
Args: p.VariableValues,
Context: p.Context,
Context: ctx,
Tracer: p.Tracer,
})

traceFinish(result.Errors)

return result
}
25 changes: 25 additions & 0 deletions trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package graphql

import (
"context"

"github.com/graphql-go/graphql/gqlerrors"
)

type TraceQueryFinishFunc func([]gqlerrors.FormattedError)
type TraceFieldFinishFunc func([]gqlerrors.FormattedError)

type Tracer interface {
TraceQuery(ctx context.Context, queryString string, operationName string) (context.Context, TraceQueryFinishFunc)
TraceField(ctx context.Context, fieldName string, typeName string) (context.Context, TraceFieldFinishFunc)
}

type NoopTracer struct{}

func (NoopTracer) TraceQuery(ctx context.Context, queryString string, operationName string) (context.Context, TraceQueryFinishFunc) {
return ctx, func(errs []gqlerrors.FormattedError) {}
}

func (NoopTracer) TraceField(ctx context.Context, fieldName string, typeName string) (context.Context, TraceFieldFinishFunc) {
return ctx, func(errs []gqlerrors.FormattedError) {}
}