From fc5ab1daa8d74fd05fc3f76457d560857c38983b Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 14 Dec 2021 09:04:15 -0800 Subject: [PATCH] Add github.com/graph-gophers/graphql-go instrumentation (#232) * Initial splunkgraphql module layout * Initial instrumentation * Add error_test * Add start to integration tests * Test span attributes * Rename tags.go to attribute.go Match OTel nomenclature. * Add changes to changelog * Fix lint issues --- .github/dependabot.yml | 8 ++ CHANGELOG.md | 3 + README.md | 1 + .../graphql-go/splunkgraphql/README.md | 12 ++ .../graphql-go/splunkgraphql/example_test.go | 51 +++++++ .../graphql-go/splunkgraphql/go.mod | 12 ++ .../graphql-go/splunkgraphql/go.sum | 32 +++++ .../graphql-go/splunkgraphql/graphql.go | 115 +++++++++++++++ .../splunkgraphql/internal/attribute.go | 26 ++++ .../graphql-go/splunkgraphql/option.go | 55 +++++++ .../graphql-go/splunkgraphql/test/go.mod | 16 +++ .../graphql-go/splunkgraphql/test/go.sum | 36 +++++ .../splunkgraphql/test/graphql_test.go | 134 ++++++++++++++++++ 13 files changed, 501 insertions(+) create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/README.md create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/example_test.go create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.sum create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/graphql.go create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal/attribute.go create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/option.go create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.mod create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.sum create mode 100644 instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/graphql_test.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5987fbca7..c36e0d2ad 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -46,6 +46,14 @@ updates: directory: "/instrumentation/github.com/go-sql-driver/mysql/splunkmysql/test" schedule: interval: "daily" + - package-ecosystem: "gomod" + directory: "/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql" + schedule: + interval: "daily" + - package-ecosystem: "gomod" + directory: "/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test" + schedule: + interval: "daily" - package-ecosystem: "gomod" directory: "/instrumentation/github.com/jackc/pgx/splunkpgx" schedule: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce1e63e5..b4b65c7d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add the `github.com/signalfx/splunk-otel-go/instrumentation/github.com/go-chi/chi/splunkchi` instrumentation for the `github.com/go-chi/chi` package. (#227) +- Add the + `github.com/signalfx/splunk-otel-go/instrumentation/graphql-gophers/graphql-go/splunkgraphql` + instrumentation for the `github.com/graph-gophers/graphql-go` module. (#232) ### Changed diff --git a/README.md b/README.md index ec1af8a15..52619c46e 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Additional recommended Splunk specific instrumentations: - [`splunkclient-go`](./instrumentation/k8s.io/client-go/splunkclient-go) - [`splunkdns`](./instrumentation/github.com/miekg/dns/splunkdns) - [`splunkgorm`](./instrumentation/github.com/jinzhu/gorm/splunkgorm) +- [`splunkgraphql`](./instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql) - [`splunkhttp`](./instrumentation/net/http/splunkhttp) - [`splunkkafka`](./instrumentation/github.com/confluentinc/confluent-kafka-go/kafka/splunkkafka) - [`splunkleveldb`](./instrumentation/github.com/syndtr/goleveldb/leveldb/splunkleveldb) diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/README.md b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/README.md new file mode 100644 index 000000000..47e95a9af --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/README.md @@ -0,0 +1,12 @@ +# Splunk instrumentation for `github.com/graph-gophers/graphql-go` + +module github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql + +This package provides OpenTelemetry instrumentation for the +[graphql](https://github.com/graph-gophers/graphql-go) package. + +## Getting Started + +This package provides an implementation of the `graphql.Tracer` that can be +used to trace `graphql` operations. See [example_test.go](./example_test.go) +for more information. diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/example_test.go b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/example_test.go new file mode 100644 index 000000000..fa8def58e --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/example_test.go @@ -0,0 +1,51 @@ +// 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_test + +import ( + "net/http" + + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" + + "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql" +) + +const schema = ` + schema { + query: Query + } + type Query { + hello: String! + } +` + +type resolver struct{} + +func (*resolver) Hello() string { return "Hello, world!" } + +func Example() { + tracer := graphql.Tracer(splunkgraphql.NewTracer()) + s := graphql.MustParseSchema(schema, new(resolver), tracer) + http.Handle("/query", &relay.Handler{Schema: s}) + + /* + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } + + ... + */ +} diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod new file mode 100644 index 000000000..7ac976a20 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.mod @@ -0,0 +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.3.0 + go.opentelemetry.io/otel/trace v1.3.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 000000000..871ef21be --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/go.sum @@ -0,0 +1,32 @@ +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/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +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/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= +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/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/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= +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/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 000000000..470da71e8 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/graphql.go @@ -0,0 +1,115 @@ +// 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" + "go.opentelemetry.io/otel/codes" + oteltrace "go.opentelemetry.io/otel/trace" + + gql "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal" + "github.com/signalfx/splunk-otel-go/instrumentation/internal" +) + +const instrumentationName = "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql" + +// otelTracer implements the graphql-go/trace.Tracer interface using +// OpenTelemetry. +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) + } + switch n := len(errs); n { + case 0: + // Nothing to do. + case 1: + span.SetStatus(codes.Error, errs[0].Error()) + default: + msg := fmt.Sprintf("%s (and %d more errors)", errs[0], n-1) + span.SetStatus(codes.Error, msg) + } + span.End() + } +} + +// TraceQuery traces a GraphQL query. +func (t *otelTracer) TraceQuery(ctx context.Context, queryString, _ string, _ map[string]interface{}, _ map[string]*introspection.Type) (context.Context, trace.TraceQueryFinishFunc) { + spanCtx, span := t.cfg.ResolveTracer(ctx).Start( + ctx, + "GraphQL request", + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + oteltrace.WithAttributes(gql.GraphQLQueryKey.String(queryString)), + ) + + return spanCtx, traceQueryFinishFunc(span) +} + +// TraceField traces a GraphQL field access. +func (t *otelTracer) TraceField(ctx context.Context, _, typeName, fieldName string, trivial bool, _ map[string]interface{}) (context.Context, trace.TraceFieldFinishFunc) { + if trivial { + return ctx, func(*errors.QueryError) {} + } + + spanCtx, span := t.cfg.ResolveTracer(ctx).Start( + ctx, + "GraphQL field", + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + oteltrace.WithAttributes( + gql.GraphQLFieldKey.String(fieldName), + gql.GraphQLTypeKey.String(typeName), + ), + ) + + 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 { + _, span := t.cfg.ResolveTracer(ctx).Start( + ctx, + "GraphQL validation", + oteltrace.WithSpanKind(oteltrace.SpanKindInternal), + ) + return traceQueryFinishFunc(span) +} diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal/attribute.go b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal/attribute.go new file mode 100644 index 000000000..599487f99 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal/attribute.go @@ -0,0 +1,26 @@ +// 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 internal provides common non-exported objects to the splunkgraphql +// and the splunkgraphql/test modules +package internal + +import "go.opentelemetry.io/otel/attribute" + +// GraphQL attributes. +var ( + GraphQLFieldKey = attribute.Key("graphql.field") + GraphQLQueryKey = attribute.Key("graphql.query") + GraphQLTypeKey = attribute.Key("graphql.type") +) 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 000000000..ec96f8995 --- /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)} +} diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.mod b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.mod new file mode 100644 index 000000000..5779579ef --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.mod @@ -0,0 +1,16 @@ +module github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test + +go 1.16 + +require ( + github.com/graph-gophers/graphql-go v1.2.0 + github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.7.0 + go.opentelemetry.io/otel/sdk v1.3.0 + go.opentelemetry.io/otel/trace v1.3.0 +) + +replace ( + github.com/signalfx/splunk-otel-go => ../../../../../../ + github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql => ../ +) diff --git a/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.sum b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.sum new file mode 100644 index 000000000..d2d03bca5 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/go.sum @@ -0,0 +1,36 @@ +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/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +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/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= +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/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/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= +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/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= +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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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/test/graphql_test.go b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/graphql_test.go new file mode 100644 index 000000000..9f8f01d36 --- /dev/null +++ b/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/test/graphql_test.go @@ -0,0 +1,134 @@ +// 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 test provides end-to-end testing of the splunkgraphql instrumentation +with the default SDK. + +This package is in a separate module from the instrumentation it tests to +isolate the dependency of the default SDK and not impose this as a transitive +dependency for users. +*/ +package test + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + traceapi "go.opentelemetry.io/otel/trace" + + "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql" + "github.com/signalfx/splunk-otel-go/instrumentation/github.com/graph-gophers/graphql-go/splunkgraphql/internal" +) + +const testSchema = ` + schema { + query: Query + } + type Query { + hello: String! + helloNonTrivial: String! + } +` + +const helloWorld = "Hello, world!" + +type testResolver struct{} + +func (*testResolver) Hello() string { return helloWorld } +func (*testResolver) HelloNonTrivial() (string, error) { return helloWorld, nil } + +func fixtures(t *testing.T) (*tracetest.SpanRecorder, *httptest.Server) { + sr := tracetest.NewSpanRecorder() + tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) + t.Cleanup(func() { require.NoError(t, tp.Shutdown(context.Background())) }) + + tracer := graphql.Tracer(splunkgraphql.NewTracer(splunkgraphql.WithTracerProvider(tp))) + schema := graphql.MustParseSchema(testSchema, new(testResolver), tracer) + srv := httptest.NewServer(&relay.Handler{Schema: schema}) + t.Cleanup(srv.Close) + + return sr, srv +} + +func TestTracerNonTrivial(t *testing.T) { + sr, srv := fixtures(t) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, srv.URL, strings.NewReader(`{ + "query": "query TestQuery() { hello, helloNonTrivial }", + "operationName": "TestQuery" + }`)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, `{"data":{"hello":"Hello, world!","helloNonTrivial":"Hello, world!"}}`, string(body)) + + spans := sr.Ended() + require.Len(t, spans, 3) + assert.Equal(t, spans[2].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) + + s := spans[0] + assert.Equal(t, "GraphQL validation", s.Name()) + assert.Equal(t, traceapi.SpanKindInternal, s.SpanKind()) + + s = spans[1] + assert.Equal(t, "GraphQL field", s.Name()) + assert.Equal(t, traceapi.SpanKindServer, s.SpanKind()) + assert.Contains(t, s.Attributes(), internal.GraphQLFieldKey.String("helloNonTrivial")) + assert.Contains(t, s.Attributes(), internal.GraphQLTypeKey.String("Query")) + + s = spans[2] + assert.Equal(t, "GraphQL request", s.Name()) + assert.Equal(t, traceapi.SpanKindServer, s.SpanKind()) + assert.Contains(t, s.Attributes(), internal.GraphQLQueryKey.String( + "query TestQuery() { hello, helloNonTrivial }", + )) +} + +func TestTracerTrivial(t *testing.T) { + sr, srv := fixtures(t) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, srv.URL, strings.NewReader(`{ + "query": "{ hello }" + }`)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, `{"data":{"hello":"Hello, world!"}}`, string(body)) + + spans := sr.Ended() + require.Len(t, spans, 2) + + // Trivial queries should not trace a field access. + s0, s1 := spans[0], spans[1] + assert.Equal(t, "GraphQL validation", s0.Name()) + assert.Equal(t, "GraphQL request", s1.Name()) + assert.Contains(t, s1.Attributes(), internal.GraphQLQueryKey.String("{ hello }")) +}