Skip to content

Commit

Permalink
Merge pull request #102 from aereal/custom-error-unwrap
Browse files Browse the repository at this point in the history
feat: add WithErrorSelector option
  • Loading branch information
aereal authored Oct 25, 2024
2 parents 4bbf5a4 + b6ac596 commit 55dd454
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 10 deletions.
3 changes: 3 additions & 0 deletions internal/test/resolvers/main.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/test/resolvers/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ type ForbiddenError struct{}
func (ForbiddenError) Error() string {
return "forbidden"
}

type NotFoundError struct{}

func (NotFoundError) Error() string { return "not found" }
40 changes: 30 additions & 10 deletions tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type config struct {
tracerProvider trace.TracerProvider
complexityExtensionName string
traceStructFields bool
errorSelector ErrorSelector
}

type Option func(c *config)
Expand Down Expand Up @@ -55,6 +56,15 @@ func TraceStructFields(v bool) Option {
}
}

// ErrorSelector is a predicate that the error should be recorded.
//
// The span records only errors that the function returns true.
// If the function returns false against all of the errors in the gqlgen response, the span status will be Unset instead of Error.
type ErrorSelector func(err error) bool

// WithErrorSelector creates an Option that tells Tracer uses the given selector.
func WithErrorSelector(fn ErrorSelector) Option { return func(c *config) { c.errorSelector = fn } }

// New returns a new Tracer with given options.
func New(opts ...Option) Tracer {
cfg := &config{}
Expand All @@ -68,10 +78,14 @@ func New(opts ...Option) Tracer {
tracer: cfg.tracerProvider.Tracer(tracerName),
complexityExtensionName: cfg.complexityExtensionName,
traceStructFields: cfg.traceStructFields,
errorSelector: cfg.errorSelector,
}
if t.complexityExtensionName == "" {
t.complexityExtensionName = defaultComplexityExtensionName
}
if t.errorSelector == nil {
t.errorSelector = func(_ error) bool { return true }
}
return t
}

Expand All @@ -80,6 +94,7 @@ type Tracer struct {
tracer trace.Tracer
complexityExtensionName string
traceStructFields bool
errorSelector ErrorSelector
}

var _ interface {
Expand Down Expand Up @@ -143,9 +158,9 @@ func (t Tracer) InterceptResponse(ctx context.Context, next graphql.ResponseHand
span.SetAttributes(attrs...)
resp := next(ctx)
if resp != nil && len(resp.Errors) > 0 {
recordGQLErrors(span, resp.Errors)
recordGQLErrors(span, resp.Errors, t.errorSelector)
if parentSpan.SpanContext().IsValid() {
recordGQLErrors(parentSpan, resp.Errors)
recordGQLErrors(parentSpan, resp.Errors, t.errorSelector)
}
}
return resp
Expand Down Expand Up @@ -203,7 +218,7 @@ func (t Tracer) InterceptField(ctx context.Context, next graphql.Resolver) (any,

resp, err := next(ctx)
if errs := graphql.GetFieldErrors(ctx, fieldCtx); len(errs) > 0 {
recordGQLErrors(span, errs)
recordGQLErrors(span, errs, t.errorSelector)
}
return resp, err
}
Expand All @@ -223,15 +238,20 @@ func operationName(ctx context.Context) string {
return string(op.Operation)
}

func recordGQLErrors(span trace.Span, errs gqlerror.List) {
span.SetStatus(codes.Error, errs.Error())
for _, e := range errs {
attrs := []attribute.KeyValue{
keyErrorPath.String(e.Path.String()),
func recordGQLErrors(span trace.Span, errs gqlerror.List, selector ErrorSelector) {
var recorded bool
for _, gqlErr := range errs {
if !selector(gqlErr) {
continue
}
err := unwrapErr(e)
span.RecordError(err, trace.WithStackTrace(true), trace.WithAttributes(attrs...))
recorded = true
attrErrorPath := keyErrorPath.String(gqlErr.Path.String())
span.RecordError(unwrapErr(gqlErr), trace.WithStackTrace(true), trace.WithAttributes(attrErrorPath))
}
if !recorded {
return
}
span.SetStatus(codes.Error, errs.Error())
}

func unwrapErr(err error) error {
Expand Down
46 changes: 46 additions & 0 deletions tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -310,6 +311,51 @@ func TestTracer(t *testing.T) {
},
},
},
{
name: "error that must be ignored from root field",
options: []otelgqlgen.Option{
otelgqlgen.WithErrorSelector(func(err error) bool {
return !errors.Is(err, &resolvers.NotFoundError{})
}),
},
params: &graphql.RawParams{
Query: `query($name: String!) {user(name: $name) {name}}`,
Variables: map[string]any{"name": "not_found"},
},
spans: tracetest.SpanStubs{
{Name: "read", SpanKind: trace.SpanKindServer},
{Name: "parsing", SpanKind: trace.SpanKindServer},
{Name: "validation", SpanKind: trace.SpanKindServer},
{
Name: "Query/user",
SpanKind: trace.SpanKindServer,
Attributes: []attribute.KeyValue{
attribute.String("graphql.resolver.object", "Query"),
attribute.String("graphql.resolver.field", "user"),
attribute.String("graphql.resolver.alias", "user"),
attribute.String("graphql.resolver.args.name", "$name"),
attribute.String("graphql.resolver.path", "user"),
attribute.Bool("graphql.resolver.is_method", true),
attribute.Bool("graphql.resolver.is_resolver", true),
},
},
{
Name: "query",
SpanKind: trace.SpanKindServer,
Attributes: []attribute.KeyValue{
attribute.String("graphql.operation.name", "query"),
attribute.String("graphql.operation.type", "query"),
attribute.String("graphql.operation.variables.name", "not_found"),
attribute.Int("graphql.operation.complexity.limit", 1000),
attribute.Int("graphql.operation.complexity.calculated", 2),
},
},
{
Name: "http_handler",
SpanKind: trace.SpanKindInternal,
},
},
},
{
name: "error from edge fields",
params: &graphql.RawParams{
Expand Down

0 comments on commit 55dd454

Please sign in to comment.