Skip to content

Commit

Permalink
pluggable tracer
Browse files Browse the repository at this point in the history
Improved performance while keeping flexibility.
  • Loading branch information
neelance committed Mar 24, 2017
1 parent 58d3d5b commit a992060
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 67 deletions.
54 changes: 16 additions & 38 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,15 @@ import (
"encoding/json"
"fmt"

opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"

"github.com/neelance/graphql-go/errors"
"github.com/neelance/graphql-go/internal/exec"
"github.com/neelance/graphql-go/internal/query"
"github.com/neelance/graphql-go/internal/schema"
"github.com/neelance/graphql-go/internal/validation"
"github.com/neelance/graphql-go/introspection"
"github.com/neelance/graphql-go/trace"
)

const OpenTracingTagQuery = "graphql.query"
const OpenTracingTagOperationName = "graphql.operationName"
const OpenTracingTagVariables = "graphql.variables"

const OpenTracingTagType = "graphql.type"
const OpenTracingTagField = "graphql.field"
const OpenTracingTagTrivial = "graphql.trivial"
const OpenTracingTagArgsPrefix = "graphql.args."
const OpenTracingTagError = "graphql.error"

// ID represents GraphQL's "ID" type. A custom type may be used instead.
type ID string

Expand All @@ -50,6 +38,7 @@ func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) {
s := &Schema{
schema: schema.New(),
MaxParallelism: 10,
Tracer: trace.OpenTracingTracer{},
}
if err := s.schema.Parse(schemaString); err != nil {
return nil, err
Expand Down Expand Up @@ -82,6 +71,9 @@ type Schema struct {

// MaxParallelism specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
MaxParallelism int

// Tracer is used to trace queries and fields. It defaults to trace.OpenTracingTracer.
Tracer trace.Tracer
}

// Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or
Expand All @@ -100,37 +92,22 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str
panic("schema created without resolver, can not exec")
}

document, err := query.Parse(queryString)
if err != nil {
return &Response{
Errors: []*errors.QueryError{err},
}
}

span, spanCtx := opentracing.StartSpanFromContext(ctx, "GraphQL request")
span.SetTag(OpenTracingTagQuery, queryString)
if operationName != "" {
span.SetTag(OpenTracingTagOperationName, operationName)
}
if len(variables) != 0 {
span.SetTag(OpenTracingTagVariables, variables)
}
defer span.Finish()

data, errs := s.doExec(spanCtx, document, operationName, variables)

if len(errs) != 0 {
ext.Error.Set(span, true)
span.SetTag(OpenTracingTagError, errs)
}
traceCtx, finish := s.Tracer.TraceQuery(ctx, queryString, operationName, variables)
data, errs := s.doExec(traceCtx, queryString, operationName, variables)
finish(errs)

return &Response{
Data: data,
Errors: errs,
}
}

