diff --git a/go.mod b/go.mod index 2c814b0b..2873065c 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,10 @@ module github.com/graph-gophers/graphql-go -require github.com/opentracing/opentracing-go v1.1.0 +require ( + github.com/go-logr/stdr v1.2.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 + go.opentelemetry.io/otel v1.3.0 + go.opentelemetry.io/otel/trace v1.3.0 +) go 1.13 diff --git a/go.sum b/go.sum index 71fd021b..fd8a1af5 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,24 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/trace/opentel/opentelemetry_tracer.go b/trace/opentel/opentelemetry_tracer.go new file mode 100644 index 00000000..55d11c0d --- /dev/null +++ b/trace/opentel/opentelemetry_tracer.go @@ -0,0 +1,92 @@ +package trace + +import ( + "context" + "fmt" + + "github.com/graph-gophers/graphql-go/errors" + "github.com/graph-gophers/graphql-go/introspection" + "github.com/graph-gophers/graphql-go/trace" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + oteltrace "go.opentelemetry.io/otel/trace" +) + +// DefaultOpenTelemetryTracer creates a tracer using a default name +func DefaultOpenTelemetryTracer() trace.Tracer { + return &OpenTelemetryTracer{ + Tracer: otel.Tracer("graphql-go"), + } +} + +// OpenTelemetryTracer is an OpenTelemetry implementation for graphql-go. Set the Tracer +// property to your tracer instance as required. +type OpenTelemetryTracer struct { + Tracer oteltrace.Tracer +} + +func (t *OpenTelemetryTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, trace.TraceQueryFinishFunc) { + spanCtx, span := t.Tracer.Start(ctx, "GraphQL Request") + + var attributes []attribute.KeyValue + attributes = append(attributes, attribute.String("graphql.query", queryString)) + if operationName != "" { + attributes = append(attributes, attribute.String("graphql.operationName", operationName)) + } + if len(variables) != 0 { + attributes = append(attributes, attribute.String("graphql.variables", fmt.Sprintf("%v", variables))) + } + span.SetAttributes(attributes...) + + 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) + } + + span.SetStatus(codes.Error, msg) + } + span.End() + } +} + +func (t *OpenTelemetryTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, trace.TraceFieldFinishFunc) { + if trivial { + return ctx, func(*errors.QueryError) {} + } + + var attributes []attribute.KeyValue + + spanCtx, span := t.Tracer.Start(ctx, fmt.Sprintf("Field: %v", label)) + attributes = append(attributes, attribute.String("graphql.type", typeName)) + attributes = append(attributes, attribute.String("graphql.field", fieldName)) + for name, value := range args { + attributes = append(attributes, attribute.String("graphql.args."+name, fmt.Sprintf("%v", value))) + } + span.SetAttributes(attributes...) + + return spanCtx, func(err *errors.QueryError) { + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + span.End() + } +} + +func (t *OpenTelemetryTracer) TraceValidation(ctx context.Context) trace.TraceValidationFinishFunc { + _, span := t.Tracer.Start(ctx, "GraphQL Validate") + + 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) + } + span.SetStatus(codes.Error, msg) + } + span.End() + } +}