mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-21 06:37:21 +00:00
perf+coverage: optimisation pass + coverage push to ≥70%
Performance / resource usage: - circuit_breaker_metrics: fix data race on failCounters map (RWMutex + double-checked locking) - server.go: drop user_id and op_name metric labels (Prometheus cardinality bound); de-duplicate extractUserInfo - graphql.go: gate runtime.ReadMemStats per-request behind ENABLE_ALLOCATION_TRACKING flag (default off) - graphql.go: collapse two-pass AST scan into single pass; lower-case once - sanitization.go: cache compiled redaction regexes per pattern via sync.Map; hoist inner constants to pkg vars - proxy.go: hoist connection/timeout substrings to pkg vars; sentinel errors for static error paths; drop dead Headers map alloc - metrics_aggregator.go: log-field allocation guarded by Logger.IsLevelEnabled - logging/logger.go: add IsLevelEnabled helper - lru_cache.go: 16-shard sharding, FNV-1a routing (concurrent throughput +22%) - cache/memory/lru_memory_cache.go: gzip compress/decompress moved outside mu.Lock - rps_tracker.go: RWMutex+uint64 -> atomic.Uint64 - retry_budget.go: drop unused mutex - api.go: bannedUsersIDs map+RWMutex -> sync.Map (+ snapshot/replace helpers) - tracing/tracing.go: pkg-level constSpanAttrs, copy-then-append in StartSpanWithAttributes - admin_dashboard.go: handleStatsWebSocket reuses bytes.Buffer + json.Encoder per connection Build / runtime: - Makefile: -ldflags="-s -w" -trimpath, CGO_ENABLED=0 for build (=1 for test recipes) - Dockerfile + Dockerfile.goreleaser: ENV GOMEMLIMIT=512MiB - main.go: blank import go.uber.org/automaxprocs (cgroup-aware GOMAXPROCS) - main.go: PPROF_PORT env var wires net/http/pprof on 127.0.0.1 only with full server timeouts - README.md: env-var docs + metric-label docs updated; cardinality note Test coverage push (per package): - main 51.2% -> 74.7% - cache 66.3% -> 93.7% - cache/redis 45.5% -> 98.2% - tracing 66.7% -> 72.9% - (cache/memory 91.6%, logging 91.9%, monitoring 77.6%, pkg/pools 100% unchanged) New test files: coverage_micro_test, coverage_extras_test, server_handlers_test, api_health_test, admin_dashboard_cluster_test, metrics_aggregator_test, concerns_test, cache/cache_coverage_test, cache/redis/redis_coverage_test, tracing/tracing_coverage_test. Bug fix: connection_resilience_test.go TestIntegratedHealthManagement.health_manager_startup was sync.Once-coupled to InitializeBackendHealth and panicked when another test (e.g. via parseConfig) had already triggered Once. Use NewBackendHealthManager directly.
This commit is contained in:
+46
-14
@@ -4,10 +4,46 @@ import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
// patternRegexCache caches the 5 outer regexes per sensitive field name.
|
||||
// Pattern set is bounded by sensitiveFieldPatterns (fixed slice) — not a leak.
|
||||
var patternRegexCache sync.Map // map[string]*patternRegexSet
|
||||
|
||||
type patternRegexSet struct {
|
||||
json *regexp.Regexp
|
||||
xml *regexp.Regexp
|
||||
quoted *regexp.Regexp
|
||||
singleQuote *regexp.Regexp
|
||||
form *regexp.Regexp
|
||||
}
|
||||
|
||||
// Constant inner regexes, pattern-independent — compile once.
|
||||
var (
|
||||
jsonValueRe = regexp.MustCompile(`:\s*"[^"]*"`)
|
||||
xmlValueRe = regexp.MustCompile(`>[^<]*<`)
|
||||
formValueRe = regexp.MustCompile(`=([^&\s"']+)`)
|
||||
)
|
||||
|
||||
func getPatternRegexSet(pattern string) *patternRegexSet {
|
||||
if v, ok := patternRegexCache.Load(pattern); ok {
|
||||
return v.(*patternRegexSet)
|
||||
}
|
||||
quoted := regexp.QuoteMeta(pattern)
|
||||
set := &patternRegexSet{
|
||||
json: regexp.MustCompile(`(?i)"` + quoted + `"\s*:\s*"[^"]*"`),
|
||||
xml: regexp.MustCompile(`(?i)<` + quoted + `>[^<]*</` + quoted + `>`),
|
||||
quoted: regexp.MustCompile(`(?i)` + quoted + `="[^"]*"`),
|
||||
singleQuote: regexp.MustCompile(`(?i)` + quoted + `='[^']*'`),
|
||||
form: regexp.MustCompile(`(?i)` + quoted + `=([^&\s"']+)(?:[&\s]|$)`),
|
||||
}
|
||||
actual, _ := patternRegexCache.LoadOrStore(pattern, set)
|
||||
return actual.(*patternRegexSet)
|
||||
}
|
||||
|
||||
// Sanitization constants
|
||||
const (
|
||||
// MaxLogBodySize is the maximum size of body content to include in logs
|
||||
@@ -110,18 +146,17 @@ func redactSensitiveFields(data map[string]any, fields []string) {
|
||||
func redactPatternInString(text string, pattern string) string {
|
||||
// Use proper regex to capture and redact complete sensitive values
|
||||
// Order matters: process most specific patterns first
|
||||
set := getPatternRegexSet(pattern)
|
||||
|
||||
// 1. JSON pattern: "field":"value" → "field":"[REDACTED]"
|
||||
jsonPattern := regexp.MustCompile(`(?i)"` + regexp.QuoteMeta(pattern) + `"\s*:\s*"[^"]*"`)
|
||||
text = jsonPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
return regexp.MustCompile(`:\s*"[^"]*"`).ReplaceAllString(match, `:"[REDACTED]"`)
|
||||
text = set.json.ReplaceAllStringFunc(text, func(match string) string {
|
||||
return jsonValueRe.ReplaceAllString(match, `:"[REDACTED]"`)
|
||||
})
|
||||
|
||||
// 2. XML pattern: <field>value</field> → <field>[REDACTED]</field>
|
||||
xmlPattern := regexp.MustCompile(`(?i)<` + regexp.QuoteMeta(pattern) + `>[^<]*</` + regexp.QuoteMeta(pattern) + `>`)
|
||||
xmlMatched := xmlPattern.MatchString(text)
|
||||
text = xmlPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
return regexp.MustCompile(`>[^<]*<`).ReplaceAllString(match, ">[REDACTED]<")
|
||||
xmlMatched := set.xml.MatchString(text)
|
||||
text = set.xml.ReplaceAllStringFunc(text, func(match string) string {
|
||||
return xmlValueRe.ReplaceAllString(match, ">[REDACTED]<")
|
||||
})
|
||||
|
||||
// If XML pattern was matched, also add a standardized redaction marker for test compatibility
|
||||
@@ -133,22 +168,19 @@ func redactPatternInString(text string, pattern string) string {
|
||||
}
|
||||
|
||||
// 3. Double quoted pattern: field="value" → field="[REDACTED]"
|
||||
quotedPattern := regexp.MustCompile(`(?i)` + regexp.QuoteMeta(pattern) + `="[^"]*"`)
|
||||
text = quotedPattern.ReplaceAllString(text, pattern+`="[REDACTED]"`)
|
||||
text = set.quoted.ReplaceAllString(text, pattern+`="[REDACTED]"`)
|
||||
|
||||
// 4. Single quoted pattern: field='value' → field='[REDACTED]'
|
||||
singleQuotedPattern := regexp.MustCompile(`(?i)` + regexp.QuoteMeta(pattern) + `='[^']*'`)
|
||||
text = singleQuotedPattern.ReplaceAllString(text, pattern+`='[REDACTED]'`)
|
||||
text = set.singleQuote.ReplaceAllString(text, pattern+`='[REDACTED]'`)
|
||||
|
||||
// 5. Form/URL pattern: field=value& or field=value$ → field=[REDACTED]& or field=[REDACTED]$
|
||||
// This must be last and should only match unquoted values
|
||||
formPattern := regexp.MustCompile(`(?i)` + regexp.QuoteMeta(pattern) + `=([^&\s"']+)(?:[&\s]|$)`)
|
||||
text = formPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
text = set.form.ReplaceAllStringFunc(text, func(match string) string {
|
||||
// Only replace if the value is not already [REDACTED]
|
||||
if strings.Contains(match, "[REDACTED]") {
|
||||
return match
|
||||
}
|
||||
return regexp.MustCompile(`=([^&\s"']+)`).ReplaceAllString(match, "=[REDACTED]")
|
||||
return formValueRe.ReplaceAllString(match, "=[REDACTED]")
|
||||
})
|
||||
|
||||
return text
|
||||
|
||||
Reference in New Issue
Block a user