Compare commits

...

10 Commits

11 changed files with 95 additions and 88 deletions
+11 -11
View File
@@ -1,4 +1,4 @@
name: Test and release
name: Autoupdate go.mod and go.sum
on:
workflow_dispatch:
@@ -34,15 +34,16 @@ jobs:
# This job is responsible for running tests and linting the codebase
test:
name: "Unit testing"
# needs: [prepare]
runs-on: ubuntu-latest
container: golang:1
# container: github/super-linter:v4
needs: [prepare]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure full history is checked out
token: ${{ secrets.GHCR_TOKEN }}
- name: Install Go
uses: actions/setup-go@v5
@@ -56,18 +57,17 @@ jobs:
apt-get install ca-certificates make -y
update-ca-certificates
go mod tidy
get -u -v ./...
go get -u -v ./...
go mod tidy -v
- name: Run unit tests
run: |
CI_RUN=${CI} make test
git config --global --add safe.directory /__w/graphql-monitoring-proxy/graphql-monitoring-proxy
# if go.mod or go.sum have changed then commit the changes to the repository
- name: Commit changes
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git add go.mod go.sum
git commit -m "Update go.mod and go.sum"
git push
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update go.mod and go.sum"
commit_options: "--no-verify --signoff"
file_pattern: "go.mod go.sum"
+1 -1
View File
@@ -15,10 +15,10 @@ type CacheConfig struct {
Logger *libpack_logger.Logger
Client CacheClient
Redis struct {
Enable bool `json:"enable"`
URL string `json:"url"`
Password string `json:"password"`
DB int `json:"db"`
Enable bool `json:"enable"`
}
TTL int `json:"ttl"`
}
+6
View File
@@ -20,6 +20,7 @@ func BenchmarkCacheLookupInMemory(b *testing.B) {
Client: libpack_cache_memory.New(5 * time.Minute),
TTL: 5,
}
EnableCache(config)
hash := "00000000000000000000000000000000001337"
data := []byte("it's fine.")
@@ -46,6 +47,8 @@ func BenchmarkCacheLookupRedis(b *testing.B) {
Client: mockedCache,
TTL: 5,
}
config.Redis.Enable = true
EnableCache(config)
hash := "00000000000000000000000000000000001337"
data := []byte("it's fine.")
@@ -67,6 +70,7 @@ func BenchmarkCacheStoreInMemory(b *testing.B) {
Client: libpack_cache_memory.New(5 * time.Minute),
TTL: 5,
}
EnableCache(config)
hash := "00000000000000000000000000000000001337"
data := []byte("it's fine.")
@@ -92,6 +96,8 @@ func BenchmarkCacheStoreRedis(b *testing.B) {
Client: mockedCache,
TTL: 5,
}
config.Redis.Enable = true
EnableCache(config)
hash := "00000000000000000000000000000000001337"
data := []byte("it's fine.")
+3 -3
View File
@@ -11,10 +11,10 @@ import (
)
type RedisConfig struct {
client *redis.Client
ctx context.Context
prefix string
client *redis.Client
builderPool *sync.Pool
prefix string
}
func (c *RedisConfig) prependKeyName(key string) string {
@@ -29,8 +29,8 @@ func (c *RedisConfig) prependKeyName(key string) string {
type RedisClientConfig struct {
RedisServer string
RedisPassword string
RedisDB int
Prefix string
RedisDB int
}
func New(redisClientConfig *RedisClientConfig) *RedisConfig {
+5 -4
View File
@@ -1,14 +1,16 @@
module github.com/lukaszraczylo/graphql-monitoring-proxy
go 1.21
go 1.21.0
toolchain go1.22.4
require (
github.com/VictoriaMetrics/metrics v1.33.1
github.com/VictoriaMetrics/metrics v1.34.0
github.com/alicebob/miniredis/v2 v2.33.0
github.com/avast/retry-go/v4 v4.6.0
github.com/goccy/go-json v0.10.3
github.com/gofiber/fiber/v2 v2.52.4
github.com/gofrs/flock v0.8.1
github.com/gofrs/flock v0.9.0
github.com/google/uuid v1.6.0
github.com/gookit/goutil v0.6.15
github.com/graphql-go/graphql v0.8.1
@@ -31,7 +33,6 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
+4 -7
View File
@@ -1,5 +1,5 @@
github.com/VictoriaMetrics/metrics v1.33.1 h1:CNV3tfm2Kpv7Y9W3ohmvqgFWPR55tV2c7M2U6OIo+UM=
github.com/VictoriaMetrics/metrics v1.33.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/VictoriaMetrics/metrics v1.34.0 h1:0i8k/gdOJdSoZB4Z9pikVnVQXfhcIvnG7M7h2WaQW2w=
github.com/VictoriaMetrics/metrics v1.34.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
@@ -15,7 +15,6 @@ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -26,8 +25,8 @@ github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.9.0 h1:QqEH0zKHPdEyY4YbJLleD9Il4ft7h6hn3gECO6Ss4rQ=
github.com/gofrs/flock v0.9.0/go.mod h1:O+L78Axre/Bc0Ya3RlNiGP+Rt0tFHWjtHTQ+B2uPZw8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
@@ -64,7 +63,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -73,7 +71,6 @@ github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+3 -3
View File
@@ -42,16 +42,16 @@ const (
var defaultOutput = os.Stdout
type Logger struct {
output io.Writer
format string
minLogLevel int
showCaller bool
output io.Writer
}
type LogMessage struct {
Message string
Pairs map[string]any
output io.Writer
Pairs map[string]any
Message string
}
func (m *LogMessage) String() string {
+3 -3
View File
@@ -59,13 +59,13 @@ func (suite *LoggerTestSuite) Test_LogsLevelsPrint() {
logger := New().SetOutput(output)
tests := []struct {
pairs map[string]any
name string
method string
message string
loggerMinLevel int
messageLogLevel int
message string
pairs map[string]any
wantOutput bool // Whether we expect output to be written
wantOutput bool
}{
{
name: "Log: Debug, Level: Debug - no pairs",
+1 -1
View File
@@ -14,8 +14,8 @@ import (
// Cache for sorted label keys to avoid repeated sorting
var sortedLabelKeysCache = struct {
sync.RWMutex
m map[string][]string
sync.RWMutex
}{m: make(map[string][]string)}
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) string {
+1 -1
View File
@@ -167,9 +167,9 @@ func TestEnsureDefaultLabels(t *testing.T) {
libpack_config.PKG_NAME = "example_microservice"
tests := []struct {
name string
inputLabels map[string]string
expectedLabels map[string]string
name string
}{
{
name: "Nil labels",
+57 -54
View File
@@ -2,6 +2,7 @@ package main
import (
"os"
"sync"
"time"
"github.com/goccy/go-json"
@@ -16,58 +17,61 @@ type RateLimitConfig struct {
Req int `json:"req"`
}
var rateLimits map[string]RateLimitConfig
var ratelimit_intervals = map[string]time.Duration{
"milli": time.Millisecond,
"micro": time.Microsecond,
"nano": time.Nanosecond,
"second": time.Second,
"minute": time.Minute,
"hour": time.Hour,
"day": time.Hour * 24,
}
var (
rateLimits map[string]RateLimitConfig
ratelimitIntervals = map[string]time.Duration{
"milli": time.Millisecond,
"micro": time.Microsecond,
"nano": time.Nanosecond,
"second": time.Second,
"minute": time.Minute,
"hour": time.Hour,
"day": 24 * time.Hour,
}
configPaths = []string{"/go/src/app/ratelimit.json", "./ratelimit.json", "./static/app/default-ratelimit.json"}
mu sync.RWMutex
)
func loadRatelimitConfig() error {
paths := []string{"/go/src/app/ratelimit.json", "./ratelimit.json", "./static/app/default-ratelimit.json"}
for _, path := range paths {
err := loadConfigFromPath(path)
if err == nil {
for _, path := range configPaths {
if err := loadConfigFromPath(path); err == nil {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Failed to load config",
Pairs: map[string]interface{}{"path": path, "error": err},
})
return nil
}
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Failed to load config",
Pairs: map[string]interface{}{"path": path, "error": err},
})
}
cfg.Logger.Error(&libpack_logger.LogMessage{
Message: "Rate limit config not found",
Pairs: map[string]interface{}{"paths": paths},
Pairs: map[string]interface{}{"paths": configPaths},
})
return os.ErrNotExist
}
func loadConfigFromPath(path string) error {
file, err := os.Open(path)
file, err := os.ReadFile(path)
if err != nil {
return err
}
defer file.Close()
config := struct {
var config struct {
RateLimit map[string]RateLimitConfig `json:"ratelimit"`
}{}
}
decoder := json.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
if err := json.Unmarshal(file, &config); err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
rateLimits = make(map[string]RateLimitConfig, len(config.RateLimit))
for key, value := range config.RateLimit {
value.RateCounterTicker = goratecounter.NewRateCounter().WithConfig(goratecounter.RateCounterConfig{
Interval: time.Duration(value.Req) * ratelimit_intervals[value.Interval],
Interval: time.Duration(value.Req) * ratelimitIntervals[value.Interval],
})
if cfg.LogLevel == "debug" {
@@ -76,15 +80,14 @@ func loadConfigFromPath(path string) error {
Pairs: map[string]interface{}{
"role": key,
"interval_provided": value.Interval,
"interval_used": ratelimit_intervals[value.Interval],
"interval_used": ratelimitIntervals[value.Interval],
"ratelimit": value.Req,
},
})
}
config.RateLimit[key] = value
rateLimits[key] = value
}
rateLimits = config.RateLimit
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit config loaded",
Pairs: map[string]interface{}{"ratelimit": rateLimits},
@@ -92,7 +95,10 @@ func loadConfigFromPath(path string) error {
return nil
}
func rateLimitedRequest(userID string, userRole string) (shouldAllow bool) {
func rateLimitedRequest(userID, userRole string) bool {
mu.RLock()
defer mu.RUnlock()
if rateLimits == nil {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit config not found",
@@ -101,19 +107,10 @@ func rateLimitedRequest(userID string, userRole string) (shouldAllow bool) {
return true
}
// Fetch role config once to avoid multiple map lookups
roleConfig, ok := rateLimits[userRole]
if !ok {
if !ok || roleConfig.RateCounterTicker == nil {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit role not found",
Pairs: map[string]interface{}{"user_role": userRole},
})
return true
}
if roleConfig.RateCounterTicker == nil {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit ticker not found",
Message: "Rate limit role or ticker not found",
Pairs: map[string]interface{}{"user_role": userRole},
})
return true
@@ -122,23 +119,29 @@ func rateLimitedRequest(userID string, userRole string) (shouldAllow bool) {
roleConfig.RateCounterTicker.Incr(1)
tickerRate := roleConfig.RateCounterTicker.GetRate()
logDetails := map[string]interface{}{
"user_role": userRole,
"user_id": userID,
"rate": tickerRate,
"config_rate": roleConfig.Req,
"interval": roleConfig.Interval,
if cfg.LogLevel == "debug" {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit ticker",
Pairs: map[string]interface{}{
"user_role": userRole,
"user_id": userID,
"rate": tickerRate,
"config_rate": roleConfig.Req,
"interval": roleConfig.Interval,
},
})
}
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit ticker",
Pairs: map[string]interface{}{"log_details": logDetails},
})
if tickerRate > float64(roleConfig.Req) {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Rate limit exceeded",
Pairs: map[string]interface{}{"log_details": logDetails},
Pairs: map[string]interface{}{
"user_role": userRole,
"user_id": userID,
"rate": tickerRate,
"config_rate": roleConfig.Req,
"interval": roleConfig.Interval,
},
})
return false
}