mirror of
https://github.com/lukaszraczylo/go-simple-graphql.git
synced 2026-10-13 00:48:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42170f9b8e | ||
|
|
4f438b7eac | ||
|
|
569867d04b | ||
|
|
9a58df7a54 | ||
|
|
4e60a90ce4 |
Vendored
+52
-47
@@ -3,7 +3,6 @@ package libpack_cache
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"flag"
|
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,26 +14,32 @@ type CacheEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
bytePool sync.Pool
|
compressPool sync.Pool
|
||||||
entries sync.Map
|
decompressPool sync.Pool
|
||||||
globalTTL time.Duration
|
entries sync.Map
|
||||||
|
globalTTL time.Duration
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(globalTTL time.Duration) *Cache {
|
func New(globalTTL time.Duration) *Cache {
|
||||||
cache := &Cache{
|
cache := &Cache{
|
||||||
globalTTL: globalTTL,
|
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.
|
go cache.cleanupRoutine(globalTTL)
|
||||||
cache.bytePool.New = func() interface{} {
|
|
||||||
return make([]byte, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the cache cleanup.
|
|
||||||
if flag.Lookup("test.v") == nil {
|
|
||||||
go cache.cleanupRoutine(globalTTL)
|
|
||||||
}
|
|
||||||
return cache
|
return cache
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,10 +51,10 @@ func (c *Cache) cleanupRoutine(globalTTL time.Duration) {
|
|||||||
c.CleanExpiredEntries()
|
c.CleanExpiredEntries()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
|
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
|
||||||
c.Lock()
|
c.Lock() // use the lock
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
expiresAt := time.Now().Add(ttl)
|
expiresAt := time.Now().Add(ttl)
|
||||||
|
|
||||||
compressedValue, err := c.compress(value)
|
compressedValue, err := c.compress(value)
|
||||||
@@ -57,25 +62,15 @@ func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
|
|||||||
return
|
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{
|
entry := CacheEntry{
|
||||||
Value: b,
|
Value: compressedValue,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
}
|
}
|
||||||
c.entries.Store(key, entry)
|
c.entries.Store(key, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Get(key string) ([]byte, bool) {
|
func (c *Cache) Get(key string) ([]byte, bool) {
|
||||||
c.RLock()
|
c.RLock() // use the read lock
|
||||||
defer c.RUnlock()
|
defer c.RUnlock()
|
||||||
|
|
||||||
entry, ok := c.entries.Load(key)
|
entry, ok := c.entries.Load(key)
|
||||||
@@ -95,15 +90,11 @@ func (c *Cache) Delete(key string) {
|
|||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
entry, ok := c.entries.Load(key)
|
_, ok := c.entries.Load(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the byte slice to the pool.
|
|
||||||
c.bytePool.Put(entry.(CacheEntry).Value)
|
|
||||||
|
|
||||||
// Delete the entry from the cache.
|
|
||||||
c.entries.Delete(key)
|
c.entries.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,37 +103,51 @@ func (c *Cache) CleanExpiredEntries() {
|
|||||||
c.entries.Range(func(key, value interface{}) bool {
|
c.entries.Range(func(key, value interface{}) bool {
|
||||||
entry := value.(CacheEntry)
|
entry := value.(CacheEntry)
|
||||||
if entry.ExpiresAt.Before(now) {
|
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)
|
c.entries.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true to continue iterating over the map.
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) compress(data []byte) ([]byte, error) {
|
func (c *Cache) compress(data []byte) ([]byte, error) {
|
||||||
|
w := c.compressPool.Get().(*gzip.Writer)
|
||||||
|
defer c.compressPool.Put(w)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := gzip.NewWriter(&buf)
|
w.Reset(&buf)
|
||||||
_, err := w.Write(data)
|
if _, err := w.Write(data); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = w.Close()
|
if err := w.Close(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) decompress(data []byte) ([]byte, error) {
|
func (c *Cache) decompress(data []byte) ([]byte, error) {
|
||||||
r, err := gzip.NewReader(bytes.NewBuffer(data))
|
r, ok := c.decompressPool.Get().(*gzip.Reader)
|
||||||
if err != nil {
|
if !ok || r == nil {
|
||||||
return nil, err
|
// 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()
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+46
@@ -0,0 +1,46 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
-36
@@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+50
-39
@@ -3,60 +3,71 @@ package gql
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/avast/retry-go/v4"
|
"github.com/avast/retry-go/v4"
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bufferPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func (qe *QueryExecutor) executeQuery() ([]byte, error) {
|
func (qe *QueryExecutor) executeQuery() ([]byte, error) {
|
||||||
httpRequest, err := http.NewRequest("POST", qe.endpoint, bytes.NewBuffer(qe.Query))
|
// Reuse buffer from pool to avoid allocations
|
||||||
|
buf := bufferPool.Get().(*bytes.Buffer)
|
||||||
|
defer bufferPool.Put(buf)
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
_, err := buf.Write(qe.Query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Error("Can't create HTTP request;", map[string]interface{}{"error": err})
|
qe.Logger.Error("Can't write to buffer", map[string]interface{}{"error": err})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRequest, err := http.NewRequest(http.MethodPost, qe.endpoint, buf)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Error("Can't create HTTP request", map[string]interface{}{"error": err})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range qe.Headers {
|
for key, value := range qe.Headers {
|
||||||
strValue, ok := value.(string)
|
httpRequest.Header.Set(key, fmt.Sprint(value))
|
||||||
if !ok {
|
|
||||||
strValue = fmt.Sprintf("%v", value)
|
|
||||||
}
|
|
||||||
httpRequest.Header.Set(key, strValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var retries_max int
|
retriesMax := 1
|
||||||
|
|
||||||
if qe.Retries {
|
if qe.Retries {
|
||||||
qe.Logger.Debug("Retries enabled - setting max retries", map[string]interface{}{"retries": qe.retries_number})
|
qe.Logger.Debug("Retries enabled", map[string]interface{}{"retries": qe.retries_number})
|
||||||
retries_max = qe.retries_number
|
retriesMax = qe.retries_number
|
||||||
} else {
|
} else {
|
||||||
qe.Logger.Debug("Retries disabled - setting max retries", map[string]interface{}{"retries": 1})
|
qe.Logger.Debug("Retries disabled", map[string]interface{}{"retries": 1})
|
||||||
retries_max = 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpResponse *http.Response
|
|
||||||
var queryResult queryResults
|
var queryResult queryResults
|
||||||
|
|
||||||
err = retry.Do(
|
err = retry.Do(
|
||||||
func() error {
|
func() error {
|
||||||
httpResponse, err = qe.client.Do(httpRequest)
|
httpResponse, err := qe.client.Do(httpRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while executing http request", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error executing HTTP request", map[string]interface{}{"error": err.Error()})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_, err := io.Copy(io.Discard, httpResponse.Body)
|
_, err := io.Copy(io.Discard, httpResponse.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while discarding http response body;", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error discarding HTTP response body", map[string]interface{}{"error": err.Error()})
|
||||||
}
|
}
|
||||||
httpResponse.Body.Close()
|
httpResponse.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 204 {
|
if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusNoContent {
|
||||||
return fmt.Errorf("HTTP error - unacceptable status code: \"%s\" for \"%s\"", httpResponse.Status, httpRequest.URL)
|
return fmt.Errorf("HTTP error - status code: %s for %s", httpResponse.Status, httpRequest.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader io.ReadCloser
|
var reader io.ReadCloser
|
||||||
@@ -64,8 +75,8 @@ func (qe *QueryExecutor) executeQuery() ([]byte, error) {
|
|||||||
if encoding == "gzip" {
|
if encoding == "gzip" {
|
||||||
reader, err = gzip.NewReader(httpResponse.Body)
|
reader, err = gzip.NewReader(httpResponse.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while creating gzip reader;", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error creating gzip reader", map[string]interface{}{"error": err.Error()})
|
||||||
return fmt.Errorf("Error while creating gzip reader: %s", err.Error())
|
return fmt.Errorf("error creating gzip reader: %w", err)
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
} else {
|
} else {
|
||||||
@@ -74,50 +85,50 @@ func (qe *QueryExecutor) executeQuery() ([]byte, error) {
|
|||||||
|
|
||||||
body, err := io.ReadAll(reader)
|
body, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while reading http response;", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error reading HTTP response", map[string]interface{}{"error": err.Error()})
|
||||||
return fmt.Errorf("Error while reading http response: %s", err.Error())
|
return fmt.Errorf("error reading HTTP response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(body, &queryResult)
|
err = json.Unmarshal(body, &queryResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while unmarshalling http response;", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error unmarshalling HTTP response", map[string]interface{}{"error": err.Error()})
|
||||||
return fmt.Errorf("Error while unmarshalling http response: %s", err.Error())
|
return fmt.Errorf("error unmarshalling HTTP response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
retry.OnRetry(func(n uint, err error) {
|
retry.OnRetry(func(n uint, err error) {
|
||||||
qe.Logger.Warning("Retrying query", map[string]interface{}{"error": err.Error(), "attempt": n})
|
qe.Logger.Warning("Retrying query", map[string]interface{}{"error": err.Error(), "attempt": n + 1})
|
||||||
}),
|
}),
|
||||||
retry.Attempts(uint(retries_max)),
|
retry.Attempts(uint(retriesMax)),
|
||||||
retry.DelayType(retry.BackOffDelay),
|
retry.DelayType(retry.BackOffDelay),
|
||||||
retry.Delay(time.Duration(qe.retries_delay)),
|
retry.Delay(time.Duration(qe.retries_delay)),
|
||||||
retry.LastErrorOnly(true),
|
retry.LastErrorOnly(true),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while executing http request - target server", map[string]interface{}{"error": err.Error()})
|
qe.Logger.Debug("Error executing HTTP request", map[string]interface{}{"error": err.Error()})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(queryResult.Errors) > 0 {
|
if len(queryResult.Errors) > 0 {
|
||||||
qe.Logger.Debug("Error while executing query;", map[string]interface{}{"error": queryResult.Errors})
|
qe.Logger.Debug("Error executing query", map[string]interface{}{"error": queryResult.Errors})
|
||||||
return nil, fmt.Errorf("Error while executing query: %s", queryResult.Errors)
|
return nil, fmt.Errorf("error executing query: %s", queryResult.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryResult.Data == nil {
|
if queryResult.Data == nil {
|
||||||
qe.Logger.Debug("Error while executing query", map[string]interface{}{"error": "no data"})
|
qe.Logger.Debug("Error executing query", map[string]interface{}{"error": "no data"})
|
||||||
return nil, fmt.Errorf("Error while executing query: no data")
|
return nil, errors.New("error executing query: no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
json_data, err := json.Marshal(queryResult.Data)
|
jsonData, err := json.Marshal(queryResult.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
qe.Logger.Debug("Error while marshalling query result;", map[string]interface{}{"error": err.Error(), "data": queryResult.Data})
|
qe.Logger.Debug("Error marshalling query result", map[string]interface{}{"error": err.Error(), "data": queryResult.Data})
|
||||||
return nil, fmt.Errorf("Error while marshalling query result: %s. Data: %s", err.Error(), queryResult.Data)
|
return nil, fmt.Errorf("error marshalling query result: %w. Data: %s", err, queryResult.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if qe.CacheKey != "no-cache" {
|
if qe.CacheKey != "no-cache" {
|
||||||
qe.cache.Set(qe.CacheKey, json_data, qe.CacheTTL)
|
qe.cache.Set(qe.CacheKey, jsonData, qe.CacheTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_data, nil
|
return jsonData, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ module github.com/lukaszraczylo/go-simple-graphql
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/avast/retry-go/v4 v4.5.1
|
github.com/avast/retry-go/v4 v4.6.0
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/gookit/goutil v0.6.15
|
github.com/gookit/goutil v0.6.15
|
||||||
github.com/rs/zerolog v1.32.0
|
github.com/rs/zerolog v1.32.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/net v0.20.0
|
golang.org/x/net v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -18,9 +18,9 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/term v0.16.0 // indirect
|
golang.org/x/term v0.20.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.15.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
|
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
|
||||||
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
|
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/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -22,25 +22,25 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
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 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
+3
-3
@@ -50,12 +50,12 @@ func (suite *Tests) TestBaseClient_decodeResponse() {
|
|||||||
response []byte
|
response []byte
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want any
|
want any
|
||||||
wantErr bool
|
name string
|
||||||
setType string
|
setType string
|
||||||
wantType string
|
wantType string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "TestBaseClient_decodeResponse_mapstring",
|
name: "TestBaseClient_decodeResponse_mapstring",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (b *BaseClient) createHttpClient() (http_client *http.Client) {
|
|||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
b.Logger.Debug("Using HTTP/1.1 over http")
|
b.Logger.Debug("Using HTTP/1.1 over http", nil)
|
||||||
} else if strings.HasPrefix(b.endpoint, "https://") {
|
} else if strings.HasPrefix(b.endpoint, "https://") {
|
||||||
tlsClientConfig := &tls.Config{}
|
tlsClientConfig := &tls.Config{}
|
||||||
if strings.HasPrefix(b.endpoint, "https://") {
|
if strings.HasPrefix(b.endpoint, "https://") {
|
||||||
@@ -46,9 +46,9 @@ func (b *BaseClient) createHttpClient() (http_client *http.Client) {
|
|||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
b.Logger.Debug("Using HTTP/2 over https")
|
b.Logger.Debug("Using HTTP/2 over https", nil)
|
||||||
} else {
|
} else {
|
||||||
b.Logger.Critical("Invalid endpoint - neither http or https")
|
b.Logger.Critical("Invalid endpoint - neither http or https", nil)
|
||||||
}
|
}
|
||||||
return http_client
|
return http_client
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-78
@@ -1,8 +1,8 @@
|
|||||||
package libpack_logging
|
package libpack_logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,124 +11,110 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
baseLogger zerolog.Logger
|
logger zerolog.Logger
|
||||||
errorLogger zerolog.Logger
|
|
||||||
nopLogger zerolog.Logger
|
|
||||||
mu sync.Mutex
|
|
||||||
minLevel zerolog.Level
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
func init() {
|
||||||
zerolog.TimeFieldFormat = time.RFC3339
|
zerolog.TimeFieldFormat = time.RFC3339
|
||||||
zerolog.MessageFieldName = "msg"
|
zerolog.MessageFieldName = "short_message"
|
||||||
zerolog.TimestampFieldName = "ts"
|
zerolog.TimestampFieldName = "timestamp"
|
||||||
zerolog.LevelFieldName = "level"
|
zerolog.LevelFieldName = "level"
|
||||||
zerolog.LevelFatalValue = "critical"
|
zerolog.LevelFatalValue = "critical"
|
||||||
}
|
|
||||||
|
|
||||||
func getMinLogLevel() zerolog.Level {
|
baseLogger = zerolog.New(os.Stdout).With().Timestamp().Logger()
|
||||||
levelStr := strings.ToLower(envutil.Getenv("LOG_LEVEL", "info"))
|
|
||||||
return matchLogLevel(levelStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add function set the minimum log level instead of using environment variable
|
switch logLevel := envutil.Getenv("LOG_LEVEL", "info"); logLevel {
|
||||||
func (lc *LogConfig) SetLogLevel(level string) {
|
|
||||||
lc.mu.Lock()
|
|
||||||
defer lc.mu.Unlock()
|
|
||||||
lc.minLevel = matchLogLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match the log level provided as string with the log level provided as zerolog.Level
|
|
||||||
func matchLogLevel(level string) zerolog.Level {
|
|
||||||
switch level {
|
|
||||||
case "debug":
|
case "debug":
|
||||||
return zerolog.DebugLevel
|
baseLogger = baseLogger.Level(zerolog.DebugLevel)
|
||||||
case "warn":
|
case "warn":
|
||||||
return zerolog.WarnLevel
|
baseLogger = baseLogger.Level(zerolog.WarnLevel)
|
||||||
case "error":
|
case "error":
|
||||||
return zerolog.ErrorLevel
|
baseLogger = baseLogger.Level(zerolog.ErrorLevel)
|
||||||
case "critical":
|
|
||||||
return zerolog.FatalLevel
|
|
||||||
default:
|
default:
|
||||||
return zerolog.InfoLevel
|
baseLogger = baseLogger.Level(zerolog.InfoLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogger() *LogConfig {
|
func NewLogger() *LogConfig {
|
||||||
zl := zerolog.New(os.Stdout).With().Timestamp().Logger()
|
return &LogConfig{logger: baseLogger}
|
||||||
// Determine the minimum log level from environment variable
|
|
||||||
return &LogConfig{
|
|
||||||
baseLogger: zl,
|
|
||||||
errorLogger: zl.Output(os.Stderr),
|
|
||||||
nopLogger: zerolog.Nop(),
|
|
||||||
minLevel: getMinLogLevel(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) getLogger(level zerolog.Level) zerolog.Logger {
|
func (lw *LogConfig) log(w io.Writer, level zerolog.Level, message string, fields map[string]interface{}) {
|
||||||
if level >= zerolog.ErrorLevel {
|
logger := lw.logger.Output(w)
|
||||||
return lc.errorLogger
|
|
||||||
}
|
|
||||||
if level < lc.minLevel {
|
|
||||||
return lc.nopLogger
|
|
||||||
}
|
|
||||||
return lc.baseLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LogConfig) Log(level zerolog.Level, message string, fields []map[string]interface{}) {
|
|
||||||
if level < lc.minLevel {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger := lc.getLogger(level)
|
|
||||||
event := logger.WithLevel(level).CallerSkipFrame(3)
|
event := logger.WithLevel(level).CallerSkipFrame(3)
|
||||||
if len(fields) == 0 {
|
|
||||||
event.Msg(message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
field := fields[0]
|
for k, val := range fields {
|
||||||
for k, val := range field {
|
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case string:
|
case string:
|
||||||
event.Str(k, v)
|
event = event.Str(k, v)
|
||||||
case int:
|
case int:
|
||||||
event.Int(k, v)
|
event = event.Int(k, v)
|
||||||
case float64:
|
case float64:
|
||||||
event.Float64(k, v)
|
event = event.Float64(k, v)
|
||||||
default:
|
default:
|
||||||
event.Interface(k, v)
|
event = event.Interface(k, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.Msg(message)
|
event.Msg(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) Info(message string, fields ...map[string]interface{}) {
|
func (lw *LogConfig) logWithLevel(level zerolog.Level, message string, fields map[string]interface{}) {
|
||||||
lc.Log(zerolog.InfoLevel, message, fields)
|
if lw.logger.GetLevel() <= level {
|
||||||
|
w := os.Stdout
|
||||||
|
if level >= zerolog.ErrorLevel {
|
||||||
|
w = os.Stderr
|
||||||
|
}
|
||||||
|
lw.log(w, level, message, fields)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) Debug(message string, fields ...map[string]interface{}) {
|
func (lw *LogConfig) Debug(message string, fields map[string]interface{}) {
|
||||||
lc.Log(zerolog.DebugLevel, message, fields)
|
lw.logWithLevel(zerolog.DebugLevel, message, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) Warn(message string, fields ...map[string]interface{}) {
|
func (lw *LogConfig) Info(message string, fields map[string]interface{}) {
|
||||||
lc.Log(zerolog.WarnLevel, message, fields)
|
lw.logWithLevel(zerolog.InfoLevel, message, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// alias Warning to Warn
|
func (lw *LogConfig) Warning(message string, fields map[string]interface{}) {
|
||||||
func (lc *LogConfig) Warning(message string, fields ...map[string]interface{}) {
|
lw.logWithLevel(zerolog.WarnLevel, message, fields)
|
||||||
lc.Warn(message, fields...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) Error(message string, fields ...map[string]interface{}) {
|
func (lw *LogConfig) Error(message string, fields map[string]interface{}) {
|
||||||
lc.Log(zerolog.ErrorLevel, message, fields)
|
lw.logWithLevel(zerolog.ErrorLevel, message, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LogConfig) Critical(message string, fields ...map[string]interface{}) {
|
func (lw *LogConfig) Critical(message string, fields map[string]interface{}) {
|
||||||
lc.Log(zerolog.FatalLevel, message, fields)
|
lw.logWithLevel(zerolog.FatalLevel, message, fields)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// alias Fatal to Critical
|
// Helper function to get a new fields map from the pool
|
||||||
func (lc *LogConfig) Fatal(message string, fields ...map[string]interface{}) {
|
func getFieldsMap() map[string]interface{} {
|
||||||
lc.Critical(message, fields...)
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,69 +2,84 @@ package gql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"github.com/gookit/goutil"
|
"github.com/gookit/goutil"
|
||||||
"github.com/gookit/goutil/strutil"
|
"github.com/gookit/goutil/strutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var jsonBufferPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new([]byte)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseClient) convertToJSON(v any) []byte {
|
func (b *BaseClient) convertToJSON(v any) []byte {
|
||||||
|
jsonBuffer := jsonBufferPool.Get().(*[]byte)
|
||||||
|
defer jsonBufferPool.Put(jsonBuffer)
|
||||||
|
|
||||||
|
*jsonBuffer = (*jsonBuffer)[:0]
|
||||||
jsonData, err := json.Marshal(v)
|
jsonData, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Logger.Error("Can't convert to json;", map[string]interface{}{"error": err.Error()})
|
b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return jsonData
|
*jsonBuffer = append(*jsonBuffer, jsonData...)
|
||||||
|
return *jsonBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseClient) compileQuery(query_partials ...any) *Query {
|
func (b *BaseClient) compileQuery(queryPartials ...any) *Query {
|
||||||
q := new(Query)
|
var query string
|
||||||
for _, partial := range query_partials {
|
var variables map[string]interface{}
|
||||||
|
for _, partial := range queryPartials {
|
||||||
switch val := partial.(type) {
|
switch val := partial.(type) {
|
||||||
case string:
|
case string:
|
||||||
q.Query = val
|
query = val
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
q.Variables = val
|
variables = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q.Query == "" {
|
|
||||||
b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"})
|
if query == "" {
|
||||||
|
b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
q.JsonQuery = b.convertToJSON(q)
|
|
||||||
return q
|
jsonQuery := b.convertToJSON(&Query{Query: query, Variables: variables})
|
||||||
|
return &Query{
|
||||||
|
Query: query,
|
||||||
|
Variables: variables,
|
||||||
|
JsonQuery: jsonQuery,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (returned_value any, err error) {
|
func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (any, error) {
|
||||||
compiledQuery := b.compileQuery(query, variables)
|
compiledQuery := b.compileQuery(query, variables)
|
||||||
if compiledQuery.JsonQuery == nil {
|
if compiledQuery.JsonQuery == nil {
|
||||||
b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"})
|
b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"})
|
||||||
return nil, fmt.Errorf("Can't compile query")
|
return nil, fmt.Errorf("can't compile query")
|
||||||
}
|
}
|
||||||
|
b.Logger.Debug("Compiled query", map[string]interface{}{"query": compiledQuery})
|
||||||
|
|
||||||
b.Logger.Debug("Compiled query;", map[string]interface{}{"query": compiledQuery})
|
enableCache, enableRetries, recompileRequired := compiledQuery.parseHeadersAndVariables(headers)
|
||||||
enable_cache, enable_retries, recompile_required := compiledQuery.parseHeadersAndVariables(headers)
|
if recompileRequired {
|
||||||
|
|
||||||
if recompile_required {
|
|
||||||
compiledQuery = b.compileQuery(query, variables)
|
compiledQuery = b.compileQuery(query, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryHash string
|
var queryHash string
|
||||||
|
if (enableCache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") {
|
||||||
if (enable_cache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") {
|
b.Logger.Debug("Cache enabled", nil)
|
||||||
b.Logger.Debug("Cache enabled")
|
|
||||||
queryHash = calculateHash(compiledQuery)
|
queryHash = calculateHash(compiledQuery)
|
||||||
cached_value := b.cacheLookup(queryHash)
|
if cachedValue := b.cacheLookup(queryHash); cachedValue != nil {
|
||||||
if cached_value != nil {
|
|
||||||
b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery})
|
b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery})
|
||||||
return cached_value, nil
|
return cachedValue, nil
|
||||||
} else {
|
|
||||||
b.Logger.Debug("Cache miss", map[string]interface{}{"query": compiledQuery})
|
|
||||||
}
|
}
|
||||||
|
b.Logger.Debug("Cache miss", map[string]interface{}{"query": compiledQuery})
|
||||||
}
|
}
|
||||||
|
|
||||||
if enable_retries || b.retries_enable {
|
if enableRetries || b.retries_enable {
|
||||||
b.Logger.Debug("Retries enabled")
|
b.Logger.Debug("Retries enabled", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
q := &QueryExecutor{
|
q := &QueryExecutor{
|
||||||
@@ -74,33 +89,37 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
|
|||||||
CacheKey: func() string {
|
CacheKey: func() string {
|
||||||
if queryHash != "" {
|
if queryHash != "" {
|
||||||
return queryHash
|
return queryHash
|
||||||
} else {
|
|
||||||
return "no-cache"
|
|
||||||
}
|
}
|
||||||
|
return "no-cache"
|
||||||
}(),
|
}(),
|
||||||
Retries: enable_retries || b.retries_enable,
|
Retries: enableRetries || b.retries_enable,
|
||||||
}
|
}
|
||||||
defer func() { q = nil }()
|
|
||||||
rv, err := q.executeQuery()
|
rv, err := q.executeQuery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Logger.Error("Error while executing query;", map[string]interface{}{"error": err.Error()})
|
b.Logger.Error("Error executing query", map[string]interface{}{"error": err.Error()})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
returned_value, err = q.decodeResponse(rv)
|
|
||||||
return returned_value, err
|
return q.decodeResponse(rv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable_cache bool, enable_retries bool, recompile_required bool) {
|
func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) {
|
||||||
if headers != nil {
|
enableCache, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlcache"))
|
||||||
enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlcache"))
|
enableRetries, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlretries"))
|
||||||
enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlretries"))
|
|
||||||
}
|
|
||||||
if q.Variables != nil {
|
if q.Variables != nil {
|
||||||
enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlcache"))
|
varEnableCache, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlcache"))
|
||||||
enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries"))
|
varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries"))
|
||||||
delete(q.Variables, "gqlcache")
|
enableCache = enableCache || varEnableCache
|
||||||
delete(q.Variables, "gqlretries")
|
enableRetries = enableRetries || varEnableRetries
|
||||||
recompile_required = true
|
|
||||||
|
if varEnableCache || varEnableRetries {
|
||||||
|
delete(q.Variables, "gqlcache")
|
||||||
|
delete(q.Variables, "gqlretries")
|
||||||
|
recompileRequired = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return enableCache, enableRetries, recompileRequired
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-3
@@ -1,6 +1,8 @@
|
|||||||
package gql
|
package gql
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func Benchmark_compileQuery(bn *testing.B) {
|
func Benchmark_compileQuery(bn *testing.B) {
|
||||||
b := NewConnection()
|
b := NewConnection()
|
||||||
@@ -12,12 +14,69 @@ func Benchmark_compileQuery(bn *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_convertToJson(bn *testing.B) {
|
func Benchmark_compileQueryWithVariables(bn *testing.B) {
|
||||||
b := NewConnection()
|
b := NewConnection()
|
||||||
|
variables := map[string]interface{}{
|
||||||
|
"var1": "value1",
|
||||||
|
}
|
||||||
bn.ResetTimer()
|
bn.ResetTimer()
|
||||||
for i := 0; i < bn.N; i++ {
|
for i := 0; i < bn.N; i++ {
|
||||||
b.convertToJSON(
|
b.compileQuery(
|
||||||
`query { viewer { login } }`,
|
`query { viewer { login } }`,
|
||||||
|
variables,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_convertToJson(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
query := `query { viewer { login } }`
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
b.convertToJSON(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Query(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
query := `query { viewer { login } }`
|
||||||
|
variables := map[string]interface{}{
|
||||||
|
"var1": "value1",
|
||||||
|
}
|
||||||
|
headers := map[string]interface{}{
|
||||||
|
"Authorization": "Bearer token",
|
||||||
|
"gqlcache": "true",
|
||||||
|
}
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
_, _ = b.Query(query, variables, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_QueryNoCacheNoRetry(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
query := `query { viewer { login } }`
|
||||||
|
variables := map[string]interface{}{
|
||||||
|
"var1": "value1",
|
||||||
|
}
|
||||||
|
headers := map[string]interface{}{
|
||||||
|
"Authorization": "Bearer token",
|
||||||
|
}
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
_, _ = b.Query(query, variables, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_QueryWithEmptyVariables(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
query := `query { viewer { login } }`
|
||||||
|
headers := map[string]interface{}{
|
||||||
|
"Authorization": "Bearer token",
|
||||||
|
"gqlcache": "true",
|
||||||
|
}
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
_, _ = b.Query(query, nil, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user