Compare commits

...

17 Commits

Author SHA1 Message Date
lukaszraczylo 6dd0ca3069 fixup! fixup! fixup! fixup! fixup! fixup! Update dependencies. 2024-06-10 23:00:24 +01:00
lukaszraczylo ef40902998 fixup! fixup! fixup! fixup! fixup! Update dependencies. 2024-06-10 22:51:59 +01:00
lukaszraczylo f55ed6d140 fixup! fixup! fixup! fixup! Update dependencies. 2024-06-10 22:43:00 +01:00
lukaszraczylo bc36607d4e fixup! fixup! fixup! Update dependencies. 2024-06-10 22:36:25 +01:00
lukaszraczylo 51d7af5fe8 fixup! fixup! Update dependencies. 2024-06-10 22:33:35 +01:00
lukaszraczylo c9b89275ca fixup! Update dependencies. 2024-06-10 22:32:54 +01:00
lukaszraczylo f0042ffe94 Update dependencies. 2024-06-10 22:25:46 +01:00
lukaszraczylo 755b433015 fixup! fixup! Add cache operations via API. 2024-06-10 22:23:33 +01:00
lukaszraczylo 86ae4ce802 fixup! Add cache operations via API. 2024-06-10 16:03:44 +01:00
lukaszraczylo 468cd959a7 Add cache operations via API. 2024-06-10 11:34:30 +01:00
lukaszraczylo 9291ac03db Improved graphql library. 2024-05-14 11:33:35 +01:00
lukaszraczylo 75944a3a52 Fixup: Update graphql client library. 2024-05-14 09:40:21 +01:00
lukaszraczylo 5a01ec3876 Improve logging and cache sub-packages to decrease the number of allocations
and improve performance.
2024-05-14 09:21:16 +01:00
lukaszraczylo c3e5b85f57 Update dependencies. 2024-05-04 21:22:19 +01:00
lukaszraczylo bc2dff0185 Update dependencies. 2024-04-12 23:30:18 +01:00
lukaszraczylo ce344d17eb Add read only replica (#11)
* Improve stats gathering.
2024-03-13 23:09:38 +00:00
lukaszraczylo dc916d36cd Fix documentation after testing. 2024-03-12 23:12:50 +00:00
19 changed files with 335 additions and 543 deletions
+4 -1
View File
@@ -2,6 +2,9 @@ FROM gcr.io/distroless/base-debian12:nonroot
WORKDIR /go/src/app
ARG TARGETARCH
ARG TARGETOS
ADD dist/bot-$TARGETOS-$TARGETARCH /go/src/app/graphql-proxy
# silly workaround for distroless image as no chmod is available
COPY --chmod=777 --chown=65532:65532 static/app /app
ADD static/default-ratelimit.json /app/ratelimit.json
ADD static/default-banned_users.json /app/banned_users.json
ADD dist/bot-$TARGETOS-$TARGETARCH /go/src/app/graphql-proxy
ENTRYPOINT ["/go/src/app/graphql-proxy"]
+6
View File
@@ -23,6 +23,7 @@ This project is in active use by [telegram-bot.app](https://telegram-bot.app), a
- [Blocking introspection](#blocking-introspection)
- [API endpoints](#api-endpoints)
- [Ban or unban the user](#ban-or-unban-the-user)
- [Cache operations](#cache-operations)
- [General](#general)
- [Metrics which matter](#metrics-which-matter)
- [Healthcheck](#healthcheck)
@@ -236,6 +237,11 @@ To do so - you need to enable the api by setting env variable `ENABLE_API=true`
* `POST /api/user-ban` - ban the user from accessing the application
* `POST /api/user-unban` - unban the user from accessing the application
#### Cache operations
* `POST /api/cache-clear` - clear the cache
* `GET /api/cache-stats` - get the cache statistics ( hits, misses, size )
Both endpoints require the `user_id` parameter to be present in the request body and allow you to provide the reason for the ban.
Example request:
+17
View File
@@ -23,6 +23,8 @@ func enableApi() {
api := apiserver.Group("/api")
api.Post("/user-ban", apiBanUser)
api.Post("/user-unban", apiUnbanUser)
api.Post("/cache-clear", apiClearCache)
api.Get("/cache-stats", apiCacheStats)
go periodicallyReloadBannedUsers()
err := apiserver.Listen(fmt.Sprintf(":%d", cfg.Server.ApiPort))
@@ -50,6 +52,21 @@ func checkIfUserIsBanned(c *fiber.Ctx, userID string) bool {
return found
}
func apiClearCache(c *fiber.Ctx) error {
cfg.Logger.Debug("Clearing cache via API", nil)
cfg.Cache.CacheClient.ClearCache()
cfg.Logger.Info("Cache cleared via API", nil)
c.Status(200).SendString("OK: cache cleared")
return nil
}
func apiCacheStats(c *fiber.Ctx) error {
stats := cfg.Cache.CacheClient.ShowStats()
cfg.Logger.Debug("Getting cache stats via API", map[string]interface{}{"stats": stats})
c.JSON(stats)
return nil
}
type apiBanUserRequest struct {
UserID string `json:"user_id"`
Reason string `json:"reason"`
+85 -45
View File
@@ -14,23 +14,33 @@ type CacheEntry struct {
}
type Cache struct {
bytePool sync.Pool
entries sync.Map
globalTTL time.Duration
sync.RWMutex
entries sync.Map
globalTTL time.Duration
compressPool sync.Pool
decompressPool sync.Pool
cacheHits int
cacheMisses int
sync.RWMutex // Reintroduced to provide lock methods
}
func New(globalTTL time.Duration) *Cache {
cache := &Cache{
globalTTL: globalTTL,
compressPool: sync.Pool{
New: func() interface{} {
w := gzip.NewWriter(nil)
return w
},
},
decompressPool: sync.Pool{
New: func() interface{} {
// Ensure that new is returning a new reader initialized with an empty byte buffer
r, _ := gzip.NewReader(bytes.NewReader([]byte{}))
return r
},
},
}
// Initialize the byte pool.
cache.bytePool.New = func() interface{} {
return make([]byte, 0)
}
// Start the cache cleanup.
go cache.cleanupRoutine(globalTTL)
return cache
}
@@ -43,10 +53,10 @@ func (c *Cache) cleanupRoutine(globalTTL time.Duration) {
c.CleanExpiredEntries()
}
}
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
c.Lock()
c.Lock() // use the lock
defer c.Unlock()
expiresAt := time.Now().Add(ttl)
compressedValue, err := c.compress(value)
@@ -54,37 +64,29 @@ func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
return
}
// Get a byte slice from the pool and ensure it's properly sized.
b := c.bytePool.Get().([]byte)
if cap(b) < len(compressedValue) {
b = make([]byte, len(compressedValue))
} else {
b = b[:len(compressedValue)]
}
copy(b, compressedValue)
entry := CacheEntry{
Value: b,
Value: compressedValue,
ExpiresAt: expiresAt,
}
c.entries.Store(key, entry)
}
func (c *Cache) Get(key string) ([]byte, bool) {
c.RLock()
c.RLock() // use the read lock
defer c.RUnlock()
entry, ok := c.entries.Load(key)
if !ok || entry.(CacheEntry).ExpiresAt.Before(time.Now()) {
c.cacheMisses++
return nil, false
}
compressedValue := entry.(CacheEntry).Value
value, err := c.decompress(compressedValue)
if err != nil {
c.cacheMisses++
return nil, false
}
c.cacheHits++
return value, true
}
@@ -92,15 +94,11 @@ func (c *Cache) Delete(key string) {
c.Lock()
defer c.Unlock()
entry, ok := c.entries.Load(key)
_, ok := c.entries.Load(key)
if !ok {
return
}
// Return the byte slice to the pool.
c.bytePool.Put(entry.(CacheEntry).Value)
// Delete the entry from the cache.
c.entries.Delete(key)
}
@@ -109,37 +107,79 @@ func (c *Cache) CleanExpiredEntries() {
c.entries.Range(func(key, value interface{}) bool {
entry := value.(CacheEntry)
if entry.ExpiresAt.Before(now) {
// Return the byte slice to the pool.
c.bytePool.Put(entry.Value)
// Delete the entry from the cache.
c.entries.Delete(key)
}
// Return true to continue iterating over the map.
return true
})
}
type CacheStats struct {
CachedQueries int `json:"cached_queries"`
CacheHits int `json:"cache_hits"`
CacheMisses int `json:"cache_misses"`
}
func (c *Cache) ShowStats() CacheStats {
c.RLock()
defer c.RUnlock()
var count int
c.entries.Range(func(_, _ interface{}) bool {
count++
return true
})
cs := CacheStats{
CachedQueries: count,
CacheHits: c.cacheHits,
CacheMisses: c.cacheMisses,
}
return cs
}
func (c *Cache) ClearCache() {
c.cacheHits = 0
c.cacheMisses = 0
c.entries = sync.Map{}
}
func (c *Cache) compress(data []byte) ([]byte, error) {
w := c.compressPool.Get().(*gzip.Writer)
defer c.compressPool.Put(w)
var buf bytes.Buffer
w := gzip.NewWriter(&buf)
_, err := w.Write(data)
if err != nil {
w.Reset(&buf)
if _, err := w.Write(data); err != nil {
return nil, err
}
err = w.Close()
if err != nil {
if err := w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (c *Cache) decompress(data []byte) ([]byte, error) {
r, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, err
r, ok := c.decompressPool.Get().(*gzip.Reader)
if !ok || r == nil {
// If r is nil or type assertion fails, create a new gzip.Reader
var err error
r, err = gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err // Handle the error if gzip.NewReader fails
}
} else {
// Reset the existing reader with new data
if err := r.Reset(bytes.NewReader(data)); err != nil {
return nil, err // Handle the error if Reset fails
}
}
defer r.Close()
return io.ReadAll(r)
// Ensure the reader is returned to the pool
defer c.decompressPool.Put(r)
// Read all the data from the reader
decompressedData, err := io.ReadAll(r)
if err != nil {
return nil, err // Handle the error if reading fails
}
return decompressedData, nil
}
+54
View File
@@ -0,0 +1,54 @@
package libpack_cache
import (
"testing"
"time"
)
// Assume that New function initializes the cache and it is defined somewhere in the libpack_cache package.
func BenchmarkCacheSet(b *testing.B) {
cache := New(30 * time.Second) // Initializing the cache with a TTL of 30 seconds
key := "benchmark-key"
value := []byte("benchmark-value")
b.ResetTimer() // Reset the timer to exclude the setup time from the benchmark
for i := 0; i < b.N; i++ {
cache.Set(key, value, 5*time.Second)
}
}
func BenchmarkCacheGet(b *testing.B) {
cache := New(30 * time.Second) // Initializing the cache
key := "benchmark-key"
value := []byte("benchmark-value")
cache.Set(key, value, 5*time.Second) // Pre-set a value to retrieve
b.ResetTimer() // Start timing
for i := 0; i < b.N; i++ {
_, _ = cache.Get(key)
}
}
func BenchmarkCacheExpire(b *testing.B) {
key := "benchmark-expire-key"
value := []byte("benchmark-value")
ttl := 5 * time.Millisecond // Setting a short TTL for quick expiration
for i := 0; i < b.N; i++ {
cache := New(30 * time.Second)
cache.Set(key, value, ttl)
time.Sleep(ttl) // Wait for the key to expire
_, _ = cache.Get(key)
}
}
func BenchmarkCacheStats(b *testing.B) {
cache := New(30 * time.Second) // Initializing the cache
key := "benchmark-key"
value := []byte("benchmark-value")
cache.Set(key, value, 5*time.Second) // Pre-set a value to retrieve
cache.Get(key)
}
+7 -10
View File
@@ -111,8 +111,8 @@ func (suite *CacheTestSuite) Test_CacheExpire() {
}
}
func (suite *CacheTestSuite) Test_CacheCleanExpiredEntries() {
cache := New(5 * time.Second)
func (suite *CacheTestSuite) Test_CacheStats() {
cache := New(30 * time.Second)
tests := []struct {
name string
cache_value string
@@ -135,14 +135,11 @@ func (suite *CacheTestSuite) Test_CacheCleanExpiredEntries() {
c, ok := cache.Get(tt.name)
suite.Equal(true, ok)
suite.Equal(tt.name, string(c))
time.Sleep(tt.ttl)
c, ok = cache.Get(tt.name)
suite.Equal(false, ok)
suite.Equal("", string(c))
cache.CleanExpiredEntries()
c, ok = cache.Get(tt.name)
suite.Equal(false, ok)
suite.Equal("", string(c))
})
}
cache.Get("non-existent-non-cached-key")
stats := cache.ShowStats()
suite.Equal(2, stats.CacheHits, "CacheHits")
suite.Equal(1, stats.CacheMisses, "CacheMisses")
suite.Equal(2, stats.CachedQueries, "CachedQueries")
}
+14 -15
View File
@@ -3,28 +3,27 @@ module github.com/lukaszraczylo/graphql-monitoring-proxy
go 1.21
require (
github.com/VictoriaMetrics/metrics v1.33.0
github.com/avast/retry-go/v4 v4.5.1
github.com/buger/jsonparser v1.1.1
github.com/goccy/go-json v0.10.2
github.com/gofiber/fiber/v2 v2.52.2
github.com/VictoriaMetrics/metrics v1.33.1
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/google/uuid v1.6.0
github.com/gookit/goutil v0.6.15
github.com/graphql-go/graphql v0.8.1
github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415
github.com/lukaszraczylo/go-ratecounter v0.1.8
github.com/lukaszraczylo/go-simple-graphql v1.2.9
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.52.0
github.com/lukaszraczylo/go-simple-graphql v1.2.14
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
github.com/valyala/fasthttp v1.54.0
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/compress v1.17.8 // 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
@@ -38,11 +37,11 @@ require (
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+28 -30
View File
@@ -1,20 +1,18 @@
github.com/VictoriaMetrics/metrics v1.33.0 h1:EnkDEaGiL2u95t+W76GfecC/LMYpy+tFrexYzBWQIAc=
github.com/VictoriaMetrics/metrics v1.33.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/VictoriaMetrics/metrics v1.33.1 h1:CNV3tfm2Kpv7Y9W3ohmvqgFWPR55tV2c7M2U6OIo+UM=
github.com/VictoriaMetrics/metrics v1.33.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo=
github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -25,8 +23,8 @@ github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -38,8 +36,8 @@ github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415 h1:lvI8Wlbg4PxkR
github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415/go.mod h1:M+UVdyqZs++xtEPrascaVmZdOMhCnxjZ2SgH+xHpR0c=
github.com/lukaszraczylo/go-ratecounter v0.1.8 h1:ZYm6Wkn58ZAlFWRmC7PaD4oAYHWcu8/0MUDWGe3PnJQ=
github.com/lukaszraczylo/go-ratecounter v0.1.8/go.mod h1:TqXEOCtFJStk1i0tkipprv1kiDHGon1MVUisjSTBSKM=
github.com/lukaszraczylo/go-simple-graphql v1.2.9 h1:JKIvAw+4O8vwTv2rZKKRtn0DjLbM8XdKXZHns31Ntvc=
github.com/lukaszraczylo/go-simple-graphql v1.2.9/go.mod h1:YOX06PIgxUyFDJZu5FFFo/9FyGTFSh9Zuld2bU8DywU=
github.com/lukaszraczylo/go-simple-graphql v1.2.14 h1:Dth+yZ+1ialCpnslSb6UgHbXszExjDUu/I95QZbnWVU=
github.com/lukaszraczylo/go-simple-graphql v1.2.14/go.mod h1:pSKmm9OLGoS9pjmIvhBB/fo0+LganRrL29CN3fdkRPw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -59,14 +57,14 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
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=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0=
github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
@@ -77,19 +75,19 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+60 -36
View File
@@ -3,6 +3,7 @@ package libpack_logging
import (
"io"
"os"
"sync"
"time"
"github.com/gookit/goutil/envutil"
@@ -13,7 +14,21 @@ type LogConfig struct {
logger zerolog.Logger
}
var baseLogger zerolog.Logger
var (
baseLogger zerolog.Logger
eventPool = sync.Pool{
New: func() interface{} {
return new(zerolog.Event)
},
}
fieldMapPool = sync.Pool{
New: func() interface{} {
return make(map[string]interface{})
},
}
)
func init() {
zerolog.TimeFieldFormat = time.RFC3339
@@ -21,10 +36,9 @@ func init() {
zerolog.TimestampFieldName = "timestamp"
zerolog.LevelFieldName = "level"
zerolog.LevelFatalValue = "critical"
baseLogger = zerolog.New(os.Stdout).With().Timestamp().Logger()
}
func NewLogger() *LogConfig {
baseLogger = zerolog.New(os.Stdout).With().Timestamp().Logger()
switch logLevel := envutil.Getenv("LOG_LEVEL", "info"); logLevel {
case "debug":
baseLogger = baseLogger.Level(zerolog.DebugLevel)
@@ -35,62 +49,72 @@ func NewLogger() *LogConfig {
default:
baseLogger = baseLogger.Level(zerolog.InfoLevel)
}
}
func NewLogger() *LogConfig {
return &LogConfig{logger: baseLogger}
}
func (lw *LogConfig) log(w io.Writer, level zerolog.Level, message string, v map[string]interface{}) {
e := lw.logger.With().Logger()
e = e.Output(w)
event := e.WithLevel(level).CallerSkipFrame(3)
for k, val := range v {
func (lw *LogConfig) log(w io.Writer, level zerolog.Level, message string, fields map[string]interface{}) {
logger := lw.logger.Output(w)
event := logger.WithLevel(level).CallerSkipFrame(3)
for k, val := range fields {
switch v := val.(type) {
case string:
event.Str(k, v)
event = event.Str(k, v)
case int:
event.Int(k, v)
event = event.Int(k, v)
case float64:
event.Float64(k, v)
event = event.Float64(k, v)
default:
event.Interface(k, val)
event = event.Interface(k, val)
}
}
event.Msg(message)
}
func (lw *LogConfig) Debug(message string, v ...map[string]interface{}) {
if !lw.logger.Debug().Enabled() {
return
func (lw *LogConfig) logWithLevel(level zerolog.Level, message string, fields map[string]interface{}) {
if lw.logger.GetLevel() <= level {
w := os.Stdout
if level >= zerolog.ErrorLevel {
w = os.Stderr
}
lw.log(w, level, message, fields)
}
lw.log(os.Stdout, zerolog.DebugLevel, message, mergeMaps(v))
}
func (lw *LogConfig) Info(message string, v ...map[string]interface{}) {
if !lw.logger.Info().Enabled() {
return
}
lw.log(os.Stdout, zerolog.InfoLevel, message, mergeMaps(v))
func (lw *LogConfig) Debug(message string, fields map[string]interface{}) {
lw.logWithLevel(zerolog.DebugLevel, message, fields)
}
func (lw *LogConfig) Warning(message string, v ...map[string]interface{}) {
lw.log(os.Stdout, zerolog.WarnLevel, message, mergeMaps(v))
func (lw *LogConfig) Info(message string, fields map[string]interface{}) {
lw.logWithLevel(zerolog.InfoLevel, message, fields)
}
func (lw *LogConfig) Error(message string, v ...map[string]interface{}) {
lw.log(os.Stderr, zerolog.ErrorLevel, message, mergeMaps(v))
func (lw *LogConfig) Warning(message string, fields map[string]interface{}) {
lw.logWithLevel(zerolog.WarnLevel, message, fields)
}
func (lw *LogConfig) Critical(message string, v ...map[string]interface{}) {
lw.log(os.Stderr, zerolog.FatalLevel, message, mergeMaps(v))
func (lw *LogConfig) Error(message string, fields map[string]interface{}) {
lw.logWithLevel(zerolog.ErrorLevel, message, fields)
}
func (lw *LogConfig) Critical(message string, fields map[string]interface{}) {
lw.logWithLevel(zerolog.FatalLevel, message, fields)
os.Exit(1)
}
func mergeMaps(maps []map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for _, m := range maps {
for k, v := range m {
result[k] = v
}
}
return result
// Helper function to get a new fields map from the pool
func getFieldsMap() map[string]interface{} {
return fieldMapPool.Get().(map[string]interface{})
}
// Helper function to put a used fields map back into the pool
func putFieldsMap(fields map[string]interface{}) {
for k := range fields {
delete(fields, k)
}
fieldMapPool.Put(fields)
}
+1
View File
@@ -25,6 +25,7 @@ func BenchmarkInfoLog(b *testing.B) {
}()
testsLogger := NewLogger()
b.ResetTimer()
for i := 0; i < b.N; i++ {
testsLogger.Info("test", map[string]interface{}{"test": "test"})
}
-373
View File
@@ -1,373 +0,0 @@
package libpack_logging
import (
"errors"
"io"
"os"
"reflect"
"testing"
"github.com/buger/jsonparser"
"github.com/stretchr/testify/suite"
)
type LoggingTestSuite struct {
suite.Suite
}
var (
testsLogger *LogConfig
)
type stdoutCapture struct {
oldStdout *os.File
readPipe *os.File
}
func (sc *stdoutCapture) StartCapture() {
sc.oldStdout = os.Stdout
sc.readPipe, os.Stdout, _ = os.Pipe()
}
func (sc *stdoutCapture) StopCapture() (string, error) {
if sc.oldStdout == nil || sc.readPipe == nil {
return "", errors.New("StartCapture not called before StopCapture on Stdout")
}
os.Stdout.Close()
os.Stdout = sc.oldStdout
bytes, err := io.ReadAll(sc.readPipe)
if err != nil {
return "", err
}
return string(bytes), nil
}
type stderrCapture struct {
oldStderr *os.File
readPipe *os.File
}
func (sc *stderrCapture) StartCapture() {
sc.oldStderr = os.Stderr
sc.readPipe, os.Stderr, _ = os.Pipe()
}
func (sc *stderrCapture) StopCapture() (string, error) {
if sc.oldStderr == nil || sc.readPipe == nil {
return "", errors.New("StartCapture not called before StopCapture on Stderr")
}
os.Stderr.Close()
os.Stderr = sc.oldStderr
bytes, err := io.ReadAll(sc.readPipe)
if err != nil {
return "", err
}
return string(bytes), nil
}
func (suite *LoggingTestSuite) SetupTest() {
}
func TestLoggingTestSuite(t *testing.T) {
suite.Run(t, new(LoggingTestSuite))
}
func (suite *LoggingTestSuite) TestLogConfig_AllHandlers() {
type args struct {
message string
}
tests := []struct {
name string
args args
wantLevel string
wantMessage string
envMinLogLevel string
loggerType string
stdOutExpect bool
stdErrExpect bool
}{
{
name: "Test log: Error",
loggerType: "Error",
args: args{
message: "This is a error message",
},
wantLevel: "error",
wantMessage: "This is a error message",
stdErrExpect: true,
stdOutExpect: false,
},
{
name: "Test log: Warning",
loggerType: "Warning",
args: args{
message: "This is a warning message",
},
wantLevel: "warn",
wantMessage: "This is a warning message",
stdErrExpect: false,
stdOutExpect: true,
envMinLogLevel: "info",
},
{
name: "Test log: Warning | Min level: Debug",
loggerType: "Warning",
args: args{
message: "This is a warning message",
},
wantLevel: "warn",
wantMessage: "This is a warning message",
stdErrExpect: false,
stdOutExpect: true,
envMinLogLevel: "debug",
},
{
name: "Test log: Info",
loggerType: "Info",
args: args{
message: "This is a info message",
},
wantLevel: "info",
wantMessage: "This is a info message",
stdErrExpect: false,
stdOutExpect: true,
},
{
name: "Test log: Info | Min level: Warn",
loggerType: "Info",
args: args{
message: "This is a info message",
},
wantLevel: "",
wantMessage: "",
stdErrExpect: false,
stdOutExpect: false,
envMinLogLevel: "warn",
},
{
name: "Test log: Warning | Min level: Warn",
loggerType: "Warning",
args: args{
message: "This is a warning message",
},
wantLevel: "warn",
wantMessage: "This is a warning message",
stdErrExpect: false,
stdOutExpect: true,
envMinLogLevel: "warn",
},
{
name: "Test log: Warning | Min level: Error",
loggerType: "Warning",
args: args{
message: "This is an error message",
},
wantLevel: "",
wantMessage: "",
stdErrExpect: false,
stdOutExpect: false,
envMinLogLevel: "error",
},
{
name: "Test log: Debug | Min level: Debug",
loggerType: "Debug",
args: args{
message: "This is a debug message",
},
wantLevel: "debug",
wantMessage: "This is a debug message",
stdErrExpect: false,
stdOutExpect: true,
envMinLogLevel: "debug",
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
if tt.envMinLogLevel != "" {
os.Setenv("LOG_LEVEL", tt.envMinLogLevel)
defer os.Unsetenv("LOG_LEVEL")
}
testsLogger = NewLogger()
captureStdout := stdoutCapture{}
captureStdout.StartCapture()
captureStderr := stderrCapture{}
captureStderr.StartCapture()
reflect.ValueOf(testsLogger).MethodByName(tt.loggerType).Call([]reflect.Value{reflect.ValueOf(tt.args.message)})
stdoutOut, err := captureStdout.StopCapture()
if err != nil {
suite.T().Fatal(err)
}
stderrOut, err := captureStderr.StopCapture()
if err != nil {
suite.T().Fatal(err)
}
if tt.stdErrExpect && !tt.stdOutExpect {
gotLvl, gotMsg, err := getResponseValues(stderrOut, "short_message")
suite.NoError(err, "Failed in [STDERR]: "+tt.name)
suite.Equal(tt.wantLevel, gotLvl, "Failed in [STDERR]: "+tt.name)
suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDERR]: "+tt.name)
suite.Equal("", stdoutOut, "Failed in [STDERR]: "+tt.name)
}
if tt.stdOutExpect && !tt.stdErrExpect {
gotLvl, gotMsg, err := getResponseValues(stdoutOut, "short_message")
suite.NoError(err, "Failed in [STDOUT]: "+tt.name)
suite.Equal(tt.wantLevel, gotLvl, "Failed in [STDOUT]: "+tt.name)
suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDOUT]: "+tt.name)
suite.Equal("", stderrOut, "Failed in [STDOUT]: "+tt.name)
}
if !tt.stdErrExpect && !tt.stdOutExpect {
suite.Equal("", stderrOut, "Failed in [NEITHER]: "+tt.name)
suite.Equal("", stdoutOut, "Failed in [NEITHER]: "+tt.name)
}
os.Unsetenv("LOG_LEVEL")
})
}
}
func (suite *LoggingTestSuite) TestFullMessage() {
type args struct {
extraFields map[string]interface{}
message string
}
extraFields := make(map[string]interface{})
extraFields["_full_message"] = "full message"
tests := []struct {
args args
name string
wantLevel string
wantMessage string
envMinLogLevel string
loggerType string
stdOutExpect bool
stdErrExpect bool
}{
{
name: "Test log: Error",
loggerType: "Error",
args: args{
message: "This is a error message",
extraFields: extraFields,
},
wantLevel: "error",
wantMessage: extraFields["_full_message"].(string),
stdErrExpect: true,
stdOutExpect: false,
},
{
name: "Test log: Info",
loggerType: "Info",
args: args{
message: "This is a info message",
extraFields: extraFields,
},
wantMessage: extraFields["_full_message"].(string),
stdErrExpect: false,
stdOutExpect: true,
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
if tt.envMinLogLevel != "" {
os.Setenv("LOG_LEVEL", tt.envMinLogLevel)
defer os.Unsetenv("LOG_LEVEL")
}
testsLogger = NewLogger()
captureStdout := stdoutCapture{}
captureStdout.StartCapture()
captureStderr := stderrCapture{}
captureStderr.StartCapture()
reflect.ValueOf(testsLogger).MethodByName(tt.loggerType).Call([]reflect.Value{
reflect.ValueOf(tt.args.message),
reflect.ValueOf(tt.args.extraFields),
})
stdoutOut, err := captureStdout.StopCapture()
if err != nil {
suite.T().Fatal(err)
}
stderrOut, err := captureStderr.StopCapture()
if err != nil {
suite.T().Fatal(err)
}
if tt.stdErrExpect && !tt.stdOutExpect {
_, gotMsg, err := getResponseValues(stderrOut, "_full_message")
suite.NoError(err, "Failed in [STDERR]: "+tt.name)
suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDERR]: "+tt.name)
}
if tt.stdOutExpect && !tt.stdErrExpect {
_, gotMsg, err := getResponseValues(stdoutOut, "_full_message")
suite.NoError(err, "Failed in [STDOUT]: "+tt.name)
suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDOUT]: "+tt.name)
}
os.Unsetenv("LOG_LEVEL")
})
}
}
func Test_getResponseValues(t *testing.T) {
type args struct {
sourceJson string
}
tests := []struct {
name string
args args
wantGotLvl string
wantGotMsg string
wantErr bool
}{
{
name: "Test with json",
args: args{
sourceJson: `{"level": "debug", "short_message": "hello world"`,
},
wantGotLvl: "debug",
wantGotMsg: "hello world",
wantErr: false,
},
{
name: "Test with json, wrong message field",
args: args{
sourceJson: `{"level": "debug", "message": "hello world"`,
},
wantGotLvl: "debug",
wantGotMsg: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotGotLvl, gotGotMsg, err := getResponseValues(tt.args.sourceJson, "short_message")
if (err != nil) != tt.wantErr {
t.Errorf("getResponseValues() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotGotLvl != tt.wantGotLvl {
t.Errorf("getResponseValues() gotGotLvl = %v, want %v", gotGotLvl, tt.wantGotLvl)
}
if gotGotMsg != tt.wantGotMsg {
t.Errorf("getResponseValues() gotGotMsg = %v, want %v", gotGotMsg, tt.wantGotMsg)
}
})
}
}
func getResponseValues(sourceJson string, key string) (gotLvl, gotMsg string, err error) {
gotLvl, err = jsonparser.GetString([]byte(sourceJson), "level")
if err != nil {
return
}
gotMsg, err = jsonparser.GetString([]byte(sourceJson), key)
return
}
+1 -1
View File
@@ -72,7 +72,7 @@ func parseConfig() {
proxy.WithClient(c.Client.FastProxyClient) // setting the global proxy client here instead of per request
c.Server.EnableApi = getDetailsFromEnv("ENABLE_API", false)
c.Server.ApiPort = getDetailsFromEnv("API_PORT", 9090)
c.Api.BannedUsersFile = getDetailsFromEnv("BANNED_USERS_FILE", "/go/src/app/banned_users.json")
c.Api.BannedUsersFile = getDetailsFromEnv("BANNED_USERS_FILE", "/app/banned_users.json")
c.Server.PurgeOnCrawl = getDetailsFromEnv("PURGE_METRICS_ON_CRAWL", false)
c.Server.PurgeEvery = getDetailsFromEnv("PURGE_METRICS_ON_TIMER", 0)
cfg = &c
+1
View File
@@ -33,6 +33,7 @@ func (suite *Tests) SetupTest() {
},
)
parseConfig()
enableApi()
StartMonitoringServer()
cfg.Logger = libpack_logging.NewLogger()
// Setup environment variables here if needed
+49 -28
View File
@@ -3,57 +3,78 @@ package libpack_monitoring
import (
"fmt"
"os"
"sort"
"strings"
"unicode"
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
)
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
var err error
if labels == nil {
labels = make(map[string]string)
}
// Adding default labels
labels["microservice"] = libpack_config.PKG_NAME
labels["pod"], err = os.Hostname()
if err != nil {
if podName, err := os.Hostname(); err == nil {
labels["pod"] = podName
} else {
labels["pod"] = "unknown"
}
var sb strings.Builder
if ms.metrics_prefix != "" {
complete_name = ms.metrics_prefix + "_" + name
} else {
complete_name = name
sb.WriteString(ms.metrics_prefix)
sb.WriteString("_")
}
if labels != nil {
keys := make([]string, 0, len(labels))
for k := range labels {
keys = append(keys, k)
sb.WriteString(name)
if len(labels) > 0 {
sb.WriteString("{")
first := true
for k, v := range labels {
if !first {
sb.WriteString(",")
}
sb.WriteString(k)
sb.WriteString("=\"")
sb.WriteString(v)
sb.WriteString("\"")
first = false
}
sort.Strings(keys)
complete_name += "{"
for _, k := range keys {
complete_name += k + "=\"" + labels[k] + "\","
}
complete_name = strings.TrimSuffix(complete_name, ",")
complete_name += "}"
sb.WriteString("}")
}
return
return sb.String()
}
// validate_metrics_name validates the name of the metric to adhere to the Prometheus naming conventions
// https://prometheus.io/docs/practices/naming/
func validate_metrics_name(name string) error {
// replace all spaces with underscores and remove all other non-alphanumeric characters
name_new := strings.ReplaceAll(name, " ", "_")
name_new = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
return r
var sb strings.Builder // Use strings.Builder for efficient string concatenation
// Track if the last character was an underscore to avoid duplicate underscores
lastWasUnderscore := false
for _, r := range name {
// Convert spaces to underscores and skip non-alphanumeric characters except underscores
if r == ' ' || (unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') {
if r == ' ' || r == '_' {
if lastWasUnderscore {
continue // Skip if the previous character was also an underscore
}
r = '_' // Convert spaces to underscores
lastWasUnderscore = true
} else {
lastWasUnderscore = false
}
sb.WriteRune(r) // Add valid characters to the builder
}
return -1
}, name_new)
name_new = strings.ReplaceAll(name_new, "__", "_")
name_new = strings.Trim(name_new, "_")
}
// Trim leading and trailing underscores
name_new := strings.Trim(sb.String(), "_")
// Check if the processed name matches the original input
if name_new != name {
return fmt.Errorf("Invalid metric name: %s, expected %s", name, name_new)
}
+1 -1
View File
@@ -37,7 +37,7 @@ func loadRatelimitConfig() error {
cfg.Logger.Debug("Failed to load config", map[string]interface{}{"path": path, "error": err})
}
cfg.Logger.Error("Rate limit config not found")
cfg.Logger.Error("Rate limit config not found", map[string]interface{}{"paths": paths})
return os.ErrNotExist
}
+2 -2
View File
@@ -75,7 +75,7 @@ func healthCheck(c *fiber.Ctx) error {
return err
}
}
cfg.Logger.Debug("Health check returning OK")
cfg.Logger.Debug("Health check returning OK", nil)
c.Status(200).SendString("Health check OK")
return nil
}
@@ -121,7 +121,7 @@ func processGraphQLRequest(c *fiber.Ctx) error {
}
if parsedResult.shouldIgnore {
cfg.Logger.Debug("Request passed as-is - probably not a GraphQL")
cfg.Logger.Debug("Request passed as-is - probably not a GraphQL", nil)
return proxyTheRequest(c, parsedResult.activeEndpoint)
}
View File
+1
View File
@@ -0,0 +1 @@
{}
@@ -80,6 +80,9 @@ spec:
env:
- name: HASURA_GRAPHQL_DATABASE_URL
value: postgres://postgres:xxx@yyy.read-only:5432/postgres
# POINT METADATA TO THE RW database (!!!)
- name: HASURA_GRAPHQL_METADATA_DATABASE_URL
value: postgres://postgres:xxx@yyy:5432/postgres
- name: HASURA_GRAPHQL_ENABLE_CONSOLE
value: "true"
- name: HASURA_GRAPHQL_DEV_MODE
@@ -92,7 +95,7 @@ spec:
value: "20"
- name: HASURA_GRAPHQL_LOG_LEVEL
value: "error"
- name: HASURA_PORT
- name: HASURA_GRAPHQL_SERVER_PORT
value: "8088"
- name: graphql-proxy