-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecution_logger.go
More file actions
151 lines (122 loc) · 3.22 KB
/
Copy pathexecution_logger.go
File metadata and controls
151 lines (122 loc) · 3.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package client
import (
"maps"
"sync"
)
// ExecutionInput represents a single strategy input captured when a Switcher is evaluated.
type ExecutionInput struct {
Strategy string
Input string
}
// ExecutionEntry stores a cached evaluation for a Switcher including inputs and the response.
// This is used by the client's execution logger to implement throttling and cached returns.
type ExecutionEntry struct {
Key string
Inputs []ExecutionInput
Response ResultDetail
}
type executionLogger struct {
mu sync.RWMutex
entries []ExecutionEntry
}
func newExecutionLogger() *executionLogger {
return &executionLogger{
entries: make([]ExecutionEntry, 0),
}
}
func (l *executionLogger) add(key string, inputs []criteriaEntry, response ResultDetail) {
l.mu.Lock()
defer l.mu.Unlock()
for i := range l.entries {
if executionEntryMatches(l.entries[i], key, inputs) {
l.entries = append(l.entries[:i], l.entries[i+1:]...)
break
}
}
l.entries = append(l.entries, ExecutionEntry{
Key: key,
Inputs: executionInputsFromCriteria(inputs),
Response: cachedResultDetail(response),
})
}
func (l *executionLogger) get(key string, inputs []criteriaEntry) ExecutionEntry {
l.mu.RLock()
defer l.mu.RUnlock()
for _, entry := range l.entries {
if executionEntryMatches(entry, key, inputs) {
return cloneExecutionEntry(entry)
}
}
return ExecutionEntry{}
}
func (l *executionLogger) clear() {
l.mu.Lock()
defer l.mu.Unlock()
l.entries = l.entries[:0]
}
func executionEntryMatches(entry ExecutionEntry, key string, inputs []criteriaEntry) bool {
return entry.Key == key && executionInputsMatch(entry.Inputs, inputs)
}
func executionInputsMatch(logged []ExecutionInput, current []criteriaEntry) bool {
if len(logged) == 0 {
return len(current) == 0
}
if len(current) == 0 {
return false
}
for _, loggedInput := range logged {
found := false
for _, currentInput := range current {
if currentInput.Strategy == loggedInput.Strategy && currentInput.Input == loggedInput.Input {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func executionInputsFromCriteria(inputs []criteriaEntry) []ExecutionInput {
if len(inputs) == 0 {
return nil
}
converted := make([]ExecutionInput, len(inputs))
for i, input := range inputs {
converted[i] = ExecutionInput(input)
}
return converted
}
func cloneExecutionEntry(entry ExecutionEntry) ExecutionEntry {
return ExecutionEntry{
Key: entry.Key,
Inputs: cloneExecutionInputs(entry.Inputs),
Response: cloneResultDetail(entry.Response),
}
}
func cloneExecutionInputs(inputs []ExecutionInput) []ExecutionInput {
if len(inputs) == 0 {
return nil
}
cloned := make([]ExecutionInput, len(inputs))
copy(cloned, inputs)
return cloned
}
func cloneResultDetail(result ResultDetail) ResultDetail {
return ResultDetail{
Result: result.Result,
Reason: result.Reason,
Metadata: cloneMetadata(result.Metadata),
}
}
func cachedResultDetail(result ResultDetail) ResultDetail {
cached := cloneResultDetail(result)
cached.Metadata["cached"] = true
return cached
}
func cloneMetadata(metadata map[string]any) map[string]any {
cloned := make(map[string]any, len(metadata))
maps.Copy(cloned, metadata)
return cloned
}