func (s *Schema) doExec(ctx context.Context, doc *query.Document, operationName string, vars map[string]interface{}) (interface{}, []*errors.QueryError) {
func (s *Schema) doExec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (interface{}, []*errors.QueryError) {
doc, qErr := query.Parse(queryString)
if qErr != nil {
return nil, []*errors.QueryError{qErr}
}

errs := validation.Validate(s.schema, doc)
if len(errs) != 0 {
return nil, errs
Expand All @@ -143,9 +120,10 @@ func (s *Schema) doExec(ctx context.Context, doc *query.Document, operationName

r := &exec.Request{
Doc: doc,
Vars: vars,
Vars: variables,
Schema: s.schema,
Limiter: make(chan struct{}, s.MaxParallelism),
Tracer: s.Tracer,
}
return r.Execute(ctx, s.exec, op)
}
Expand Down
44 changes: 15 additions & 29 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,14 @@ import (
"strings"
"sync"

"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"

"github.com/neelance/graphql-go/errors"
"github.com/neelance/graphql-go/internal/common"
"github.com/neelance/graphql-go/internal/lexer"
"github.com/neelance/graphql-go/internal/query"
"github.com/neelance/graphql-go/internal/schema"
"github.com/neelance/graphql-go/trace"
)

// keep in sync with main package
const OpenTracingTagType = "graphql.type"
const OpenTracingTagField = "graphql.field"
const OpenTracingTagTrivial = "graphql.trivial"
const OpenTracingTagArgsPrefix = "graphql.args."
const OpenTracingTagError = "graphql.error"

type Exec struct {
queryExec iExec
mutationExec iExec
Expand Down Expand Up @@ -309,7 +300,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
argsPacker: argsPacker,
hasError: hasError,
trivial: !hasContext && argsPacker == nil && !hasError,
spanLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
traceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
}
if err := b.assignExec(&fe.valueExec, f.Type, m.Type.Out(0)); err != nil {
return nil, err
Expand All @@ -331,6 +322,7 @@ type Request struct {
Vars map[string]interface{}
Schema *schema.Schema
Limiter chan struct{}
Tracer trace.Tracer
wg sync.WaitGroup
mu sync.Mutex
errs []*errors.QueryError
Expand Down Expand Up @@ -439,7 +431,7 @@ type normalFieldExec struct {
hasError bool
trivial bool
valueExec iExec
spanLabel string
traceLabel string
}

type metaFieldExec func(ctx context.Context, r *Request, e *objectExec, f *query.Field, resolver reflect.Value) interface{}
Expand Down Expand Up @@ -543,32 +535,28 @@ func (fe *normalFieldExec) execField(ctx context.Context, r *Request, e *objectE
r.Limiter <- struct{}{}
}

span, spanCtx := opentracing.StartSpanFromContext(ctx, fe.spanLabel)
defer span.Finish()
span.SetTag(OpenTracingTagType, fe.typeName)
span.SetTag(OpenTracingTagField, fe.field.Name)
if fe.trivial {
span.SetTag(OpenTracingTagTrivial, true)
}
for name, value := range args {
span.SetTag(OpenTracingTagArgsPrefix+name, value)
}

var result reflect.Value
err := func() (err *errors.QueryError) {
var err *errors.QueryError

traceCtx, finish := r.Tracer.TraceField(ctx, fe.traceLabel, fe.typeName, fe.field.Name, fe.trivial, args)
defer func() {
finish(err)
}()

err = func() (err *errors.QueryError) {
defer func() {
if panicValue := recover(); panicValue != nil {
err = makePanicError(panicValue)
}
}()

if err := spanCtx.Err(); err != nil {
if err := traceCtx.Err(); err != nil {
return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled
}

var in []reflect.Value
if fe.hasContext {
in = append(in, reflect.ValueOf(spanCtx))
in = append(in, reflect.ValueOf(traceCtx))
}
if fe.argsPacker != nil {
in = append(in, packedArgs)
Expand All @@ -590,12 +578,10 @@ func (fe *normalFieldExec) execField(ctx context.Context, r *Request, e *objectE

if err != nil {
r.addError(err)
ext.Error.Set(span, true)
span.SetTag(OpenTracingTagError, err.Error())
return nil // TODO handle non-nil
}

return fe.valueExec.exec(spanCtx, r, f.SelSet, result)
return fe.valueExec.exec(traceCtx, r, f.SelSet, result)
}

if fe.trivial {
Expand Down
68 changes: 68 additions & 0 deletions trace/trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package trace

import (
"context"
"fmt"

"github.com/neelance/graphql-go/errors"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)

type TraceQueryFinishFunc func([]*errors.QueryError)
type TraceFieldFinishFunc func(*errors.QueryError)

type Tracer interface {
TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (context.Context, TraceQueryFinishFunc)
TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc)
}

type OpenTracingTracer struct{}

func (OpenTracingTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (context.Context, TraceQueryFinishFunc) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "GraphQL request")
span.SetTag("graphql.query", queryString)

if operationName != "" {
span.SetTag("graphql.operationName", operationName)
}

if len(variables) != 0 {
span.SetTag("graphql.variables", variables)
}

return spanCtx, 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 (OpenTracingTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc) {
if trivial {
return ctx, noop
}

span, spanCtx := opentracing.StartSpanFromContext(ctx, label)
span.SetTag("graphql.type", typeName)
span.SetTag("graphql.field", fieldName)
for name, value := range args {
span.SetTag("graphql.args."+name, value)
}

return spanCtx, func(err *errors.QueryError) {
if err != nil {
ext.Error.Set(span, true)
span.SetTag("graphql.error", err.Error())
}
span.Finish()
}
}

func noop(*errors.QueryError) {}

0 comments on commit a992060

Please sign in to comment.