Skip to content

Commit

Permalink
http status code span attribute for graphql response
Browse files Browse the repository at this point in the history
  • Loading branch information
esara committed Jun 10, 2024
1 parent d547edf commit a11bc5c
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 6 deletions.
48 changes: 46 additions & 2 deletions gqlgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ package otelgqlgen
import (
"context"
"fmt"
"net/http"
"reflect"
"strconv"

"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler/extension"

otelcontrib "go.opentelemetry.io/contrib"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
oteltrace "go.opentelemetry.io/otel/trace"
)

Expand Down Expand Up @@ -96,11 +100,31 @@ func (a Tracer) InterceptResponse(ctx context.Context, next graphql.ResponseHand
}

resp := next(ctx)
var statusCode = http.StatusOK
if resp != nil && len(resp.Errors) > 0 {
statusCode = http.StatusInternalServerError
span.SetStatus(codes.Error, resp.Errors.Error())
span.RecordError(fmt.Errorf(resp.Errors.Error()))
span.SetAttributes(ResolverErrors(resp.Errors)...)
}
if resp.Errors[0].Extensions["code"] != nil {
var code = resp.Errors[0].Extensions["code"]
if reflect.TypeOf(code).Kind() == reflect.Int {
statusCode = code.(int)
} else if reflect.TypeOf(code).Kind() == reflect.Int32 {
statusCode = int(code.(int32))
} else if reflect.TypeOf(code).Kind() == reflect.String {
status, err := strconv.Atoi(code.(string))
if err == nil {
statusCode = status

Check warning on line 118 in gqlgen.go

View check run for this annotation

Codecov / codecov/patch

gqlgen.go#L110-L118

Added lines #L110 - L118 were not covered by tests
}
}
}
}

// Stable: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes
span.SetAttributes(semconv.HTTPResponseStatusCode(statusCode))
// Experimental: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md#common-attributes
span.SetAttributes(semconv.HTTPStatusCode(statusCode)) // nolint:staticcheck

return resp
}
Expand Down Expand Up @@ -131,11 +155,31 @@ func (a Tracer) InterceptField(ctx context.Context, next graphql.Resolver) (inte
resp, err := next(ctx)

errList := graphql.GetFieldErrors(ctx, fc)
var statusCode = http.StatusOK
if len(errList) != 0 {
statusCode = http.StatusInternalServerError
span.SetStatus(codes.Error, errList.Error())
span.RecordError(fmt.Errorf(errList.Error()))
span.SetAttributes(ResolverErrors(errList)...)
}
if errList[0].Extensions["code"] != nil {
var code = errList[0].Extensions["code"]
if reflect.TypeOf(code).Kind() == reflect.Int {
statusCode = code.(int)
} else if reflect.TypeOf(code).Kind() == reflect.Int32 {
statusCode = int(code.(int32))
} else if reflect.TypeOf(code).Kind() == reflect.String {
status, err := strconv.Atoi(code.(string))
if err == nil {
statusCode = status

Check warning on line 173 in gqlgen.go

View check run for this annotation

Codecov / codecov/patch

gqlgen.go#L165-L173

Added lines #L165 - L173 were not covered by tests
}
}
}
}

// Stable: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes
span.SetAttributes(semconv.HTTPResponseStatusCode(statusCode))
// Experimental: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md#common-attributes
span.SetAttributes(semconv.HTTPStatusCode(statusCode)) // nolint:staticcheck

return resp, err
}
Expand Down
11 changes: 7 additions & 4 deletions gqlgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
)

Expand Down Expand Up @@ -376,9 +377,10 @@ func TestVariablesAttributes(t *testing.T) {

testSpans(t, spanRecorder, namelessQueryName, codes.Unset)
spans := spanRecorder.Ended()
assert.Len(t, spans[1].Attributes(), 2)
assert.Len(t, spans[1].Attributes(), 4)
assert.Equal(t, attribute.Key("gql.request.query"), spans[1].Attributes()[0].Key)
assert.Equal(t, attribute.Key("gql.request.variables.id"), spans[1].Attributes()[1].Key)
assert.Equal(t, semconv.HTTPResponseStatusCodeKey, spans[1].Attributes()[2].Key)

assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
}
Expand Down Expand Up @@ -414,9 +416,10 @@ func TestVariablesAttributesCustomBuilder(t *testing.T) {

testSpans(t, spanRecorder, namelessQueryName, codes.Unset)
spans := spanRecorder.Ended()
assert.Len(t, spans[1].Attributes(), 2)
assert.Len(t, spans[1].Attributes(), 4)
assert.Equal(t, attribute.Key("gql.request.query"), spans[1].Attributes()[0].Key)
assert.Equal(t, attribute.Key("id"), spans[1].Attributes()[1].Key)
assert.Equal(t, semconv.HTTPResponseStatusCodeKey, spans[1].Attributes()[2].Key)

assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
}
Expand Down Expand Up @@ -444,9 +447,9 @@ func TestVariablesAttributesDisabled(t *testing.T) {

testSpans(t, spanRecorder, namelessQueryName, codes.Unset)
spans := spanRecorder.Ended()
assert.Len(t, spans[1].Attributes(), 1)
assert.Len(t, spans[1].Attributes(), 3)
assert.Equal(t, attribute.Key("gql.request.query"), spans[1].Attributes()[0].Key)

assert.Equal(t, semconv.HTTPResponseStatusCodeKey, spans[1].Attributes()[1].Key) // nolint:staticcheck
assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
}

Expand Down

0 comments on commit a11bc5c

Please sign in to comment.