mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-24 04:31:09 +00:00
55fc2ae1de
BREAKING CHANGE: upgrades the HTTP framework from gofiber/fiber/v2 to gofiber/fiber/v3, and gofiber/websocket/v2 to gofiber/contrib/v3/websocket. - Handlers now take fiber.Ctx (interface) instead of *fiber.Ctx. - DisableStartupMessage moved from fiber.Config to fiber.ListenConfig on the proxy/api/monitoring Listen calls. - cors AllowOrigins is now []string; c.BodyParser -> c.Bind().Body(). - app.Test takes fiber.TestConfig instead of an int timeout (tests updated). - WebSocket swapped to contrib/v3/websocket (same Conn/New/Config/IsCloseError/IsWebSocketUpgrade API; Conn.Query preserved). - Adopts fasthttp v1.71.0 (pulled by fiber v3), resolving the v1.71 Host-header enforcement that broke the prior pinned-v1.69 workaround. fix(connpool): the background keep-alive goroutine no longer reads the mutable global cfg (captures HostGraphQL/HealthcheckGraphQL at pool creation). Under v3 timing it raced with parseConfig/test cfg reassignment; go test -race ./... is now clean (0 data races). test: make the circuit-breaker cache-fallback and request-coalescing integration tests deterministic (cache TTL outlives the retry loop; coalescing asserts against a measured single-request baseline instead of a hard-coded count); drop the flaky timing-ratio assertion in TestTimingAttackResistance (constant-time guarantee comes from subtle.ConstantTimeCompare, not wall-clock timing). Verified: go build, go vet, golangci-lint (0 issues), go test -race ./... (all packages pass, 0 races), govulncheck (no vulnerabilities). App + dashboard + WebSocket verified live under v3 in single-node and cluster modes.
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
fiber "github.com/gofiber/fiber/v3"
|
|
"github.com/graphql-go/graphql/language/ast"
|
|
"github.com/graphql-go/graphql/language/parser"
|
|
"github.com/graphql-go/graphql/language/source"
|
|
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
|
)
|
|
|
|
// debugParseGraphQLQuery provides detailed logging for mutation routing analysis
|
|
// This is automatically called when LOG_LEVEL=DEBUG to help identify routing issues
|
|
//
|
|
// It logs:
|
|
// - GraphQL query structure (operations, selections, directives)
|
|
// - Final routing decision (which endpoint was chosen)
|
|
// - Automatic detection of mutations routed to wrong endpoints
|
|
//
|
|
// To enable: Set LOG_LEVEL=DEBUG and restart the proxy
|
|
func debugParseGraphQLQuery(c fiber.Ctx, query string) {
|
|
if cfg == nil || cfg.Logger == nil {
|
|
return
|
|
}
|
|
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: "=== DEBUG: Parsing GraphQL Query ===",
|
|
Pairs: map[string]any{
|
|
"query_length": len(query),
|
|
"query_preview": truncateString(query, 100),
|
|
},
|
|
})
|
|
|
|
// Parse the query
|
|
src := source.NewSource(&source.Source{
|
|
Body: []byte(query),
|
|
Name: "Debug GraphQL request",
|
|
})
|
|
|
|
p, err := parser.Parse(parser.ParseParams{Source: src})
|
|
if err != nil {
|
|
cfg.Logger.Error(&libpack_logger.LogMessage{
|
|
Message: "DEBUG: Failed to parse query",
|
|
Pairs: map[string]any{"error": err.Error()},
|
|
})
|
|
return
|
|
}
|
|
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: "DEBUG: Query parsed successfully",
|
|
Pairs: map[string]any{
|
|
"definitions_count": len(p.Definitions),
|
|
},
|
|
})
|
|
|
|
// Analyze each definition
|
|
for i, d := range p.Definitions {
|
|
if oper, ok := d.(*ast.OperationDefinition); ok {
|
|
operationType := strings.ToLower(oper.Operation)
|
|
operationName := "unnamed"
|
|
if oper.Name != nil {
|
|
operationName = oper.Name.Value
|
|
}
|
|
|
|
// Count selections
|
|
selectionCount := 0
|
|
if oper.SelectionSet != nil {
|
|
selectionCount = len(oper.GetSelectionSet().Selections)
|
|
}
|
|
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: fmt.Sprintf("DEBUG: Definition #%d (OperationDefinition)", i),
|
|
Pairs: map[string]any{
|
|
"operation_type": operationType,
|
|
"operation_name": operationName,
|
|
"selection_count": selectionCount,
|
|
"is_mutation": operationType == "mutation",
|
|
"directive_count": len(oper.Directives),
|
|
},
|
|
})
|
|
|
|
// Log selections for mutations
|
|
if operationType == "mutation" && oper.SelectionSet != nil {
|
|
for j, sel := range oper.GetSelectionSet().Selections {
|
|
if field, ok := sel.(*ast.Field); ok {
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: fmt.Sprintf("DEBUG: Mutation field #%d", j),
|
|
Pairs: map[string]any{
|
|
"field_name": field.Name.Value,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} else if frag, ok := d.(*ast.FragmentDefinition); ok {
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: fmt.Sprintf("DEBUG: Definition #%d (FragmentDefinition)", i),
|
|
Pairs: map[string]any{
|
|
"fragment_name": frag.Name.Value,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// Now run the actual parsing to see the result
|
|
result := parseGraphQLQuery(c)
|
|
|
|
cfg.Logger.Info(&libpack_logger.LogMessage{
|
|
Message: "DEBUG: Final routing decision",
|
|
Pairs: map[string]any{
|
|
"operation_type": result.operationType,
|
|
"operation_name": result.operationName,
|
|
"active_endpoint": result.activeEndpoint,
|
|
"should_block": result.shouldBlock,
|
|
"should_ignore": result.shouldIgnore,
|
|
"write_endpoint": cfg.Server.HostGraphQL,
|
|
"read_endpoint": cfg.Server.HostGraphQLReadOnly,
|
|
"is_using_write": result.activeEndpoint == cfg.Server.HostGraphQL,
|
|
},
|
|
})
|
|
|
|
// Check for potential issues
|
|
if result.operationType == "mutation" && result.activeEndpoint != cfg.Server.HostGraphQL {
|
|
cfg.Logger.Error(&libpack_logger.LogMessage{
|
|
Message: "DEBUG: ⚠️ BUG DETECTED: Mutation routed to wrong endpoint!",
|
|
Pairs: map[string]any{
|
|
"expected_endpoint": cfg.Server.HostGraphQL,
|
|
"actual_endpoint": result.activeEndpoint,
|
|
},
|
|
})
|
|
}
|
|
|
|
if result.operationType == "mutation" && strings.Contains(strings.ToLower(result.activeEndpoint), "read") {
|
|
cfg.Logger.Error(&libpack_logger.LogMessage{
|
|
Message: "DEBUG: ⚠️ CRITICAL: Mutation endpoint contains 'read' in URL!",
|
|
Pairs: map[string]any{
|
|
"endpoint": result.activeEndpoint,
|
|
},
|
|
})
|
|
}
|
|
}
|