MM-28859 Add feature flag managment system using split.io and remove viper. (#15954)

* Add feature flag managment system using split.io and remove viper.

* Fixing tests.

* Attempt to fix postgres tests.

* Fix watch filepath for advanced logging.

* Review fixes.

* Some error wrapping.

* Remove unessisary store interface.

* Desanitize SplitKey

* Simplify.

* Review feedback.

* Rename split mlog adatper to split logger.

* fsInner

* Style.

* Restore oldcfg test.

* Downgrading non-actionable feature flag errors to warnings.

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Christopher Speller
2020-10-29 15:54:39 -07:00
коммит произвёл GitHub
родитель 8bb772638c
Коммит 1aadd36644
423 изменённых файлов: 37646 добавлений и 20257 удалений

13
vendor/github.com/splitio/go-toolkit/v3/LICENSE сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
Copyright © 2020 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

179
vendor/github.com/splitio/go-toolkit/v3/asynctask/asynctasks.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,179 @@
package asynctask
import (
"fmt"
"sync/atomic"
"time"
"github.com/splitio/go-toolkit/v3/logging"
)
// AsyncTask is a struct that wraps tasks that should run periodically and can be remotely stopped & started,
// as well as making it's status (running/stopped) available.
type AsyncTask struct {
task func(l logging.LoggerInterface) error
name string
running atomic.Value
incoming chan int
period int
onInit func(l logging.LoggerInterface) error
onStop func(l logging.LoggerInterface)
logger logging.LoggerInterface
finished atomic.Value
finishChan chan struct{}
}
const (
taskMessageStop = iota
taskMessageWakeup
)
func (t *AsyncTask) _running() bool {
res, ok := t.running.Load().(bool)
if !ok {
t.logger.Error("Error parsing async task status flag")
return false
}
return res
}
// Start initiates the task. It wraps the execution in a closure guarded by a call to recover() in order
// to prevent the main application from crashin if something goes wrong while the sdk interacts with the backend.
func (t *AsyncTask) Start() {
if t._running() {
if t.logger != nil {
t.logger.Warning("Task %s is already running. Aborting new execution.", t.name)
}
return
}
t.running.Store(true)
go func() {
defer func() {
t.finished.Store(true)
t.finishChan <- struct{}{}
}()
defer func() {
if r := recover(); r != nil {
t.running.Store(false)
if t.logger != nil {
t.logger.Error(fmt.Sprintf(
"AsyncTask %s is panicking! Delaying execution for %d seconds (1 period)",
t.name,
t.period,
))
t.logger.Error(r)
}
time.Sleep(time.Duration(t.period) * time.Second)
}
}()
// If there's an initialization function, execute it
if t.onInit != nil {
err := t.onInit(t.logger)
if err != nil {
// If something goes wrong during initialization, abort.
if t.logger != nil {
t.logger.Error(err.Error())
}
return
}
}
// Create timeout timer
idleDuration := time.Second * time.Duration(t.period)
taskTimer := time.NewTimer(idleDuration)
defer taskTimer.Stop()
// Task execution
for t._running() {
// Run the wrapped task and handle the returned error if any.
err := t.task(t.logger)
if err != nil && t.logger != nil {
t.logger.Error(err.Error())
}
// Resetting timer
taskTimer.Reset(idleDuration)
// Wait for either a timeout or an interruption (can be a stop signal or a wake up)
select {
case msg := <-t.incoming:
switch msg {
case taskMessageStop:
t.running.Store(false)
case taskMessageWakeup:
}
case <-taskTimer.C: // Timedout
}
}
// Post-execution cleanup
if t.onStop != nil {
t.onStop(t.logger)
}
}()
}
func (t *AsyncTask) sendSignal(signal int) error {
select {
case t.incoming <- signal:
return nil
default:
return fmt.Errorf("Couldn't send message to task %s", t.name)
}
}
// Stop executes onStop hook if any, blocks until its done (if blocking = true) and prevents future executions of the task.
func (t *AsyncTask) Stop(blocking bool) error {
if !t._running() || t.finished.Load().(bool) {
// Task already stopped
return nil
}
if err := t.sendSignal(taskMessageStop); err != nil {
// If the signal couldnt be sent, return error!
return err
}
if blocking {
// If blocking was set to true, wait until an empty strcut is pushed into the channel
<-t.finishChan
}
return nil
}
// WakeUp interrupts the task's sleep period and resumes execution
func (t *AsyncTask) WakeUp() error {
return t.sendSignal(taskMessageWakeup)
}
// IsRunning returns true if the task is currently running
func (t *AsyncTask) IsRunning() bool {
return t._running()
}
// NewAsyncTask creates a new task and returns a pointer to it
func NewAsyncTask(
name string,
task func(l logging.LoggerInterface) error,
period int,
onInit func(l logging.LoggerInterface) error,
onStop func(l logging.LoggerInterface),
logger logging.LoggerInterface,
) *AsyncTask {
t := AsyncTask{
name: name,
task: task,
period: period,
onInit: onInit,
onStop: onStop,
logger: logger,
incoming: make(chan int, 10),
finishChan: make(chan struct{}, 1),
}
t.running.Store(false)
t.finished.Store(false)
return &t
}

67
vendor/github.com/splitio/go-toolkit/v3/common/iterutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,67 @@
package common
import (
"errors"
"math"
"time"
)
// WithAttempts executes a function N times or until no error is returned
func WithAttempts(attempts int, main func() error) error {
err := errors.New("")
remaining := attempts
for err != nil && remaining > 0 {
remaining--
err = main()
}
return err
}
// WithBackoff wraps the function to add Exponential backoff
func WithBackoff(duration time.Duration, main func() error) func() error {
var count time.Duration = 1
return func() error {
err := main()
if err != nil {
time.Sleep(count * duration)
count++
} else {
count = 0
}
return main()
}
}
// WithBackoffCancelling wraps the function to add Exponential backoff
func WithBackoffCancelling(unit time.Duration, max time.Duration, main func() bool) func() {
cancel := make(chan struct{})
go func() {
attempts := 0
isDone := main()
// Create timeout timer for backoff
backoffTimer := time.NewTimer(MinDuration(time.Duration(math.Pow(2, float64(attempts)))*unit, max))
defer backoffTimer.Stop()
for !isDone {
attempts++
// Setting timer considerint attempts
backoffTimer.Reset(MinDuration(time.Duration(math.Pow(2, float64(attempts)))*unit, max))
select {
case <-cancel:
return
case <-backoffTimer.C: // Timedout
isDone = main()
}
}
}()
return func() {
select {
case cancel <- struct{}{}:
return
default:
}
}
}

105
vendor/github.com/splitio/go-toolkit/v3/common/refutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,105 @@
package common
// StringRef returns ref
func StringRef(str string) *string {
return &str
}
// IntRef returns ref
func IntRef(number int) *int {
return &number
}
// Int64Ref returns ref
func Int64Ref(number int64) *int64 {
return &number
}
// Float64Ref returns ref
func Float64Ref(number float64) *float64 {
return &number
}
// Int64Value returns value
func Int64Value(number *int64) int64 {
if number == nil {
return 0
}
return *number
}
// IntRefOrNil returns ref
func IntRefOrNil(number int) *int {
if number == 0 {
return nil
}
return IntRef(number)
}
// Int64RefOrNil returns ref
func Int64RefOrNil(number int64) *int64 {
if number == 0 {
return nil
}
return Int64Ref(number)
}
// StringRefOrNil returns ref
func StringRefOrNil(str string) *string {
if str == "" {
return nil
}
return StringRef(str)
}
// AsIntOrNil returns ref
func AsIntOrNil(data interface{}) *int {
if data == nil {
return nil
}
number, ok := data.(int)
if !ok {
return nil
}
return IntRef(number)
}
// AsInt64OrNil returns ref
func AsInt64OrNil(data interface{}) *int64 {
if data == nil {
return nil
}
number, ok := data.(int64)
if !ok {
return nil
}
return Int64Ref(number)
}
// AsFloat64OrNil return ref
func AsFloat64OrNil(data interface{}) *float64 {
if data == nil {
return nil
}
number, ok := data.(float64)
if !ok {
return nil
}
return Float64Ref(number)
}
// AsStringOrNil returns ref
func AsStringOrNil(data interface{}) *string {
if data == nil {
return nil
}
str, ok := data.(string)
if !ok {
return nil
}
return StringRef(str)
}

18
vendor/github.com/splitio/go-toolkit/v3/common/sliceutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,18 @@
package common
// Partition create partitions considering the passed amount
func Partition(items []string, maxItems int) [][]string {
var splitted [][]string
for i := 0; i < len(items); i += maxItems {
end := i + maxItems
if end > len(items) {
end = len(items)
}
splitted = append(splitted, items[i:end])
}
return splitted
}

9
vendor/github.com/splitio/go-toolkit/v3/common/strutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,9 @@
package common
// StringValueOrDefault returns original value if not empty. Default otherwise.
func StringValueOrDefault(str string, def string) string {
if str != "" {
return str
}
return def
}

25
vendor/github.com/splitio/go-toolkit/v3/common/timeutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,25 @@
package common
import "time"
// MinDuration returns the min duration among them
func MinDuration(d1, d2 time.Duration, ds ...time.Duration) time.Duration {
min := d1
for _, d := range append(ds, d2) {
if d < min {
min = d
}
}
return min
}
// MaxDuration returns the max duration among them
func MaxDuration(d1, d2 time.Duration, ds ...time.Duration) time.Duration {
max := d1
for _, d := range append(ds, d2) {
if d > max {
max = d
}
}
return max
}

56
vendor/github.com/splitio/go-toolkit/v3/datastructures/set/functions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
package set
// Union calculates the union of two or more sets
func Union(set1, set2 Set, sets ...Set) Set {
u := set1.Copy()
set2.Each(func(item interface{}) bool {
u.Add(item)
return true
})
for _, set := range sets {
set.Each(func(item interface{}) bool {
u.Add(item)
return true
})
}
return u
}
// Intersection calculates the intersection of two or more sets
func Intersection(set1, set2 Set, sets ...Set) Set {
all := Union(set1, set2, sets...)
result := Union(set1, set2, sets...)
all.Each(func(item interface{}) bool {
if !set1.Has(item) || !set2.Has(item) {
result.Remove(item)
}
for _, set := range sets {
if !set.Has(item) {
result.Remove(item)
}
}
return true
})
return result
}
// Difference calculates the difference of two or more sets
func Difference(set1, set2 Set, sets ...Set) Set {
s := set1.Copy()
s.Separate(set2)
for _, set := range sets {
s.Separate(set) // seperate is thread safe
}
return s
}
// SymmetricDifference calculates the symmetric difference of two or more sets
func SymmetricDifference(s Set, t Set) Set {
u := Difference(s, t)
v := Difference(t, s)
return Union(u, v)
}

355
vendor/github.com/splitio/go-toolkit/v3/datastructures/set/implementations.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,355 @@
package set
import (
"sync"
)
var keyExists = struct{}{} // Value that indicates existance in the set for the key element
// ThreadUnsafeSet structure. Container of unique items with O(1) access time. NOT THREAD SAFE
type ThreadUnsafeSet struct {
set
}
// NewSet Constructs a new set from an optinal slice of items
func NewSet(items ...interface{}) *ThreadUnsafeSet {
s := &ThreadUnsafeSet{}
s.m = make(map[interface{}]struct{})
s.Add(items...)
return s
}
// Add adds new items to the set
func (s *ThreadUnsafeSet) Add(items ...interface{}) {
if len(items) == 0 {
return
}
for _, item := range items {
s.m[item] = keyExists
}
}
// Remove removes items from the set
func (s *ThreadUnsafeSet) Remove(items ...interface{}) {
if len(items) == 0 {
return
}
for _, item := range items {
delete(s.m, item)
}
}
// Pop removes an item from the set and returns it.
func (s *set) Pop() interface{} {
for item := range s.m {
delete(s.m, item)
return item
}
return nil
}
// Has returns true if the items passed are present in the set
func (s *ThreadUnsafeSet) Has(items ...interface{}) bool {
// assume checked for empty item, which not exist
if len(items) == 0 {
return false
}
has := true
for _, item := range items {
if _, has = s.m[item]; !has {
break
}
}
return has
}
// Size returns the size of the set
func (s *ThreadUnsafeSet) Size() int {
return len(s.m)
}
// Clear removes all elements from the set
func (s *ThreadUnsafeSet) Clear() {
s.m = make(map[interface{}]struct{})
}
// IsEqual returns true if the received set is equal to this one
func (s *ThreadUnsafeSet) IsEqual(t Set) bool {
// Force locking only if given set is threadsafe.
if conv, ok := t.(*ThreadSafeSet); ok {
conv.l.RLock()
defer conv.l.RUnlock()
}
// return false if they are no the same size
if sameSize := len(s.m) == t.Size(); !sameSize {
return false
}
equal := true
t.Each(func(item interface{}) bool {
_, equal = s.m[item]
return equal // if false, Each() will end
})
return equal
}
// IsSubset returns true if the passed set is a subset of this one
func (s *ThreadUnsafeSet) IsSubset(t Set) (subset bool) {
subset = true
t.Each(func(item interface{}) bool {
_, subset = s.m[item]
return subset
})
return subset
}
// Each executes a passed function on each of the items passed.
func (s *ThreadUnsafeSet) Each(f func(item interface{}) bool) {
for item := range s.m {
if !f(item) {
break
}
}
}
// List returns a slice of the items in th set
func (s *ThreadUnsafeSet) List() []interface{} {
list := make([]interface{}, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
return list
}
// Copy returns a new set with a copy of the elements
func (s *ThreadUnsafeSet) Copy() Set {
return NewSet(s.List()...)
}
// Merge adds all the elefements in the passed set to this one.
func (s *ThreadUnsafeSet) Merge(t Set) {
t.Each(func(item interface{}) bool {
s.m[item] = keyExists
return true
})
}
// Separate removes all the items that are present in the passed set from this set
func (s *ThreadUnsafeSet) Separate(t Set) {
s.Remove(t.List()...)
}
// IsEmpty returns true if the set has no elements
func (s *ThreadUnsafeSet) IsEmpty() bool {
return s.Size() == 0
}
// IsSuperset returns true if the passed set is a supertset of this one
func (s *ThreadUnsafeSet) IsSuperset(t Set) bool {
return t.IsSubset(s)
}
// ** Thread safe implementation
// ThreadSafeSet is a thread safe implementation of the set data structure
type ThreadSafeSet struct {
set
l sync.RWMutex
}
// NewThreadSafeSet instantiates a new ThreadSafeSet
func NewThreadSafeSet(items ...interface{}) *ThreadSafeSet {
s := &ThreadSafeSet{}
s.m = make(map[interface{}]struct{})
// Ensure interface compliance
var _ Set = s
s.Add(items...)
return s
}
// Add adds a new element to the set
func (s *ThreadSafeSet) Add(items ...interface{}) {
if len(items) == 0 {
return
}
s.l.Lock()
defer s.l.Unlock()
for _, item := range items {
s.m[item] = keyExists
}
}
// Remove deletes an elemenet from the set.
func (s *ThreadSafeSet) Remove(items ...interface{}) {
if len(items) == 0 {
return
}
s.l.Lock()
defer s.l.Unlock()
for _, item := range items {
delete(s.m, item)
}
}
// Pop removes an element from the set and returns it
func (s *ThreadSafeSet) Pop() interface{} {
s.l.RLock()
for item := range s.m {
s.l.RUnlock()
s.l.Lock()
delete(s.m, item)
s.l.Unlock()
return item
}
s.l.RUnlock()
return nil
}
// Has returns true if the element passed is in the set
func (s *ThreadSafeSet) Has(items ...interface{}) bool {
// assume checked for empty item, which not exist
if len(items) == 0 {
return false
}
s.l.RLock()
defer s.l.RUnlock()
has := true
for _, item := range items {
if _, has = s.m[item]; !has {
break
}
}
return has
}
// Size returns the number of elements in the set
func (s *ThreadSafeSet) Size() int {
s.l.RLock()
defer s.l.RUnlock()
l := len(s.m)
return l
}
// Clear removes all the elements in the set
func (s *ThreadSafeSet) Clear() {
s.l.Lock()
defer s.l.Unlock()
s.m = make(map[interface{}]struct{})
}
// IsEqual returns true if the set contains the same elements as the passed one
func (s *ThreadSafeSet) IsEqual(t Set) bool {
s.l.RLock()
defer s.l.RUnlock()
// Force locking only if given set is threadsafe.
if conv, ok := t.(*ThreadSafeSet); ok {
conv.l.RLock()
defer conv.l.RUnlock()
}
// return false if they are no the same size
if sameSize := len(s.m) == t.Size(); !sameSize {
return false
}
equal := true
t.Each(func(item interface{}) bool {
_, equal = s.m[item]
return equal // if false, Each() will end
})
return equal
}
// IsSubset returns true if the passed set is a subset of this one
func (s *ThreadSafeSet) IsSubset(t Set) (subset bool) {
s.l.RLock()
defer s.l.RUnlock()
subset = true
t.Each(func(item interface{}) bool {
_, subset = s.m[item]
return subset
})
return
}
// Each executes the passed function on each item from the set
func (s *ThreadSafeSet) Each(f func(item interface{}) bool) {
s.l.RLock()
defer s.l.RUnlock()
for item := range s.m {
if !f(item) {
break
}
}
}
// List returns a list with all the elements of the set
func (s *ThreadSafeSet) List() []interface{} {
s.l.RLock()
defer s.l.RUnlock()
list := make([]interface{}, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
return list
}
// Merge adds all the elements of the passed set into this one
func (s *ThreadSafeSet) Merge(t Set) {
s.l.Lock()
defer s.l.Unlock()
t.Each(func(item interface{}) bool {
s.m[item] = keyExists
return true
})
}
// Copy returns a copy of the this thread
func (s *ThreadSafeSet) Copy() Set {
return NewThreadSafeSet(s.List()...)
}
// Separate removes all the items that are present in the passed set from this set
func (s *ThreadSafeSet) Separate(t Set) {
s.Remove(t.List()...)
}
// IsEmpty returns true if the set has no elements
func (s *ThreadSafeSet) IsEmpty() bool {
return s.Size() == 0
}
// IsSuperset returns true if the passed set is a supertset of this one
func (s *ThreadSafeSet) IsSuperset(t Set) bool {
return t.IsSubset(s)
}

24
vendor/github.com/splitio/go-toolkit/v3/datastructures/set/set.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,24 @@
package set
// Set interface shared between Thread-Safe and Thread-Unsafe implementations
type Set interface {
Add(items ...interface{})
Remove(items ...interface{})
Pop() interface{}
Has(items ...interface{}) bool
Size() int
Clear()
IsEmpty() bool
IsEqual(s Set) bool
IsSubset(s Set) bool
IsSuperset(s Set) bool
Each(func(interface{}) bool)
List() []interface{}
Copy() Set
Merge(s Set)
Separate(t Set)
}
type set struct {
m map[interface{}]struct{}
}

47
vendor/github.com/splitio/go-toolkit/v3/injection/container.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,47 @@
package injection
import (
"reflect"
"sync"
)
// NewContext return an instance of Context
func NewContext() *Context {
return &Context{container: make(map[string]interface{})}
}
// Context of dependencies to be injected as context
type Context struct {
container map[string]interface{}
mx sync.Mutex
}
// AddDependency adds a dependency to the container
func (c *Context) AddDependency(key string, d interface{}) {
c.mx.Lock()
c.container[key] = d
c.mx.Unlock()
}
// Dependency returns an object given a key
func (c *Context) Dependency(key string) interface{} {
c.mx.Lock()
defer c.mx.Unlock()
return c.container[key]
}
// Inject add the Context instance as dependency
func (c *Context) Inject(o interface{}) {
rv := reflect.ValueOf(c)
reflectedContext := reflect.ValueOf(o).Elem()
typeOfT := rv.Type()
for i := 0; i < reflectedContext.NumField(); i++ {
f := reflectedContext.Field(i)
if f.Type().String() == typeOfT.String() {
reflectedContext.Field(i).Set(rv)
}
}
}

33
vendor/github.com/splitio/go-toolkit/v3/logging/functions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
package logging
import (
"fmt"
"net/http"
"regexp"
"strings"
)
// ObfuscateAPIKey obfucate part of api key
func ObfuscateAPIKey(apikey string) string {
obfuscationIndex := 80
total := len(apikey)
charsToObfuscate := obfuscationIndex * total / 100
toShow := (total - charsToObfuscate) / 2
return strings.Join([]string{apikey[:toShow], apikey[len(apikey)-toShow:]}, "...")
}
// ObfuscateHTTPHeader obfuscates sensitive data into headers
func ObfuscateHTTPHeader(headers http.Header) string {
var re = regexp.MustCompile(`Authorization:\[Bearer ([0-9|a-z|A-Z|\s]*)\]`)
var str = fmt.Sprint(headers)
match := re.FindStringSubmatch(str)
if len(match) == 2 {
str = strings.Replace(str, match[1], ObfuscateAPIKey(match[1]), 1)
return fmt.Sprint("[REQUEST_HEADERS]", str, "[END_REQUEST_HEADERS]")
}
return str
}

12
vendor/github.com/splitio/go-toolkit/v3/logging/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
package logging
// LoggerInterface ...
// If a custom logger object is to be used, it should comply with the following
// interface. (Standard go-lang library log.Logger.Println method signature)
type LoggerInterface interface {
Error(msg ...interface{})
Warning(msg ...interface{})
Info(msg ...interface{})
Debug(msg ...interface{})
Verbose(msg ...interface{})
}

93
vendor/github.com/splitio/go-toolkit/v3/logging/levels.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,93 @@
package logging
import (
"math"
)
// Standard values
const (
// Discard 0 value, so when can use it as "the lack of a logging level"
_ = iota
// LevelError log level
LevelError
// LevelWarning log level
LevelWarning
// LevelInfo log level
LevelInfo
// LevelDebug log level
LevelDebug
// LevelVerbose log level
LevelVerbose
)
// Special values
const (
// LevelNone implies that NOTHING will be logged, not even errors
LevelNone = math.MinInt32
// LevelAll implies that All logging levels will be recorded
LevelAll = math.MaxInt32
)
// LevelFilteredLoggerWrapper forwards log message to delegate if level is set higher than incoming message
type LevelFilteredLoggerWrapper struct {
level int
delegate LoggerInterface
}
// Error forwards error logging messages
func (l *LevelFilteredLoggerWrapper) Error(is ...interface{}) {
if l.level >= LevelError {
l.delegate.Error(is...)
}
}
// Warning forwards warning logging messages
func (l *LevelFilteredLoggerWrapper) Warning(is ...interface{}) {
if l.level >= LevelWarning {
l.delegate.Warning(is...)
}
}
// Info forwards info logging messages
func (l *LevelFilteredLoggerWrapper) Info(is ...interface{}) {
if l.level >= LevelInfo {
l.delegate.Info(is...)
}
}
// Debug forwards debug logging messages
func (l *LevelFilteredLoggerWrapper) Debug(is ...interface{}) {
if l.level >= LevelDebug {
l.delegate.Debug(is...)
}
}
// Verbose forwards verbose logging messages
func (l *LevelFilteredLoggerWrapper) Verbose(is ...interface{}) {
if l.level >= LevelVerbose {
l.delegate.Verbose(is...)
}
}
var levels map[string]int = map[string]int{
"ERROR": LevelError,
"WARNING": LevelWarning,
"INFO": LevelInfo,
"DEBUG": LevelDebug,
"VERBOSE": LevelVerbose,
}
// Level gets current level
func Level(level string) int {
l, ok := levels[level]
if !ok {
panic("Invalid log level " + level)
}
return l
}

129
vendor/github.com/splitio/go-toolkit/v3/logging/logging.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,129 @@
// Package logging ...
// Handles logging within the SDK
package logging
import (
"fmt"
"io"
"log"
"os"
)
const (
skipStackFrameBase = 3 // How many stack frames to skip when logging filename
)
// LoggerOptions ...
// Struct that must be passed to the NewLogger constructor to setup a logger
// CommonWriter and ErrorWriter can be <nil>. In that case they'll default to os.Stdout
type LoggerOptions struct {
LogLevel int
ErrorWriter io.Writer
WarningWriter io.Writer
InfoWriter io.Writer
DebugWriter io.Writer
VerboseWriter io.Writer
StandardLoggerFlags int
Prefix string
ExtraFramesToSkip int
}
// Logger struct. Encapsulates four different loggers, each for a different "level",
// and provides Error, Debug, Warning and Info functions, that will forward a message
// to the appropriate logger.
type Logger struct {
debugLogger log.Logger
infoLogger log.Logger
warningLogger log.Logger
errorLogger log.Logger
verboseLogger log.Logger
framesToSkip int
}
// Verbose logs a message with Debug level
func (l *Logger) Verbose(msg ...interface{}) {
l.verboseLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Debug logs a message with Debug level
func (l *Logger) Debug(msg ...interface{}) {
l.debugLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Info logs a message with Info level
func (l *Logger) Info(msg ...interface{}) {
l.infoLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Warning logs a message with Warning level
func (l *Logger) Warning(msg ...interface{}) {
l.warningLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Error logs a message with Error level
func (l *Logger) Error(msg ...interface{}) {
l.errorLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
func normalizeOptions(options *LoggerOptions) *LoggerOptions {
var toRet *LoggerOptions
if options == nil {
toRet = &LoggerOptions{}
} else {
toRet = options
}
if toRet.DebugWriter == nil {
toRet.DebugWriter = os.Stdout
}
if toRet.ErrorWriter == nil {
toRet.ErrorWriter = os.Stdout
}
if toRet.InfoWriter == nil {
toRet.InfoWriter = os.Stdout
}
if toRet.VerboseWriter == nil {
toRet.VerboseWriter = os.Stdout
}
if toRet.WarningWriter == nil {
toRet.WarningWriter = os.Stdout
}
if toRet.StandardLoggerFlags == 0 {
toRet.StandardLoggerFlags = log.Ldate | log.Ltime
}
switch toRet.LogLevel {
case LevelAll, LevelDebug, LevelError, LevelInfo, LevelNone, LevelVerbose, LevelWarning:
default:
toRet.LogLevel = LevelError
}
return toRet
}
// NewLogger instantiates a new Logger instance. Requires a pointer to a LoggerOptions struct to be passed.
func NewLogger(options *LoggerOptions) LoggerInterface {
options = normalizeOptions(options)
prefix := ""
if options.Prefix != "" {
prefix = fmt.Sprintf("%s - ", options.Prefix)
}
logger := &Logger{
debugLogger: *log.New(options.DebugWriter, fmt.Sprintf("%sDEBUG - ", prefix), options.StandardLoggerFlags),
infoLogger: *log.New(options.InfoWriter, fmt.Sprintf("%sINFO - ", prefix), options.StandardLoggerFlags),
warningLogger: *log.New(options.WarningWriter, fmt.Sprintf("%sWARNING - ", prefix), options.StandardLoggerFlags),
errorLogger: *log.New(options.ErrorWriter, fmt.Sprintf("%sERROR - ", prefix), options.StandardLoggerFlags),
verboseLogger: *log.New(options.VerboseWriter, fmt.Sprintf("%sVERBOSE - ", prefix), options.StandardLoggerFlags),
framesToSkip: 3 + options.ExtraFramesToSkip,
}
return &LevelFilteredLoggerWrapper{
delegate: logger,
level: options.LogLevel,
}
}

106
vendor/github.com/splitio/go-toolkit/v3/logging/rotate.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,106 @@
package logging
import (
"fmt"
"os"
"strconv"
"sync"
)
// FileRotateOptions struct to configure FileRotate
type FileRotateOptions struct {
MaxBytes int64
BackupCount int
Path string
}
// FileRotate rotates a log file at MaxBytes
type FileRotate struct {
fl *os.File
fm *sync.Mutex
options *FileRotateOptions
}
// NewFileRotate returns a pointer to a FileRotate instance
func NewFileRotate(opt *FileRotateOptions) (*FileRotate, error) {
fileWriter, err := os.OpenFile(opt.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
fl := &FileRotate{fl: fileWriter, fm: &sync.Mutex{}, options: opt}
return fl, nil
}
func (f *FileRotate) shouldRotate(bytesToAdd int64) bool {
fi, err := f.fl.Stat()
if err != nil {
fmt.Println("Error getting stats of file")
return false
}
if fi.Size()+bytesToAdd >= f.options.MaxBytes {
return true
}
return false
}
func (f *FileRotate) rotate() error {
f.fl.Close()
for i := f.options.BackupCount - 1; i >= 0; i-- {
var currentLog string
if i == 0 {
currentLog = f.options.Path
} else {
currentLog = f.options.Path + "." + strconv.Itoa(i)
}
if _, err := os.Stat(currentLog); err == nil {
rotateLog := f.options.Path + "." + strconv.Itoa(i+1)
err := os.Rename(currentLog, rotateLog)
if err != nil {
fmt.Printf("Error rotating log file: %s \n", err.Error())
return err
}
}
}
var err error
f.fl, err = os.OpenFile(f.options.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Printf("Error reopening log file: %s \n", err.Error())
return err
}
return nil
}
func (f *FileRotate) write(p []byte) (n int, err error) {
f.fm.Lock()
if f.shouldRotate(int64(len(p))) {
f.rotate()
}
n, err = f.fl.Write(p)
f.fm.Unlock()
if err != nil {
fmt.Println("Error writing in rotated log file", f.options.Path)
return n, err
}
return n, nil
}
// Write writes async the log message
func (f *FileRotate) Write(p []byte) (n int, err error) {
dst := make([]byte, len(p))
copy(dst, p)
go f.write(dst)
return len(p), nil
}

44
vendor/github.com/splitio/go-toolkit/v3/nethelpers/ip.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,44 @@
package nethelpers
import (
"errors"
"net"
)
// ExternalIP returns the IP address of the host
func ExternalIP() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("Could not determine IP Address")
}

231
vendor/github.com/splitio/go-toolkit/v3/provisional/hashing/murmur128.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,231 @@
package hashing
// Implementation borrowed from https://github.com/spaolacci/murmur3,
// distributed under BSD-3 license.
import (
//"encoding/binary"
"hash"
"math/bits"
"unsafe"
)
const (
c1_128 = 0x87c37b91114253d5
c2_128 = 0x4cf5ad432745937f
)
// Hash128 represents a 128-bit hasher
// Hack: the standard api doesn't define any Hash128 interface.
type Hash128 interface {
hash.Hash
Sum128() (uint64, uint64)
}
// digest128 represents a partial evaluation of a 128 bites hash.
type digest128 struct {
clen int // Digested input cumulative length.
tail []byte // 0 to Size()-1 bytes view of `buf'.
buf [16]byte // Expected (but not required) to be Size() large.
seed uint32 // Seed for initializing the hash.
h1 uint64 // Unfinalized running hash part 1.
h2 uint64 // Unfinalized running hash part 2.
}
// New128 returns a 128-bit hasher
func New128() Hash128 { return New128WithSeed(0) }
// New128WithSeed returns a 128-bit hasher set with explicit seed value
func New128WithSeed(seed uint32) Hash128 {
d := new(digest128)
d.seed = seed
d.Reset()
return d
}
func (d *digest128) BlockSize() int { return 1 }
func (d *digest128) Write(p []byte) (n int, err error) {
n = len(p)
d.clen += n
if len(d.tail) > 0 {
// Stick back pending bytes.
nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1].
if nfree < len(p) {
// One full block can be formed.
block := append(d.tail, p[:nfree]...)
p = p[nfree:]
_ = d.bmix(block) // No tail.
} else {
// Tail's buf is large enough to prevent reallocs.
p = append(d.tail, p...)
}
}
d.tail = d.bmix(p)
// Keep own copy of the 0 to Size()-1 pending bytes.
nn := copy(d.buf[:], d.tail)
d.tail = d.buf[:nn]
return n, nil
}
func (d *digest128) Reset() {
d.clen = 0
d.tail = nil
d.reset()
}
func (d *digest128) Size() int { return 16 }
func (d *digest128) reset() { d.h1, d.h2 = uint64(d.seed), uint64(d.seed) }
func (d *digest128) Sum(b []byte) []byte {
h1, h2 := d.Sum128()
return append(b,
byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32),
byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1),
byte(h2>>56), byte(h2>>48), byte(h2>>40), byte(h2>>32),
byte(h2>>24), byte(h2>>16), byte(h2>>8), byte(h2),
)
}
func (d *digest128) bmix(p []byte) (tail []byte) {
h1, h2 := d.h1, d.h2
nblocks := len(p) / 16
for i := 0; i < nblocks; i++ {
t := (*[2]uint64)(unsafe.Pointer(&p[i*16]))
k1, k2 := t[0], t[1]
k1 *= c1_128
k1 = bits.RotateLeft64(k1, 31)
k1 *= c2_128
h1 ^= k1
h1 = bits.RotateLeft64(h1, 27)
h1 += h2
h1 = h1*5 + 0x52dce729
k2 *= c2_128
k2 = bits.RotateLeft64(k2, 33)
k2 *= c1_128
h2 ^= k2
h2 = bits.RotateLeft64(h2, 31)
h2 += h1
h2 = h2*5 + 0x38495ab5
}
d.h1, d.h2 = h1, h2
return p[nblocks*d.Size():]
}
func (d *digest128) Sum128() (h1, h2 uint64) {
h1, h2 = d.h1, d.h2
var k1, k2 uint64
switch len(d.tail) & 15 {
case 15:
k2 ^= uint64(d.tail[14]) << 48
fallthrough
case 14:
k2 ^= uint64(d.tail[13]) << 40
fallthrough
case 13:
k2 ^= uint64(d.tail[12]) << 32
fallthrough
case 12:
k2 ^= uint64(d.tail[11]) << 24
fallthrough
case 11:
k2 ^= uint64(d.tail[10]) << 16
fallthrough
case 10:
k2 ^= uint64(d.tail[9]) << 8
fallthrough
case 9:
k2 ^= uint64(d.tail[8]) << 0
k2 *= c2_128
k2 = bits.RotateLeft64(k2, 33)
k2 *= c1_128
h2 ^= k2
fallthrough
case 8:
k1 ^= uint64(d.tail[7]) << 56
fallthrough
case 7:
k1 ^= uint64(d.tail[6]) << 48
fallthrough
case 6:
k1 ^= uint64(d.tail[5]) << 40
fallthrough
case 5:
k1 ^= uint64(d.tail[4]) << 32
fallthrough
case 4:
k1 ^= uint64(d.tail[3]) << 24
fallthrough
case 3:
k1 ^= uint64(d.tail[2]) << 16
fallthrough
case 2:
k1 ^= uint64(d.tail[1]) << 8
fallthrough
case 1:
k1 ^= uint64(d.tail[0]) << 0
k1 *= c1_128
k1 = bits.RotateLeft64(k1, 31)
k1 *= c2_128
h1 ^= k1
}
h1 ^= uint64(d.clen)
h2 ^= uint64(d.clen)
h1 += h2
h2 += h1
h1 = fmix64(h1)
h2 = fmix64(h2)
h1 += h2
h2 += h1
return h1, h2
}
func fmix64(k uint64) uint64 {
k ^= k >> 33
k *= 0xff51afd7ed558ccd
k ^= k >> 33
k *= 0xc4ceb9fe1a85ec53
k ^= k >> 33
return k
}
// Sum128 returns the MurmurHash3 sum of data. It is equivalent to the
// following sequence (without the extra burden and the extra allocation):
// hasher := New128()
// hasher.Write(data)
// return hasher.Sum128()
func Sum128(data []byte) (h1 uint64, h2 uint64) { return Sum128WithSeed(data, 0) }
// Sum128WithSeed returns the MurmurHash3 sum of data. It is equivalent to the
// following sequence (without the extra burden and the extra allocation):
// hasher := New128WithSeed(seed)
// hasher.Write(data)
// return hasher.Sum128()
func Sum128WithSeed(data []byte, seed uint32) (h1 uint64, h2 uint64) {
d := digest128{h1: uint64(seed), h2: uint64(seed)}
d.seed = seed
d.tail = d.bmix(data)
d.clen = len(data)
return d.Sum128()
}

91
vendor/github.com/splitio/go-toolkit/v3/provisional/int64cache/cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,91 @@
package int64cache
import (
"container/list"
"fmt"
"sync"
)
// Int64Cache is an in-memory TTL & LRU cache
type Int64Cache interface {
Get(key int64) (int64, error)
Set(key int64, value int64) error
}
// Impl implements the LocalCache interface
type Impl struct {
maxLen int
items map[int64]*list.Element
lru *list.List
mutex sync.Mutex
}
type entry struct {
key int64
value int64
}
// Get retrieves an item if exist, nil + an error otherwise
func (c *Impl) Get(key int64) (int64, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
node, ok := c.items[key]
if !ok {
return 0, &Miss{}
}
entry, ok := node.Value.(entry)
if !ok {
return 0, fmt.Errorf("Invalid data in cache for key %d", key)
}
c.lru.MoveToFront(node)
return entry.value, nil
}
// Set adds a new item. Since the cache being full results in removing the LRU element, this method never fails.
func (c *Impl) Set(key int64, value int64) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if node, ok := c.items[key]; ok {
c.lru.MoveToFront(node)
node.Value = entry{key: key, value: value}
} else {
// Drop the LRU item on the list before adding a new one.
if c.lru.Len() == c.maxLen {
entry, ok := c.lru.Back().Value.(entry)
if !ok {
return fmt.Errorf("Invalid data in list for key %d", key)
}
key := entry.key
delete(c.items, key)
c.lru.Remove(c.lru.Back())
}
ptr := c.lru.PushFront(entry{key: key, value: value})
c.items[key] = ptr
}
return nil
}
// NewInt64Cache returns a new LocalCache instance of the specified size and TTL
func NewInt64Cache(maxSize int) (*Impl, error) {
if maxSize <= 0 {
return nil, fmt.Errorf("Cache size should be > 0. Is: %d", maxSize)
}
return &Impl{
maxLen: maxSize,
lru: new(list.List),
items: make(map[int64]*list.Element, maxSize),
}, nil
}
// Miss is a special error indicating the key was not found in the cache
type Miss struct {
Key int64
}
func (m *Miss) Error() string {
return fmt.Sprintf("key %d not found in cache", m.Key)
}

127
vendor/github.com/splitio/go-toolkit/v3/queuecache/cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,127 @@
package queuecache
import (
"errors"
"fmt"
"math"
"sync"
)
// RefillError struct to be returned when the refill function panics
type RefillError struct {
OriginalPanic interface{}
}
func (e *RefillError) Error() string {
return "Supplied refilling function panicked. See `.OriginalPanic` property to get panicked content"
}
// MessagesDroppedError is the Error to be returned when messages fail to be added to the queue.
type MessagesDroppedError struct {
MessagesDropped int
}
func (e *MessagesDroppedError) Error() string {
return fmt.Sprintf(
"%d messages were dropped. Please report this error as it's most likely a bug in the library",
e.MessagesDropped,
)
}
// InMemoryQueueCacheOverlay offers an in-memory queue that gets re-populated whenever it runs out of items
type InMemoryQueueCacheOverlay struct {
maxSize int
writeCursor int
readCursor int
queue []interface{}
lock sync.Mutex
refillCustom func(count int) ([]interface{}, error)
}
// New creates a new InMemoryQueueCacheOverlay
func New(maxSize int, refillFunc func(count int) ([]interface{}, error)) *InMemoryQueueCacheOverlay {
return &InMemoryQueueCacheOverlay{
queue: make([]interface{}, maxSize),
maxSize: maxSize,
writeCursor: 0,
readCursor: 0,
refillCustom: refillFunc,
}
}
// Count returns the number of cached items
func (i *InMemoryQueueCacheOverlay) Count() int {
if i.writeCursor == i.readCursor {
return 0
} else if i.writeCursor > i.readCursor {
return i.writeCursor - i.readCursor
}
return i.maxSize - (i.readCursor - i.writeCursor)
}
func (i *InMemoryQueueCacheOverlay) write(elem interface{}) error {
if ((i.writeCursor + 1) % i.maxSize) == i.readCursor {
return errors.New("QUEUE_FULL")
}
i.queue[i.writeCursor] = elem
i.writeCursor = (i.writeCursor + 1) % i.maxSize
return nil
}
func (i *InMemoryQueueCacheOverlay) read() (interface{}, error) {
if i.readCursor == i.writeCursor {
return nil, errors.New("QUEUE_EMPTY")
}
toReturn := i.queue[i.readCursor]
i.readCursor = (i.readCursor + 1) % i.maxSize
return toReturn, nil
}
func (i *InMemoryQueueCacheOverlay) refillWrapper(count int) (result []interface{}, err error) {
defer func() {
if r := recover(); r != nil {
result = nil
err = &RefillError{OriginalPanic: r}
}
}()
return i.refillCustom(count)
}
// Fetch items (will re-populate if necessary)
func (i *InMemoryQueueCacheOverlay) Fetch(requestedCount int) ([]interface{}, error) {
defer i.lock.Unlock()
i.lock.Lock()
dropped := 0
if i.Count() < requestedCount {
toAdd, err := i.refillWrapper(i.maxSize - i.Count() - 1)
if err != nil {
return nil, err
}
for _, item := range toAdd {
err = i.write(item)
if err != nil {
dropped++
}
}
}
toReturn := make([]interface{}, int(math.Min(float64(requestedCount), float64(i.Count()))))
for index := 0; index < len(toReturn); index++ {
elem, err := i.read()
if err != nil {
return toReturn[0:index], nil
}
toReturn[index] = elem
}
if dropped > 0 {
return toReturn, &MessagesDroppedError{MessagesDropped: dropped}
}
return toReturn, nil
}

23
vendor/github.com/splitio/go-toolkit/v3/redis/helpers/helpers.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
package helpers
import (
"fmt"
"github.com/splitio/go-toolkit/v3/redis"
)
const (
pong = "PONG"
)
// EnsureConnected pings redis
func EnsureConnected(client redis.Client) {
res := client.Ping()
if res.Err() != nil {
panic(fmt.Sprintf("Couldn't connect to redis: %s", res.Err()))
}
if res.String() != pong {
panic(fmt.Sprintf("Invalid redis ping response when connecting: %s", res.String()))
}
}

160
vendor/github.com/splitio/go-toolkit/v3/redis/prefixedclient.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,160 @@
package redis
import (
"fmt"
"strings"
"time"
)
// PrefixedRedisClient struct
type PrefixedRedisClient struct {
Prefix string
Client Client
}
// withPrefix adds a prefix to the key if the prefix supplied has a length greater than 0
func (p *PrefixedRedisClient) withPrefix(key string) string {
if len(p.Prefix) > 0 {
return fmt.Sprintf("%s.%s", p.Prefix, key)
}
return key
}
// withoutPrefix removes the prefix from a key if the prefix has a length greater than 0
func (p *PrefixedRedisClient) withoutPrefix(key string) string {
if len(p.Prefix) > 0 {
return strings.Replace(key, fmt.Sprintf("%s.", p.Prefix), "", 1)
}
return key
}
// Get wraps around redis get method by adding prefix and returning string and error directly
func (p *PrefixedRedisClient) Get(key string) (string, error) {
return p.Client.Get(p.withPrefix(key)).ResultString()
}
// Set wraps around redis get method by adding prefix and returning error directly
func (p *PrefixedRedisClient) Set(key string, value interface{}, expiration time.Duration) error {
return p.Client.Set(p.withPrefix(key), value, expiration).Err()
}
// Keys wraps around redis keys method by adding prefix and returning []string and error directly
func (p *PrefixedRedisClient) Keys(pattern string) ([]string, error) {
keys, err := p.Client.Keys(p.withPrefix(pattern)).Multi()
if err != nil {
return nil, err
}
woPrefix := make([]string, len(keys))
for index, key := range keys {
woPrefix[index] = p.withoutPrefix(key)
}
return woPrefix, nil
}
// Del wraps around redis del method by adding prefix and returning int64 and error directly
func (p *PrefixedRedisClient) Del(keys ...string) (int64, error) {
prefixedKeys := make([]string, len(keys))
for i, k := range keys {
prefixedKeys[i] = p.withPrefix(k)
}
return p.Client.Del(prefixedKeys...).Result()
}
// SMembers returns a slice with all the members of a set
func (p *PrefixedRedisClient) SMembers(key string) ([]string, error) {
return p.Client.SMembers(p.withPrefix(key)).Multi()
}
// SIsMember returns true if members is in the set
func (p *PrefixedRedisClient) SIsMember(key string, member interface{}) bool {
return p.Client.SIsMember(p.withPrefix(key), member).Bool()
}
// SAdd adds new members to a set
func (p *PrefixedRedisClient) SAdd(key string, members ...interface{}) (int64, error) {
return p.Client.SAdd(p.withPrefix(key), members...).Result()
}
// SRem removes members from a set
func (p *PrefixedRedisClient) SRem(key string, members ...interface{}) (int64, error) {
return p.Client.SRem(p.withPrefix(key), members...).Result()
}
// Exists returns true if a key exists in redis
func (p *PrefixedRedisClient) Exists(keys ...string) (int64, error) {
prefixedKeys := make([]string, len(keys))
for i, k := range keys {
prefixedKeys[i] = p.withPrefix(k)
}
val, err := p.Client.Exists(prefixedKeys...).Result()
return val, err
}
// Incr increments a key. Sets it in one if it doesn't exist
func (p *PrefixedRedisClient) Incr(key string) (int64, error) {
return p.Client.Incr(p.withPrefix(key)).Result()
}
// Decr increments a key. Sets it in one if it doesn't exist
func (p *PrefixedRedisClient) Decr(key string) (int64, error) {
return p.Client.Decr(p.withPrefix(key)).Result()
}
// RPush insert all the specified values at the tail of the list stored at key
func (p *PrefixedRedisClient) RPush(key string, values ...interface{}) (int64, error) {
return p.Client.RPush(p.withPrefix(key), values...).Result()
}
// LRange Returns the specified elements of the list stored at key
func (p *PrefixedRedisClient) LRange(key string, start, stop int64) ([]string, error) {
return p.Client.LRange(p.withPrefix(key), start, stop).Multi()
}
// LTrim Trim an existing list so that it will contain only the specified range of elements specified
func (p *PrefixedRedisClient) LTrim(key string, start, stop int64) error {
return p.Client.LTrim(p.withPrefix(key), start, stop).Err()
}
// LLen Returns the length of the list stored at key
func (p *PrefixedRedisClient) LLen(key string) (int64, error) {
return p.Client.LLen(p.withPrefix(key)).Result()
}
// Expire set expiration time for particular key
func (p *PrefixedRedisClient) Expire(key string, value time.Duration) bool {
return p.Client.Expire(p.withPrefix(key), value).Bool()
}
// TTL for particular key
func (p *PrefixedRedisClient) TTL(key string) time.Duration {
return p.Client.TTL(p.withPrefix(key)).Duration()
}
// MGet fetchs multiple results
func (p *PrefixedRedisClient) MGet(keys []string) ([]interface{}, error) {
keysWithPrefix := make([]string, 0)
for _, key := range keys {
keysWithPrefix = append(keysWithPrefix, p.withPrefix(key))
}
return p.Client.MGet(keysWithPrefix).MultiInterface()
}
// SCard implements SCard wrapper for redis
func (p *PrefixedRedisClient) SCard(key string) (int64, error) {
return p.Client.SCard(p.withPrefix(key)).Result()
}
// Eval implements Eval wrapper for redis
func (p *PrefixedRedisClient) Eval(script string, keys []string, args ...interface{}) error {
return p.Client.Eval(script, keys, args...).Err()
}
// NewPrefixedRedisClient returns a new Prefixed Redis Client
func NewPrefixedRedisClient(redisClient Client, prefix string) (*PrefixedRedisClient, error) {
return &PrefixedRedisClient{
Client: redisClient,
Prefix: prefix,
}, nil
}

8
vendor/github.com/splitio/go-toolkit/v3/redis/types.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
package redis
import (
wredis "github.com/go-redis/redis/v8"
)
// UniversalOptions type used for redis package
type UniversalOptions = wredis.UniversalOptions

293
vendor/github.com/splitio/go-toolkit/v3/redis/wrapper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,293 @@
package redis
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
// ===== Command output / return value types
// Result generic interface
type Result interface {
Int() int64
String() string
Bool() bool
Duration() time.Duration
Result() (int64, error)
ResultString() (string, error)
Multi() ([]string, error)
MultiInterface() ([]interface{}, error)
Err() error
}
// ResultImpl generic interface
type ResultImpl struct {
value int64
valueString string
valueBool bool
valueDuration time.Duration
err error
multi []string
multiInterface []interface{}
}
// Int implementation
func (r *ResultImpl) Int() int64 {
return r.value
}
// String implementation
func (r *ResultImpl) String() string {
return r.valueString
}
// Bool implementation
func (r *ResultImpl) Bool() bool {
return r.valueBool
}
// Duration implementation
func (r *ResultImpl) Duration() time.Duration {
return r.valueDuration
}
// Err implementation
func (r *ResultImpl) Err() error {
return r.err
}
// Result implementation
func (r *ResultImpl) Result() (int64, error) {
return r.value, r.err
}
// ResultString implementation
func (r *ResultImpl) ResultString() (string, error) {
return r.valueString, r.err
}
// Multi implementation
func (r *ResultImpl) Multi() ([]string, error) {
return r.multi, r.err
}
// MultiInterface implementation
func (r *ResultImpl) MultiInterface() ([]interface{}, error) {
return r.multiInterface, r.err
}
// ====== Client
// Client interface which specifies the currently used subset of redis operations
type Client interface {
Del(keys ...string) Result
Exists(keys ...string) Result
Get(key string) Result
Set(key string, value interface{}, expiration time.Duration) Result
Ping() Result
Keys(pattern string) Result
SMembers(key string) Result
SIsMember(key string, member interface{}) Result
SAdd(key string, members ...interface{}) Result
SRem(key string, members ...interface{}) Result
Incr(key string) Result
Decr(key string) Result
RPush(key string, values ...interface{}) Result
LRange(key string, start, stop int64) Result
LTrim(key string, start, stop int64) Result
LLen(key string) Result
Expire(key string, value time.Duration) Result
TTL(key string) Result
MGet(keys []string) Result
SCard(key string) Result
Eval(script string, keys []string, args ...interface{}) Result
}
// ClientImpl wrapps redis client
type ClientImpl struct {
wrapped redis.UniversalClient
}
func (c *ClientImpl) wrapResult(result interface{}) Result {
if result == nil {
return nil
}
switch v := result.(type) {
case *redis.StatusCmd:
return &ResultImpl{
valueString: v.Val(),
err: v.Err(),
}
case *redis.IntCmd:
return &ResultImpl{
value: v.Val(),
err: v.Err(),
}
case *redis.StringCmd:
return &ResultImpl{
valueString: v.Val(),
err: v.Err(),
}
case *redis.StringSliceCmd:
return &ResultImpl{
err: v.Err(),
multi: v.Val(),
}
case *redis.BoolCmd:
return &ResultImpl{
valueBool: v.Val(),
err: v.Err(),
}
case *redis.DurationCmd:
return &ResultImpl{
valueDuration: v.Val(),
err: v.Err(),
}
case *redis.SliceCmd:
return &ResultImpl{
err: v.Err(),
multiInterface: v.Val(),
}
case *redis.Cmd:
return &ResultImpl{
err: v.Err(),
}
default:
return nil
}
}
// Del implements Del wrapper for redis
func (c *ClientImpl) Del(keys ...string) Result {
res := c.wrapped.Del(context.TODO(), keys...)
return c.wrapResult(res)
}
// Exists implements Exists wrapper for redis
func (c *ClientImpl) Exists(keys ...string) Result {
res := c.wrapped.Exists(context.TODO(), keys...)
return c.wrapResult(res)
}
// Get implements Get wrapper for redis
func (c *ClientImpl) Get(key string) Result {
res := c.wrapped.Get(context.TODO(), key)
return c.wrapResult(res)
}
// Set implements Set wrapper for redis
func (c *ClientImpl) Set(key string, value interface{}, expiration time.Duration) Result {
res := c.wrapped.Set(context.TODO(), key, value, expiration)
return c.wrapResult(res)
}
// Ping implements Ping wrapper for redis
func (c *ClientImpl) Ping() Result {
res := c.wrapped.Ping(context.TODO())
return c.wrapResult(res)
}
// Keys implements Keys wrapper for redis
func (c *ClientImpl) Keys(pattern string) Result {
res := c.wrapped.Keys(context.TODO(), pattern)
return c.wrapResult(res)
}
// SMembers implements SMembers wrapper for redis
func (c *ClientImpl) SMembers(key string) Result {
res := c.wrapped.SMembers(context.TODO(), key)
return c.wrapResult(res)
}
// SIsMember implements SIsMember wrapper for redis
func (c *ClientImpl) SIsMember(key string, member interface{}) Result {
res := c.wrapped.SIsMember(context.TODO(), key, member)
return c.wrapResult(res)
}
// SAdd implements SAdd wrapper for redis
func (c *ClientImpl) SAdd(key string, members ...interface{}) Result {
res := c.wrapped.SAdd(context.TODO(), key, members...)
return c.wrapResult(res)
}
// SRem implements SRem wrapper for redis
func (c *ClientImpl) SRem(key string, members ...interface{}) Result {
res := c.wrapped.SRem(context.TODO(), key, members...)
return c.wrapResult(res)
}
// Incr implements Incr wrapper for redis
func (c *ClientImpl) Incr(key string) Result {
res := c.wrapped.Incr(context.TODO(), key)
return c.wrapResult(res)
}
// Decr implements Decr wrapper for redis
func (c *ClientImpl) Decr(key string) Result {
res := c.wrapped.Decr(context.TODO(), key)
return c.wrapResult(res)
}
// RPush implements RPush wrapper for redis
func (c *ClientImpl) RPush(key string, values ...interface{}) Result {
res := c.wrapped.RPush(context.TODO(), key, values...)
return c.wrapResult(res)
}
// LRange implements LRange wrapper for redis
func (c *ClientImpl) LRange(key string, start, stop int64) Result {
res := c.wrapped.LRange(context.TODO(), key, start, stop)
return c.wrapResult(res)
}
// LTrim implements LTrim wrapper for redis
func (c *ClientImpl) LTrim(key string, start, stop int64) Result {
res := c.wrapped.LTrim(context.TODO(), key, start, stop)
return c.wrapResult(res)
}
// LLen implements LLen wrapper for redis
func (c *ClientImpl) LLen(key string) Result {
res := c.wrapped.LLen(context.TODO(), key)
return c.wrapResult(res)
}
// Expire implements Expire wrapper for redis
func (c *ClientImpl) Expire(key string, value time.Duration) Result {
res := c.wrapped.Expire(context.TODO(), key, value)
return c.wrapResult(res)
}
// TTL implements TTL wrapper for redis
func (c *ClientImpl) TTL(key string) Result {
res := c.wrapped.TTL(context.TODO(), key)
return c.wrapResult(res)
}
// MGet implements MGet wrapper for redis
func (c *ClientImpl) MGet(keys []string) Result {
res := c.wrapped.MGet(context.TODO(), keys...)
return c.wrapResult(res)
}
// SCard implements SCard wrapper for redis
func (c *ClientImpl) SCard(key string) Result {
res := c.wrapped.SCard(context.TODO(), key)
return c.wrapResult(res)
}
// Eval implements Eval wrapper for redis
func (c *ClientImpl) Eval(script string, keys []string, args ...interface{}) Result {
res := c.wrapped.Eval(context.TODO(), script, keys, args...)
return c.wrapResult(res)
}
// NewClient returns new client implementation
func NewClient(options *UniversalOptions) (Client, error) {
return &ClientImpl{
wrapped: redis.NewUniversalClient(options),
}, nil
}

214
vendor/github.com/splitio/go-toolkit/v3/sse/sse.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,214 @@
package sse
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// OK It could connect streaming
OK = iota
// ErrorOnClientCreation Could not create client
ErrorOnClientCreation
// ErrorRequestPerformed Could not perform request
ErrorRequestPerformed
// ErrorConnectToStreaming Could not connect to streaming
ErrorConnectToStreaming
// ErrorReadingStream Error in streaming
ErrorReadingStream
// ErrorKeepAlive timedout
ErrorKeepAlive
// ErrorInternal Internal error for streaming
ErrorInternal
// ErrorUnexpected unexpected error occures
ErrorUnexpected
)
var sseDelimiter [2]byte = [...]byte{':', ' '}
var sseData [4]byte = [...]byte{'d', 'a', 't', 'a'}
var sseKeepAlive [10]byte = [...]byte{':', 'k', 'e', 'e', 'p', 'a', 'l', 'i', 'v', 'e'}
// SSEClient struct
type SSEClient struct {
url string
client http.Client
status chan int
shutdown chan struct{}
timeout int
logger logging.LoggerInterface
}
// NewSSEClient creates new SSEClient
func NewSSEClient(url string, status chan int, timeout int, logger logging.LoggerInterface) (*SSEClient, error) {
if cap(status) < 1 {
return nil, errors.New("Status channel should have length")
}
if timeout < 1 {
return nil, errors.New("Timeout should be higher than 0")
}
return &SSEClient{
url: url,
client: http.Client{},
status: status,
shutdown: make(chan struct{}, 1),
timeout: timeout,
logger: logger,
}, nil
}
// Shutdown stops SSE
func (l *SSEClient) Shutdown() {
select {
case l.shutdown <- struct{}{}:
default:
l.logger.Error("Awaited unexpected event")
}
}
func parseData(raw []byte) (map[string]interface{}, error) {
data := make(map[string]interface{})
err := json.Unmarshal(raw, &data)
if err != nil {
return nil, fmt.Errorf("error parsing json: %w", err)
}
return data, nil
}
func (l *SSEClient) readEvent(reader *bufio.Reader) (map[string]interface{}, error) {
line, err := reader.ReadBytes('\n')
if err != nil {
return nil, err
}
if len(line) < 2 {
return nil, nil
}
splitted := bytes.Split(line, sseDelimiter[:])
if bytes.Compare(splitted[0], sseData[:]) != 0 {
return nil, nil
}
raw := bytes.TrimSpace(splitted[1])
l.logger.Debug("LINE:", string(line))
data, err := parseData(raw)
if err != nil {
l.logger.Error("Error parsing event: ", err)
return nil, nil
}
return data, nil
}
func parseHTTPError(resp *http.Response) int {
if resp.StatusCode >= http.StatusInternalServerError {
return ErrorInternal
}
return ErrorConnectToStreaming
}
// Do starts streaming
func (l *SSEClient) Do(params map[string]string, callback func(e map[string]interface{})) {
select {
case <-l.shutdown:
// Skipping previous shutdown
default:
}
ctx, cancel := context.WithCancel(context.Background())
shouldRun := atomic.Value{}
shouldRun.Store(false)
activeGoroutines := sync.WaitGroup{}
defer func() {
l.logger.Info("SSE streaming exiting")
cancel()
shouldRun.Store(false)
activeGoroutines.Wait()
}()
req, err := http.NewRequest("GET", l.url, nil)
if err != nil {
l.logger.Error(err)
l.status <- ErrorOnClientCreation
return
}
req = req.WithContext(ctx)
query := req.URL.Query()
for key, value := range params {
query.Add(key, value)
}
req.URL.RawQuery = query.Encode()
req.Header.Set("Accept", "text/event-stream")
resp, err := l.client.Do(req)
if err != nil {
l.logger.Error(err)
l.status <- ErrorRequestPerformed
return
}
if resp.StatusCode != 200 {
l.status <- parseHTTPError(resp)
return
}
defer resp.Body.Close()
l.status <- OK
reader := bufio.NewReader(resp.Body)
eventChannel := make(chan map[string]interface{}, 1000)
shouldRun.Store(true)
go func() {
for shouldRun.Load().(bool) {
event, err := l.readEvent(reader)
if err != nil {
l.logger.Error(err)
close(eventChannel)
return
}
eventChannel <- event
}
}()
// Create timeout timer in case SSE dont receive notifications or keepalive messages
idleDuration := time.Duration(l.timeout) * time.Second
keepAliveTimer := time.NewTimer(idleDuration)
defer keepAliveTimer.Stop()
for {
// Resetting timer
keepAliveTimer.Reset(idleDuration)
select {
case <-l.shutdown:
l.logger.Info("Shutting down listener")
return
case event, ok := <-eventChannel:
if !ok {
l.status <- ErrorReadingStream
return
}
if event != nil {
activeGoroutines.Add(1)
go func() {
defer activeGoroutines.Done()
callback(event)
}()
}
case <-keepAliveTimer.C: // Timedout
l.status <- ErrorKeepAlive
return
}
}
}

166
vendor/github.com/splitio/go-toolkit/v3/workerpool/workerpool.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,166 @@
package workerpool
import (
"fmt"
"github.com/splitio/go-toolkit/v3/logging"
"sync"
"time"
)
const (
workerSignalStop = iota
)
// WorkerAdmin struct handles multiple worker execution, popping jobs from a single queue
type WorkerAdmin struct {
queue chan interface{}
signalsMutex sync.RWMutex
signals map[string]chan int
logger logging.LoggerInterface
wg sync.WaitGroup
}
// Worker interface should be implemented by concrete workers that will perform the actual job
type Worker interface {
// Name should return a unique identifier for a particular worker
Name() string
// DoWork should receive a message, and perform the actual work, only an error should be returned
DoWork(message interface{}) error
// OnError will be called if DoWork returns an error != nil
OnError(e error)
// Cleanup will be called when the worker is shutting down
Cleanup() error
// FailureTime should return the amount of time the worker should wait after resuming work if an error occurs
FailureTime() int64
}
func (a *WorkerAdmin) workerWrapper(w Worker) {
a.signalsMutex.Lock()
a.signals[w.Name()] = make(chan int, 10)
a.signalsMutex.Unlock()
defer a.wg.Done()
defer func() {
if r := recover(); r != nil {
a.logger.Error(fmt.Sprintf(
"Worker %s is panicking with the following error \"%s\" and will be shutted down.",
w.Name(),
r,
))
}
if a.signals != nil { // This should ALWAYS be the case, but just in case... we don't want to panic here.
a.signalsMutex.Lock()
delete(a.signals, w.Name())
a.signalsMutex.Unlock()
}
}()
defer w.Cleanup()
for {
a.signalsMutex.RLock()
signal := a.signals[w.Name()]
a.signalsMutex.RUnlock()
select {
case msg := <-signal:
switch msg {
case workerSignalStop:
return
}
case msg := <-a.queue:
if err := w.DoWork(msg); err != nil {
w.OnError(err)
time.Sleep(time.Duration(w.FailureTime()) * time.Millisecond)
}
}
}
}
// AddWorker registers a new worker in the admin
func (a *WorkerAdmin) AddWorker(w Worker) {
if w == nil {
a.logger.Error("AddWorker called with nil")
return
}
a.wg.Add(1)
go a.workerWrapper(w)
}
// QueueMessage adds a new message that will be popped by a worker and processed
func (a *WorkerAdmin) QueueMessage(m interface{}) bool {
if m == nil {
a.logger.Warning("Nil message not added to queue")
return false
}
select {
case a.queue <- m:
return true
default:
return false
}
}
// StopWorker ends the worker's event loop, preventing it from picking further jobs
func (a *WorkerAdmin) StopWorker(name string) error {
a.signalsMutex.RLock()
c, ok := a.signals[name]
a.signalsMutex.RUnlock()
if !ok {
return fmt.Errorf("Worker %s doesn't exist, hence it cannot be stopped", name)
}
select {
case c <- workerSignalStop:
default:
return fmt.Errorf("Couldn't send stop signal to worker %s", name)
}
return nil
}
// StopAll ends all worker's event loops
func (a *WorkerAdmin) StopAll(blocking bool) error {
failed := make([]string, 0)
workerNames := make([]string, 0)
// Get worker names safely
a.signalsMutex.RLock()
for workerName := range a.signals {
workerNames = append(workerNames, workerName)
}
a.signalsMutex.RUnlock()
for _, workerName := range workerNames {
err := a.StopWorker(workerName)
if err != nil {
a.logger.Error(err)
failed = append(failed, workerName)
}
}
if len(failed) > 0 {
return fmt.Errorf("Workers %v failed to shutdown", failed)
}
if blocking {
a.wg.Wait()
}
return nil
}
// QueueSize returns the current queue size
func (a *WorkerAdmin) QueueSize() int {
return len(a.queue)
}
// IsWorkerRunning returns true if the worker exists and is currently running
func (a *WorkerAdmin) IsWorkerRunning(name string) bool {
a.signalsMutex.RLock()
_, ok := a.signals[name]
a.signalsMutex.RUnlock()
return ok // We consider a worker to be running if it exists in the list of valid signal channels
}
// NewWorkerAdmin instantiates a new WorkerAdmin and returns a pointer to it.
func NewWorkerAdmin(queueSize int, logger logging.LoggerInterface) *WorkerAdmin {
return &WorkerAdmin{
signals: make(map[string]chan int, 0),
logger: logger,
queue: make(chan interface{}, queueSize),
}
}