diff --git a/router-tests/go.mod b/router-tests/go.mod index 09abf7cf33..d3940063ef 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -26,7 +26,7 @@ require ( github.com/wundergraph/cosmo/demo/pkg/subgraphs/projects v0.0.0-20250715110703-10f2e5f9c79e github.com/wundergraph/cosmo/router v0.0.0-20251125205644-175f80c4e6d9 github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e - github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240 + github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241 go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel/sdk v1.36.0 go.opentelemetry.io/otel/sdk/metric v1.36.0 diff --git a/router-tests/go.sum b/router-tests/go.sum index 7360bf44c2..23ed1909fb 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -352,8 +352,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk= github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240 h1:xnYwsUrmDcQnrZQ4+WZ8oODktsKJyAJWiYplWfmiQuk= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241 h1:ch/8hfDaw4oz1Cx3Wb+OUl4qiAo17OdGhYMdRYnX8Is= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/router-tests/telemetry/telemetry_test.go b/router-tests/telemetry/telemetry_test.go index 4e498a32b9..7da39399da 100644 --- a/router-tests/telemetry/telemetry_test.go +++ b/router-tests/telemetry/telemetry_test.go @@ -11254,6 +11254,139 @@ func TestFlakyTelemetry(t *testing.T) { }) }) + t.Run("verify errors being attached to unrelated span subgraphs", func(t *testing.T) { + simulateConnectionFailureOnClose := func(w http.ResponseWriter) { + hj, ok := w.(http.Hijacker) + if !ok { + // If the hijacker is not available, we switch to panic + // to simulate a failure + panic("service failure") + } + conn, _, err := hj.Hijack() + if err != nil { + // Hijacking failed, switch to panic + // to simulate a failure + panic(err) + } + _ = conn.Close() + } + + t.Run("with one subgraph giving an error", func(t *testing.T) { + t.Parallel() + + exporter := tracetest.NewInMemoryExporter(t) + testenv.Run(t, &testenv.Config{ + TraceExporter: exporter, + Subgraphs: testenv.SubgraphsConfig{ + Products: testenv.SubgraphConfig{ + Middleware: func(_ http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + simulateConnectionFailureOnClose(w) + }) + }, + }, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employees { id isAvailable products derivedMood } }`, + }) + + sn := exporter.GetSpans().Snapshots() + + subgraphThatShouldHaveError := "products" + + for _, span := range sn { + if slices.Contains([]string{"Engine - Fetch"}, span.Name()) { + attributes := span.Attributes() + events := span.Events() + + hasErrorEvent := false + subgraphName := "" + + if len(events) > 0 { + require.Len(t, events, 1) + require.Equal(t, "exception", events[0].Name) + hasErrorEvent = true + } + + for _, attributeEntry := range attributes { + if attributeEntry.Key == otel.WgSubgraphName { + subgraphName = attributeEntry.Value.AsString() + } + } + + if subgraphName == subgraphThatShouldHaveError { + require.True(t, hasErrorEvent) + } else { + require.False(t, hasErrorEvent) + } + } + } + }) + }) + + t.Run("with multiple subgraphs giving an error", func(t *testing.T) { + t.Parallel() + + exporter := tracetest.NewInMemoryExporter(t) + testenv.Run(t, &testenv.Config{ + TraceExporter: exporter, + Subgraphs: testenv.SubgraphsConfig{ + Products: testenv.SubgraphConfig{ + Middleware: func(_ http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + simulateConnectionFailureOnClose(w) + }) + }, + }, + Availability: testenv.SubgraphConfig{ + Middleware: func(_ http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + simulateConnectionFailureOnClose(w) + }) + }, + }, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employees { id isAvailable derivedMood products } }`, + }) + + sn := exporter.GetSpans().Snapshots() + + subgraphsThatShouldHaveError := []string{"products", "availability"} + + for _, span := range sn { + if slices.Contains([]string{"Engine - Fetch"}, span.Name()) { + attributes := span.Attributes() + events := span.Events() + + hasErrorEvent := false + subgraphName := "" + + if len(events) > 0 { + require.Len(t, events, 1) + require.Equal(t, "exception", events[0].Name) + hasErrorEvent = true + } + + for _, attributeEntry := range attributes { + if attributeEntry.Key == otel.WgSubgraphName { + subgraphName = attributeEntry.Value.AsString() + } + } + + if slices.Contains(subgraphsThatShouldHaveError, subgraphName) { + require.True(t, hasErrorEvent) + } else { + require.False(t, hasErrorEvent) + } + } + } + }) + }) + }) + } func TestExcludeAttributesWithCustomExporter(t *testing.T) { diff --git a/router/core/graphql_handler.go b/router/core/graphql_handler.go index 01bc656d1e..e53489c059 100644 --- a/router/core/graphql_handler.go +++ b/router/core/graphql_handler.go @@ -141,21 +141,19 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ) defer graphqlExecutionSpan.End() - resolveCtx := &resolve.Context{ - Variables: reqCtx.operation.variables, - RemapVariables: reqCtx.operation.remapVariables, - Files: reqCtx.operation.files, - Request: resolve.Request{ - Header: r.Header, - }, - RenameTypeNames: h.executor.RenameTypeNames, - TracingOptions: reqCtx.operation.traceOptions, - InitialPayload: reqCtx.operation.initialPayload, - Extensions: reqCtx.operation.extensions, - ExecutionOptions: reqCtx.operation.executionOptions, + resolveCtx := resolve.NewContext(executionContext) + resolveCtx.Variables = reqCtx.operation.variables + resolveCtx.RemapVariables = reqCtx.operation.remapVariables + resolveCtx.Files = reqCtx.operation.files + resolveCtx.Request = resolve.Request{ + Header: r.Header, } + resolveCtx.RenameTypeNames = h.executor.RenameTypeNames + resolveCtx.TracingOptions = reqCtx.operation.traceOptions + resolveCtx.InitialPayload = reqCtx.operation.initialPayload + resolveCtx.Extensions = reqCtx.operation.extensions + resolveCtx.ExecutionOptions = reqCtx.operation.executionOptions - resolveCtx = resolveCtx.WithContext(executionContext) if h.authorizer != nil { resolveCtx = WithAuthorizationExtension(resolveCtx) resolveCtx.SetAuthorizer(h.authorizer) diff --git a/router/core/websocket.go b/router/core/websocket.go index 15ab328c59..94aa65f75e 100644 --- a/router/core/websocket.go +++ b/router/core/websocket.go @@ -1007,20 +1007,6 @@ func (h *WebSocketConnectionHandler) executeSubscription(registration *Subscript return } } - resolveCtx := &resolve.Context{ - Variables: operationCtx.Variables(), - Request: resolve.Request{ - Header: registration.clientRequest.Header, - ID: h.initRequestID, - }, - RenameTypeNames: h.graphqlHandler.executor.RenameTypeNames, - RemapVariables: operationCtx.remapVariables, - TracingOptions: operationCtx.traceOptions, - Extensions: operationCtx.extensions, - } - if h.forwardInitialPayload && operationCtx.initialPayload != nil { - resolveCtx.InitialPayload = operationCtx.initialPayload - } reqContext := buildRequestContext(requestContextOptions{ operationContext: operationCtx, @@ -1033,7 +1019,22 @@ func (h *WebSocketConnectionHandler) executeSubscription(registration *Subscript if origCtx := getRequestContext(h.request.Context()); origCtx != nil { reqContext.expressionContext = *origCtx.expressionContext.Clone() } - resolveCtx = resolveCtx.WithContext(withRequestContext(h.ctx, reqContext)) + + resolveCtx := resolve.NewContext(withRequestContext(h.ctx, reqContext)) + resolveCtx.Variables = operationCtx.Variables() + resolveCtx.Request = resolve.Request{ + Header: registration.clientRequest.Header, + ID: h.initRequestID, + } + resolveCtx.RenameTypeNames = h.graphqlHandler.executor.RenameTypeNames + resolveCtx.RemapVariables = operationCtx.remapVariables + resolveCtx.TracingOptions = operationCtx.traceOptions + resolveCtx.Extensions = operationCtx.extensions + + if h.forwardInitialPayload && operationCtx.initialPayload != nil { + resolveCtx.InitialPayload = operationCtx.initialPayload + } + if h.graphqlHandler.authorizer != nil { resolveCtx = WithAuthorizationExtension(resolveCtx) resolveCtx.SetAuthorizer(h.graphqlHandler.authorizer) diff --git a/router/go.mod b/router/go.mod index 144000118f..e330485902 100644 --- a/router/go.mod +++ b/router/go.mod @@ -31,7 +31,7 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/twmb/franz-go v1.16.1 - github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240 + github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241 // Do not upgrade, it renames attributes we rely on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 go.opentelemetry.io/contrib/propagators/b3 v1.23.0 diff --git a/router/go.sum b/router/go.sum index 221a7d9dd5..f4d1a1ecc9 100644 --- a/router/go.sum +++ b/router/go.sum @@ -322,8 +322,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk= github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240 h1:xnYwsUrmDcQnrZQ4+WZ8oODktsKJyAJWiYplWfmiQuk= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.240/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241 h1:ch/8hfDaw4oz1Cx3Wb+OUl4qiAo17OdGhYMdRYnX8Is= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.241/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=