ldclient-rb 0.8.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +3 -3
- data/ext/mkrf_conf.rb +11 -0
- data/ldclient-rb.gemspec +3 -1
- data/lib/ldclient-rb.rb +6 -2
- data/lib/ldclient-rb/{store.rb → cache_store.rb} +0 -0
- data/lib/ldclient-rb/config.rb +27 -29
- data/lib/ldclient-rb/evaluation.rb +265 -0
- data/lib/ldclient-rb/events.rb +75 -0
- data/lib/ldclient-rb/feature_store.rb +60 -0
- data/lib/ldclient-rb/ldclient.rb +92 -303
- data/lib/ldclient-rb/polling.rb +56 -0
- data/lib/ldclient-rb/requestor.rb +56 -0
- data/lib/ldclient-rb/stream.rb +40 -140
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/config_spec.rb +3 -3
- data/spec/fixtures/feature.json +27 -58
- data/spec/ldclient_spec.rb +20 -226
- data/spec/stream_spec.rb +7 -63
- metadata +28 -7
- data/lib/ldclient-rb/settings.rb +0 -40
@@ -0,0 +1,75 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "faraday"
|
3
|
+
|
4
|
+
module LaunchDarkly
|
5
|
+
|
6
|
+
class EventProcessor
|
7
|
+
def initialize(sdk_key, config)
|
8
|
+
@queue = Queue.new
|
9
|
+
@sdk_key = sdk_key
|
10
|
+
@config = config
|
11
|
+
@client = Faraday.new
|
12
|
+
@worker = create_worker
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_worker
|
16
|
+
Thread.new do
|
17
|
+
loop do
|
18
|
+
begin
|
19
|
+
flush
|
20
|
+
sleep(@config.flush_interval)
|
21
|
+
rescue StandardError => exn
|
22
|
+
log_exception(__method__.to_s, exn)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_flushed_events(events)
|
29
|
+
res = @client.post (@config.events_uri + "/bulk") do |req|
|
30
|
+
req.headers["Authorization"] = @sdk_key
|
31
|
+
req.headers["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
32
|
+
req.headers["Content-Type"] = "application/json"
|
33
|
+
req.body = events.to_json
|
34
|
+
req.options.timeout = @config.read_timeout
|
35
|
+
req.options.open_timeout = @config.connect_timeout
|
36
|
+
end
|
37
|
+
if res.status / 100 != 2
|
38
|
+
@config.logger.error("[LDClient] Unexpected status code while processing events: #{res.status}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def flush
|
43
|
+
events = []
|
44
|
+
begin
|
45
|
+
loop do
|
46
|
+
events << @queue.pop(true)
|
47
|
+
end
|
48
|
+
rescue ThreadError
|
49
|
+
end
|
50
|
+
|
51
|
+
if !events.empty?
|
52
|
+
post_flushed_events(events)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_event(event)
|
57
|
+
return if @offline
|
58
|
+
|
59
|
+
if @queue.length < @config.capacity
|
60
|
+
event[:creationDate] = (Time.now.to_f * 1000).to_i
|
61
|
+
@config.logger.debug("[LDClient] Enqueueing event: #{event.to_json}")
|
62
|
+
@queue.push(event)
|
63
|
+
|
64
|
+
if !@worker.alive?
|
65
|
+
@worker = create_worker
|
66
|
+
end
|
67
|
+
else
|
68
|
+
@config.logger.warn("[LDClient] Exceeded event queue capacity. Increase capacity to avoid dropping events.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private :create_worker, :post_flushed_events
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "concurrent/atomics"
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
|
5
|
+
class InMemoryFeatureStore
|
6
|
+
def initialize
|
7
|
+
@features = Hash.new
|
8
|
+
@lock = Concurrent::ReadWriteLock.new
|
9
|
+
@initialized = Concurrent::AtomicBoolean.new(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(key)
|
13
|
+
@lock.with_read_lock do
|
14
|
+
f = @features[key.to_sym]
|
15
|
+
(f.nil? || f[:deleted]) ? nil : f
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def all
|
20
|
+
@lock.with_read_lock do
|
21
|
+
@features.select { |_k, f| not f[:deleted] }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key, version)
|
26
|
+
@lock.with_write_lock do
|
27
|
+
old = @features[key.to_sym]
|
28
|
+
|
29
|
+
if !old.nil? && old[:version] < version
|
30
|
+
old[:deleted] = true
|
31
|
+
old[:version] = version
|
32
|
+
@features[key.to_sym] = old
|
33
|
+
elsif old.nil?
|
34
|
+
@features[key.to_sym] = { deleted: true, version: version }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def init(fs)
|
40
|
+
@lock.with_write_lock do
|
41
|
+
@features.replace(fs)
|
42
|
+
@initialized.make_true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def upsert(key, feature)
|
47
|
+
@lock.with_write_lock do
|
48
|
+
old = @features[key.to_sym]
|
49
|
+
|
50
|
+
if old.nil? || old[:version] < feature[:version]
|
51
|
+
@features[key.to_sym] = feature
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialized?
|
57
|
+
@initialized.value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -1,100 +1,77 @@
|
|
1
|
-
require "faraday/http_cache"
|
2
|
-
require "json"
|
3
1
|
require "digest/sha1"
|
4
|
-
require "thread"
|
5
2
|
require "logger"
|
6
|
-
require "net/http/persistent"
|
7
3
|
require "benchmark"
|
8
|
-
require "
|
4
|
+
require "waitutil"
|
5
|
+
require "json"
|
6
|
+
require "openssl"
|
9
7
|
|
10
8
|
module LaunchDarkly
|
11
|
-
BUILTINS = [:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
|
12
|
-
|
13
9
|
#
|
14
|
-
# A client for
|
10
|
+
# A client for LaunchDarkly. Client instances are thread-safe. Users
|
15
11
|
# should create a single client instance for the lifetime of the application.
|
16
12
|
#
|
17
13
|
#
|
18
14
|
class LDClient
|
19
|
-
include
|
15
|
+
include Evaluation
|
20
16
|
#
|
21
17
|
# Creates a new client instance that connects to LaunchDarkly. A custom
|
22
18
|
# configuration parameter can also supplied to specify advanced options,
|
23
19
|
# but for most use cases, the default configuration is appropriate.
|
24
20
|
#
|
25
21
|
#
|
26
|
-
# @param
|
22
|
+
# @param sdk_key [String] the SDK key for your LaunchDarkly account
|
27
23
|
# @param config [Config] an optional client configuration object
|
28
24
|
#
|
29
25
|
# @return [LDClient] The LaunchDarkly client instance
|
30
|
-
def initialize(
|
31
|
-
@
|
32
|
-
@api_key = api_key
|
26
|
+
def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
|
27
|
+
@sdk_key = sdk_key
|
33
28
|
@config = config
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
29
|
+
@store = config.feature_store
|
30
|
+
requestor = Requestor.new(sdk_key, config)
|
31
|
+
|
32
|
+
if !@config.offline?
|
33
|
+
if @config.stream?
|
34
|
+
@update_processor = StreamProcessor.new(sdk_key, config, requestor)
|
35
|
+
else
|
36
|
+
@update_processor = PollingProcessor.new(config, requestor)
|
37
|
+
end
|
38
|
+
@update_processor.start
|
43
39
|
end
|
44
40
|
|
45
|
-
@
|
46
|
-
end
|
41
|
+
@event_processor = EventProcessor.new(sdk_key, config)
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
43
|
+
if !@config.offline? && wait_for_sec > 0
|
44
|
+
begin
|
45
|
+
WaitUtil.wait_for_condition("LaunchDarkly client initialization", :timeout_sec => wait_for_sec, :delay_sec => 0.1) do
|
46
|
+
@update_processor.initialized?
|
47
|
+
end
|
48
|
+
rescue WaitUtil::TimeoutError
|
49
|
+
@config.logger.error("[LDClient] Timeout encountered waiting for LaunchDarkly client initialization")
|
53
50
|
end
|
54
|
-
rescue ThreadError
|
55
|
-
end
|
56
|
-
|
57
|
-
if !events.empty?
|
58
|
-
post_flushed_events(events)
|
59
51
|
end
|
60
52
|
end
|
61
53
|
|
62
|
-
def
|
63
|
-
|
64
|
-
next @client.post (@config.events_uri + "/bulk") do |req|
|
65
|
-
req.headers["Authorization"] = "api_key " + @api_key
|
66
|
-
req.headers["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
67
|
-
req.headers["Content-Type"] = "application/json"
|
68
|
-
req.body = events.to_json
|
69
|
-
req.options.timeout = @config.read_timeout
|
70
|
-
req.options.open_timeout = @config.connect_timeout
|
71
|
-
end
|
72
|
-
end
|
73
|
-
if res.status / 100 != 2
|
74
|
-
@config.logger.error("[LDClient] Unexpected status code while processing events: #{res.status}")
|
75
|
-
end
|
54
|
+
def flush
|
55
|
+
@event_processor.flush
|
76
56
|
end
|
77
57
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
flush
|
58
|
+
def toggle?(key, user, default = False)
|
59
|
+
@config.logger.warn("[LDClient] toggle? is deprecated. Use variation instead")
|
60
|
+
variation(key, user, default)
|
61
|
+
end
|
83
62
|
|
84
|
-
|
85
|
-
|
86
|
-
log_exception(__method__.to_s, exn)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
63
|
+
def secure_mode_hash(user)
|
64
|
+
OpenSSL::HMAC.hexdigest('sha256', @sdk_key, user[:key].to_s)
|
90
65
|
end
|
91
66
|
|
92
|
-
|
93
|
-
|
67
|
+
# Returns whether the client has been initialized and is ready to serve feature flag requests
|
68
|
+
# @return [Boolean] true if the client has been initialized
|
69
|
+
def initialized?
|
70
|
+
@update_processor.initialized?
|
94
71
|
end
|
95
72
|
|
96
73
|
#
|
97
|
-
#
|
74
|
+
# Determines the variation of a feature flag to present to a user. At a minimum,
|
98
75
|
# the user hash should contain a +:key+ .
|
99
76
|
#
|
100
77
|
# @example Basic user hash
|
@@ -110,8 +87,6 @@ module LaunchDarkly
|
|
110
87
|
# @example More complete user hash
|
111
88
|
# {key: "user@example.com", ip: "127.0.0.1", country: "US"}
|
112
89
|
#
|
113
|
-
# Countries should be sent as ISO 3166-1 alpha-2 codes.
|
114
|
-
#
|
115
90
|
# The user hash can contain arbitrary custom attributes stored in a +:custom+ sub-hash:
|
116
91
|
#
|
117
92
|
# @example A user hash with custom attributes
|
@@ -123,51 +98,53 @@ module LaunchDarkly
|
|
123
98
|
# @param key [String] the unique feature key for the feature flag, as shown
|
124
99
|
# on the LaunchDarkly dashboard
|
125
100
|
# @param user [Hash] a hash containing parameters for the end user requesting the flag
|
126
|
-
# @param default=false
|
101
|
+
# @param default=false the default value of the flag
|
127
102
|
#
|
128
|
-
# @return
|
129
|
-
# default value if
|
130
|
-
def
|
131
|
-
return default if @offline
|
103
|
+
# @return the variation to show the user, or the
|
104
|
+
# default value if there's an an error
|
105
|
+
def variation(key, user, default)
|
106
|
+
return default if @config.offline?
|
132
107
|
|
133
108
|
unless user
|
134
109
|
@config.logger.error("[LDClient] Must specify user")
|
110
|
+
@event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
|
135
111
|
return default
|
136
112
|
end
|
137
|
-
sanitize_user(user)
|
138
|
-
|
139
|
-
if @config.stream? && !@stream_processor.started?
|
140
|
-
@stream_processor.start
|
141
|
-
end
|
142
113
|
|
143
|
-
if
|
144
|
-
|
145
|
-
|
146
|
-
|
114
|
+
if !@update_processor.initialized?
|
115
|
+
@config.logger.error("[LDClient] Client has not finished initializing. Returning default value")
|
116
|
+
@event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
|
117
|
+
return default
|
147
118
|
end
|
148
|
-
value = evaluate(feature, user)
|
149
|
-
value = value.nil? ? default : value
|
150
119
|
|
151
|
-
|
152
|
-
|
153
|
-
return value
|
154
|
-
rescue StandardError => error
|
155
|
-
log_exception(__method__.to_s, error)
|
156
|
-
default
|
157
|
-
end
|
158
|
-
|
159
|
-
def add_event(event)
|
160
|
-
return if @offline
|
120
|
+
sanitize_user(user)
|
121
|
+
feature = @store.get(key)
|
161
122
|
|
162
|
-
if
|
163
|
-
|
164
|
-
@
|
123
|
+
if feature.nil?
|
124
|
+
@config.logger.error("[LDClient] Unknown feature flag #{key}. Returning default value")
|
125
|
+
@event_processor.add_event(kind: "feature", key: key, value: default, default: default, user: user)
|
126
|
+
return default
|
127
|
+
end
|
165
128
|
|
166
|
-
|
167
|
-
|
129
|
+
begin
|
130
|
+
res = evaluate(feature, user, @store)
|
131
|
+
if !res[:events].nil?
|
132
|
+
res[:events].each do |event|
|
133
|
+
@event_processor.add_event(event)
|
134
|
+
end
|
168
135
|
end
|
169
|
-
|
170
|
-
|
136
|
+
if !res[:value].nil?
|
137
|
+
@event_processor.add_event(kind: "feature", key: key, user: user, value: res[:value], default: default, version: feature[:version])
|
138
|
+
return res[:value]
|
139
|
+
else
|
140
|
+
@config.logger.debug("[LDClient] Result value is null in toggle")
|
141
|
+
@event_processor.add_event(kind: "feature", key: key, user: user, value: default, default: default, version: feature[:version])
|
142
|
+
return default
|
143
|
+
end
|
144
|
+
rescue => exn
|
145
|
+
@config.logger.warn("[LDClient] Error evaluating feature flag: #{exn.inspect}. \nTrace: #{exn.backtrace}")
|
146
|
+
@event_processor.add_event(kind: "feature", key: key, user: user, value: default, default: default, version: feature[:version])
|
147
|
+
return default
|
171
148
|
end
|
172
149
|
end
|
173
150
|
|
@@ -176,21 +153,10 @@ module LaunchDarkly
|
|
176
153
|
#
|
177
154
|
# @param [Hash] The user to register
|
178
155
|
#
|
156
|
+
# @return [void]
|
179
157
|
def identify(user)
|
180
158
|
sanitize_user(user)
|
181
|
-
add_event(kind: "identify", key: user[:key], user: user)
|
182
|
-
end
|
183
|
-
|
184
|
-
def set_offline
|
185
|
-
@offline = true
|
186
|
-
end
|
187
|
-
|
188
|
-
def set_online
|
189
|
-
@offline = false
|
190
|
-
end
|
191
|
-
|
192
|
-
def is_offline?
|
193
|
-
@offline
|
159
|
+
@event_processor.add_event(kind: "identify", key: user[:key], user: user)
|
194
160
|
end
|
195
161
|
|
196
162
|
#
|
@@ -203,204 +169,30 @@ module LaunchDarkly
|
|
203
169
|
# @return [void]
|
204
170
|
def track(event_name, user, data)
|
205
171
|
sanitize_user(user)
|
206
|
-
add_event(kind: "custom", key: event_name, user: user, data: data)
|
207
|
-
end
|
208
|
-
|
209
|
-
#
|
210
|
-
# Returns the key of every feature flag
|
211
|
-
#
|
212
|
-
def all_keys
|
213
|
-
all_flags.keys
|
172
|
+
@event_processor.add_event(kind: "custom", key: event_name, user: user, data: data)
|
214
173
|
end
|
215
174
|
|
216
175
|
#
|
217
|
-
# Returns all feature
|
176
|
+
# Returns all feature flag values for the given user
|
218
177
|
#
|
219
|
-
def all_flags
|
220
|
-
|
221
|
-
|
222
|
-
if @config.stream? && !@stream_processor.started?
|
223
|
-
@stream_processor.start
|
224
|
-
end
|
225
|
-
|
226
|
-
if @config.stream? && @stream_processor.initialized?
|
227
|
-
@stream_processor.get_all_features
|
228
|
-
else
|
229
|
-
res = make_request "/api/eval/features"
|
230
|
-
|
231
|
-
if res.status / 100 == 2
|
232
|
-
JSON.parse(res.body, symbolize_names: true)
|
233
|
-
else
|
234
|
-
@config.logger.error("[LDClient] Unexpected status code #{res.status}")
|
235
|
-
Hash.new
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def get_user_settings(user)
|
241
|
-
Hash[all_flags.map { |key, feature| [key, evaluate(feature, user)]}]
|
242
|
-
end
|
243
|
-
|
244
|
-
def get_streamed_flag(key)
|
245
|
-
feature = get_flag_stream(key)
|
246
|
-
if @config.debug_stream?
|
247
|
-
polled = get_flag_int(key)
|
248
|
-
diff = HashDiff.diff(feature, polled)
|
249
|
-
if not diff.empty?
|
250
|
-
@config.logger.error("Streamed flag differs from polled flag " + diff.to_s)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
feature
|
254
|
-
end
|
255
|
-
|
256
|
-
def get_flag_stream(key)
|
257
|
-
@stream_processor.get_feature(key)
|
258
|
-
end
|
259
|
-
|
260
|
-
def get_flag_int(key)
|
261
|
-
res = log_timings("Feature request") do
|
262
|
-
next make_request "/api/eval/features/" + key
|
263
|
-
end
|
264
|
-
|
265
|
-
if res.status == 401
|
266
|
-
@config.logger.error("[LDClient] Invalid API key")
|
267
|
-
return nil
|
268
|
-
end
|
269
|
-
|
270
|
-
if res.status == 404
|
271
|
-
@config.logger.error("[LDClient] Unknown feature key: #{key}")
|
272
|
-
return nil
|
273
|
-
end
|
274
|
-
|
275
|
-
if res.status / 100 != 2
|
276
|
-
@config.logger.error("[LDClient] Unexpected status code #{res.status}")
|
277
|
-
return nil
|
278
|
-
end
|
279
|
-
|
280
|
-
JSON.parse(res.body, symbolize_names: true)
|
281
|
-
end
|
282
|
-
|
283
|
-
def make_request(path)
|
284
|
-
@client.get (@config.base_uri + path) do |req|
|
285
|
-
req.headers["Authorization"] = "api_key " + @api_key
|
286
|
-
req.headers["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
287
|
-
req.options.timeout = @config.read_timeout
|
288
|
-
req.options.open_timeout = @config.connect_timeout
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def param_for_user(feature, user)
|
293
|
-
return nil unless user[:key]
|
294
|
-
|
295
|
-
id_hash = user[:key]
|
296
|
-
if user[:secondary]
|
297
|
-
id_hash += "." + user[:secondary]
|
298
|
-
end
|
299
|
-
|
300
|
-
hash_key = "%s.%s.%s" % [feature[:key], feature[:salt], id_hash]
|
301
|
-
|
302
|
-
hash_val = (Digest::SHA1.hexdigest(hash_key))[0..14]
|
303
|
-
hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
|
304
|
-
end
|
305
|
-
|
306
|
-
def match_target?(target, user)
|
307
|
-
attrib = target[:attribute].to_sym
|
308
|
-
|
309
|
-
if BUILTINS.include?(attrib)
|
310
|
-
return false unless user[attrib]
|
311
|
-
|
312
|
-
u_value = user[attrib]
|
313
|
-
return target[:values].include? u_value
|
314
|
-
else # custom attribute
|
315
|
-
return false unless user[:custom]
|
316
|
-
return false unless user[:custom].include? attrib
|
317
|
-
|
318
|
-
u_value = user[:custom][attrib]
|
319
|
-
if u_value.is_a? Array
|
320
|
-
return ! ((target[:values] & u_value).empty?)
|
321
|
-
else
|
322
|
-
return target[:values].include? u_value
|
323
|
-
end
|
324
|
-
|
325
|
-
return false
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
def match_user?(variation, user)
|
330
|
-
if variation[:userTarget]
|
331
|
-
return match_target?(variation[:userTarget], user)
|
332
|
-
end
|
333
|
-
false
|
334
|
-
end
|
335
|
-
|
336
|
-
def find_user_match(feature, user)
|
337
|
-
feature[:variations].each do |variation|
|
338
|
-
return variation[:value] if match_user?(variation, user)
|
339
|
-
end
|
340
|
-
nil
|
341
|
-
end
|
342
|
-
|
343
|
-
def match_variation?(variation, user)
|
344
|
-
variation[:targets].each do |target|
|
345
|
-
if !!variation[:userTarget] && target[:attribute].to_sym == :key
|
346
|
-
next
|
347
|
-
end
|
348
|
-
|
349
|
-
if match_target?(target, user)
|
350
|
-
return true
|
351
|
-
end
|
352
|
-
end
|
353
|
-
false
|
354
|
-
end
|
355
|
-
|
356
|
-
def find_target_match(feature, user)
|
357
|
-
feature[:variations].each do |variation|
|
358
|
-
return variation[:value] if match_variation?(variation, user)
|
359
|
-
end
|
360
|
-
nil
|
361
|
-
end
|
362
|
-
|
363
|
-
def find_weight_match(feature, param)
|
364
|
-
total = 0.0
|
365
|
-
feature[:variations].each do |variation|
|
366
|
-
total += variation[:weight].to_f / 100.0
|
178
|
+
def all_flags(user)
|
179
|
+
sanitize_user(user)
|
180
|
+
return Hash.new if @config.offline?
|
367
181
|
|
368
|
-
|
182
|
+
unless user
|
183
|
+
@config.logger.error("[LDClient] Must specify user in all_flags")
|
184
|
+
return Hash.new
|
369
185
|
end
|
370
186
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
def evaluate(feature, user)
|
375
|
-
return nil if feature.nil?
|
376
|
-
return nil unless feature[:on]
|
377
|
-
|
378
|
-
param = param_for_user(feature, user)
|
379
|
-
return nil if param.nil?
|
380
|
-
|
381
|
-
value = find_user_match(feature, user)
|
382
|
-
return value if !value.nil?
|
383
|
-
|
384
|
-
value = find_target_match(feature, user)
|
385
|
-
return value if !value.nil?
|
386
|
-
|
387
|
-
find_weight_match(feature, param)
|
388
|
-
end
|
187
|
+
begin
|
188
|
+
features = @store.all
|
389
189
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
begin
|
396
|
-
res = block.call
|
397
|
-
rescue StandardError => e
|
398
|
-
exn = e
|
399
|
-
end
|
190
|
+
# TODO rescue if necessary
|
191
|
+
Hash[features.map{|k,f| [k, evaluate(f, user, @store)[:value]] }]
|
192
|
+
rescue => exn
|
193
|
+
@config.logger.warn("[LDClient] Error evaluating all flags: #{exn.inspect}. \nTrace: #{exn.backtrace}")
|
194
|
+
return Hash.new
|
400
195
|
end
|
401
|
-
@config.logger.debug { "[LDClient] #{label} timing: #{bench}".chomp }
|
402
|
-
raise exn if exn
|
403
|
-
res
|
404
196
|
end
|
405
197
|
|
406
198
|
def log_exception(caller, exn)
|
@@ -415,9 +207,6 @@ module LaunchDarkly
|
|
415
207
|
end
|
416
208
|
end
|
417
209
|
|
418
|
-
private :
|
419
|
-
:get_flag_stream, :get_flag_int, :make_request, :param_for_user,
|
420
|
-
:match_target?, :match_user?, :match_variation?, :evaluate,
|
421
|
-
:create_worker, :log_timings, :log_exception, :sanitize_user
|
210
|
+
private :evaluate, :log_exception, :sanitize_user
|
422
211
|
end
|
423
212
|
end
|