Compare commits

...

6 Commits

Author SHA1 Message Date
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
11 changed files with 202 additions and 557 deletions
+52 -44
View File
@@ -14,23 +14,31 @@ 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
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 +51,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,25 +62,15 @@ 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)
@@ -92,15 +90,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 +103,51 @@ 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
})
}
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
}
-36
View File
@@ -110,39 +110,3 @@ func (suite *CacheTestSuite) Test_CacheExpire() {
})
}
}
func (suite *CacheTestSuite) Test_CacheCleanExpiredEntries() {
cache := New(5 * time.Second)
tests := []struct {
name string
cache_value string
ttl time.Duration
}{
{
name: "test1",
cache_value: "test1-123",
ttl: 2 * time.Second,
},
{
name: "test2",
cache_value: "test2-123",
ttl: 5 * time.Second,
},
}
for _, tt := range tests {
suite.T().Run(tt.name, func(t *testing.T) {
cache.Set(tt.name, []byte(tt.name), tt.ttl)
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))
})
}
}
+11 -12
View File
@@ -3,20 +3,19 @@ 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/VictoriaMetrics/metrics v1.33.1
github.com/avast/retry-go/v4 v4.6.0
github.com/goccy/go-json v0.10.2
github.com/gofiber/fiber/v2 v2.52.2
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/lukaszraczylo/go-simple-graphql v1.2.12
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/valyala/fasthttp v1.52.0
)
@@ -24,7 +23,7 @@ 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.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+22 -24
View File
@@ -1,11 +1,9 @@
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=
@@ -13,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/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.12 h1:JI2DseFRby/sxSk51DrZPHydENG74fhfA5x+iqHhbnc=
github.com/lukaszraczylo/go-simple-graphql v1.2.12/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=
@@ -61,8 +59,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz
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/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=
@@ -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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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
}
+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)
}
@@ -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