mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-24 06:48:09 +00:00
fix: restore tracing endpoint validation; honor per-side client timeouts; cache eviction target; redis close
- tracing: validateEndpoint used fmt.Sscanf("%s:%d") which treats %s as
greedy, so every valid host:port endpoint failed and traced==nil disabled
tracing entirely. Validate with net.SplitHostPort (pure helper, port range
0-65535); test TestValidateEndpoint.
- proxy: createFasthttpClient used clientTimeout for every timeout slot,
ignoring the parsed CLIENT_READ_TIMEOUT/CLIENT_WRITE_TIMEOUT (silent
config loss, documented separately in README). Honour per-side read/write
timeouts, falling back to client timeout when unset.
- cache/memory: evictToFreeMemory capped candidates at maxCacheSize/5 and
stopped at the subset end, so a large write could leave the cache over
its memory limit. Collect all entries and sort.Slice (correct + O(n log n)
vs O(cap^2)), evict until the target is met or the cache is drained.
- cache/redis: the Redis cache client was never closed at shutdown (unlike
the metrics aggregator). Add RedisConfig.Close, CacheWrapper.Close, a
cache.Shutdown and register it with the ShutdownManager.
This commit is contained in:
+22
-10
@@ -7,6 +7,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
@@ -37,6 +39,21 @@ type TraceSpanInfo struct {
|
||||
TraceParent string `json:"traceparent"`
|
||||
}
|
||||
|
||||
// validateEndpoint checks that endpoint is a hostname:port pair with a port in
|
||||
// [0, 65535]. Extracted from NewTracing so it can be unit-tested without
|
||||
// needing a live collector.
|
||||
func validateEndpoint(endpoint string) error {
|
||||
_, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid endpoint format: must be 'hostname:port'")
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 0 || port > 65535 {
|
||||
return fmt.Errorf("invalid endpoint format: port must be between 0 and 65535")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTracing creates a new tracing setup with OTLP exporter
|
||||
func NewTracing(ctx context.Context, endpoint string) (*TracingSetup, error) {
|
||||
if ctx == nil {
|
||||
@@ -46,16 +63,11 @@ func NewTracing(ctx context.Context, endpoint string) (*TracingSetup, error) {
|
||||
return nil, fmt.Errorf("endpoint cannot be empty")
|
||||
}
|
||||
|
||||
// Validate endpoint format
|
||||
// A simple validation to check if the endpoint has a reasonable format
|
||||
// We're looking for hostname:port where port is a valid port number (0-65535)
|
||||
var host string
|
||||
var port int
|
||||
if n, err := fmt.Sscanf(endpoint, "%s:%d", &host, &port); err != nil || n != 2 {
|
||||
return nil, fmt.Errorf("invalid endpoint format: must be 'hostname:port'")
|
||||
}
|
||||
if port < 0 || port > 65535 {
|
||||
return nil, fmt.Errorf("invalid port number: must be between 0 and 65535")
|
||||
// Validate endpoint format: a hostname:port pair with a valid port.
|
||||
// (Previous code used fmt.Sscanf("%s:%d") which treats %s as greedy and
|
||||
// rejected every valid "host:port" string, silently disabling tracing.)
|
||||
if err := validateEndpoint(endpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the exporter directly with the endpoint
|
||||
|
||||
@@ -21,9 +21,7 @@ func TestNewTracing_NilContext_ReturnsError(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestNewTracing_InvalidEndpointFormats covers endpoint validation branches.
|
||||
// Note: fmt.Sscanf("%s:%d") treats %s as greedy so any "host:port" string hits
|
||||
// the format error (n!=2). The port-range branch (port>65535) requires n==2
|
||||
// which Sscanf never produces for "host:port" strings — that's a source quirk.
|
||||
// validateEndpoint rejects missing/host-only endpoints and out-of-range ports.
|
||||
func TestNewTracing_InvalidEndpointFormats_ReturnsError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -118,3 +116,32 @@ func TestExtractSpanContext_ValidTraceparent_ReturnsValid(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.True(t, spanCtx.IsValid())
|
||||
}
|
||||
|
||||
// TestValidateEndpoint checks the extracted endpoint validator. Regression for
|
||||
// the old fmt.Sscanf("%s:%d") which rejected every valid "host:port"
|
||||
// endpoint (crushing tracing into permanent no-op).
|
||||
func TestValidateEndpoint(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
endpoint string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "valid host:port", endpoint: "localhost:4317", wantErr: false},
|
||||
{name: "valid fqdn:port", endpoint: "otel.example.com:4317", wantErr: false},
|
||||
{name: "valid high port", endpoint: "collector:65535", wantErr: false},
|
||||
{name: "missing port", endpoint: "localhost", wantErr: true},
|
||||
{name: "port too high", endpoint: "localhost:999999", wantErr: true},
|
||||
{name: "non-numeric port", endpoint: "localhost:abc", wantErr: true},
|
||||
{name: "bare number", endpoint: "12345", wantErr: true},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateEndpoint(tt.endpoint)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid endpoint format")
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user