diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod index b9f9f74110..34f7593557 100644 --- a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod @@ -1,3 +1,12 @@ module github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql go 1.16 + +require ( + github.com/graph-gophers/graphql-go v1.2.0 + github.com/signalfx/splunk-otel-go v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/trace v1.2.0 +) + +replace github.com/signalfx/splunk-otel-go => ../../../../../ diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.sum b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.sum new file mode 100644 index 0000000000..52392b26e9 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.sum @@ -0,0 +1,25 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/graph-gophers/graphql-go v1.2.0 h1:j3tCG0UcE+3f84OAw/4/6YQKyTr+r0yuUKtnxiu5OH4= +github.com/graph-gophers/graphql-go v1.2.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/contrib/propagators/b3 v1.2.0/go.mod h1:kO8hNKCfa1YmQJ0lM7pzfJGvbXEipn/S7afbOfaw2Kc= +go.opentelemetry.io/otel v1.2.0 h1:YOQDvxO1FayUcT9MIhJhgMyNO1WqoduiyvQHzGN0kUQ= +go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= +go.opentelemetry.io/otel/exporters/jaeger v1.2.0/go.mod h1:KJLFbEMKTNPIfOxcg/WikIozEoKcPgJRz3Ce1vLlM8E= +go.opentelemetry.io/otel/sdk v1.2.0/go.mod h1:jNN8QtpvbsKhgaC6V5lHiejMoKD+V8uadoSafgHPx1U= +go.opentelemetry.io/otel/trace v1.2.0 h1:Ys3iqbqZhcf28hHzrm5WAquMkDHNZTUkw7KHbuNjej0= +go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/graphql.go b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/graphql.go new file mode 100644 index 0000000000..175eee8eaf --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/graphql.go @@ -0,0 +1,106 @@ +// Copyright Splunk Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package splunkgraphql provides OpenTelemetry instrumentation for the +// github.com/graph-gophers/graphql-go module. +package splunkgraphql + +import ( + "context" + "fmt" + + "github.com/graph-gophers/graphql-go/errors" + "github.com/graph-gophers/graphql-go/introspection" + "github.com/graph-gophers/graphql-go/trace" + "github.com/signalfx/splunk-otel-go/instrumentation/internal" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + oteltrace "go.opentelemetry.io/otel/trace" +) + +const instrumentationName = "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql" + +type otelTracer struct { + cfg internal.Config +} + +var ( + _ trace.Tracer = (*otelTracer)(nil) + _ trace.ValidationTracerContext = (*otelTracer)(nil) +) + +// NewTracer returns a new trace.Tracer backed by OpenTelemetry. +func NewTracer(opts ...Option) trace.Tracer { + cfg := internal.NewConfig(instrumentationName, localToInternal(opts)...) + return &otelTracer{cfg: *cfg} +} + +func traceQueryFinishFunc(span oteltrace.Span) trace.TraceQueryFinishFunc { + return func(errs []*errors.QueryError) { + for _, err := range errs { + span.RecordError(err) + } + if len(errs) > 0 { + msg := fmt.Sprintf("%d validation errors", len(errs)) + span.SetStatus(codes.Error, msg) + } + span.End() + } +} + +// TraceQuery traces a GraphQL query. +func (t *otelTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, trace.TraceQueryFinishFunc) { + tracer := t.cfg.ResolveTracer(ctx) + spanCtx, span := tracer.Start(ctx, "GraphQL request", + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + ) + span.SetAttributes(attribute.String("trace.operation", "request")) + span.SetAttributes(attribute.String("graphql.query", queryString)) + + if operationName != "" { + span.SetAttributes(attribute.String("graphql.operationName", operationName)) + } + + return spanCtx, traceQueryFinishFunc(span) +} + +// TraceField traces a GraphQL field access. +func (t *otelTracer) 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) {} + } + + tracer := t.cfg.ResolveTracer(ctx) + spanCtx, span := tracer.Start(ctx, label) + span.SetAttributes(attribute.String("trace.operation", "field")) + span.SetAttributes(attribute.String("graphql.type", typeName)) + span.SetAttributes(attribute.String("graphql.field", fieldName)) + + return spanCtx, func(err *errors.QueryError) { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + span.End() + } +} + +// TraceValidation traces the schema validation step preceding an operation. +func (t *otelTracer) TraceValidation(ctx context.Context) trace.TraceValidationFinishFunc { + tracer := t.cfg.ResolveTracer(ctx) + _, span := tracer.Start(ctx, "Validate query") + span.SetAttributes(attribute.String("trace.operation", "validation")) + + return traceQueryFinishFunc(span) +} diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/option.go b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/option.go new file mode 100644 index 0000000000..ec96f89952 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/option.go @@ -0,0 +1,55 @@ +// Copyright Splunk Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package splunkgraphql + +import ( + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/signalfx/splunk-otel-go/instrumentation/internal" +) + +func localToInternal(opts []Option) []internal.Option { + out := make([]internal.Option, len(opts)) + for i, o := range opts { + out[i] = internal.OptionFunc(func(c *internal.Config) { o.apply(c) }) + } + return out +} + +// Option applies options to a configuration. +type Option interface { + apply(*internal.Config) +} + +type optionConv struct { + iOpt internal.Option +} + +func (o optionConv) apply(c *internal.Config) { + o.iOpt.Apply(c) +} + +// WithTracerProvider returns an Option that sets the TracerProvider used for +// a configuration. +func WithTracerProvider(tp trace.TracerProvider) Option { + return optionConv{iOpt: internal.WithTracerProvider(tp)} +} + +// WithAttributes returns an Option that appends attr to the attributes set +// for every span created. +func WithAttributes(attr []attribute.KeyValue) Option { + return optionConv{iOpt: internal.WithAttributes(attr)} +}