launchdarkly-server-sdk 7.0.2 → 8.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -4
- data/lib/ldclient-rb/config.rb +50 -70
- data/lib/ldclient-rb/context.rb +65 -50
- data/lib/ldclient-rb/evaluation_detail.rb +5 -1
- data/lib/ldclient-rb/events.rb +81 -8
- data/lib/ldclient-rb/impl/big_segments.rb +1 -1
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +3 -3
- data/lib/ldclient-rb/impl/context_filter.rb +30 -9
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +59 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/event_sender.rb +1 -0
- data/lib/ldclient-rb/impl/event_types.rb +61 -3
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +19 -2
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +25 -3
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/in_memory_store.rb +7 -0
- data/lib/ldclient-rb/integrations/file_data.rb +1 -1
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
- data/lib/ldclient-rb/integrations/test_data.rb +3 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
- data/lib/ldclient-rb/interfaces.rb +671 -0
- data/lib/ldclient-rb/ldclient.rb +313 -22
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/polling.rb +51 -5
- data/lib/ldclient-rb/reference.rb +11 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +91 -29
- data/lib/ldclient-rb/util.rb +89 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +1 -0
- metadata +44 -6
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
require "ldclient-rb/impl/big_segments"
|
2
|
+
require "ldclient-rb/impl/broadcaster"
|
3
|
+
require "ldclient-rb/impl/data_source"
|
4
|
+
require "ldclient-rb/impl/data_store"
|
2
5
|
require "ldclient-rb/impl/diagnostic_events"
|
3
6
|
require "ldclient-rb/impl/evaluator"
|
7
|
+
require "ldclient-rb/impl/evaluation_with_hook_result"
|
8
|
+
require "ldclient-rb/impl/flag_tracker"
|
4
9
|
require "ldclient-rb/impl/store_client_wrapper"
|
10
|
+
require "ldclient-rb/impl/migrations/tracker"
|
11
|
+
require "concurrent"
|
5
12
|
require "concurrent/atomics"
|
6
13
|
require "digest/sha1"
|
14
|
+
require "forwardable"
|
7
15
|
require "logger"
|
8
16
|
require "benchmark"
|
9
17
|
require "json"
|
@@ -16,6 +24,10 @@ module LaunchDarkly
|
|
16
24
|
#
|
17
25
|
class LDClient
|
18
26
|
include Impl
|
27
|
+
extend Forwardable
|
28
|
+
|
29
|
+
def_delegators :@config, :logger
|
30
|
+
|
19
31
|
#
|
20
32
|
# Creates a new client instance that connects to LaunchDarkly. A custom
|
21
33
|
# configuration parameter can also supplied to specify advanced options,
|
@@ -44,16 +56,24 @@ module LaunchDarkly
|
|
44
56
|
end
|
45
57
|
|
46
58
|
@sdk_key = sdk_key
|
59
|
+
@hooks = Concurrent::Array.new(config.hooks)
|
60
|
+
|
61
|
+
@shared_executor = Concurrent::SingleThreadExecutor.new
|
62
|
+
|
63
|
+
data_store_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, config.logger)
|
64
|
+
store_sink = LaunchDarkly::Impl::DataStore::UpdateSink.new(data_store_broadcaster)
|
47
65
|
|
48
66
|
# We need to wrap the feature store object with a FeatureStoreClientWrapper in order to add
|
49
67
|
# some necessary logic around updates. Unfortunately, we have code elsewhere that accesses
|
50
68
|
# the feature store through the Config object, so we need to make a new Config that uses
|
51
69
|
# the wrapped store.
|
52
|
-
@store = Impl::FeatureStoreClientWrapper.new(config.feature_store)
|
70
|
+
@store = Impl::FeatureStoreClientWrapper.new(config.feature_store, store_sink, config.logger)
|
53
71
|
updated_config = config.clone
|
54
72
|
updated_config.instance_variable_set(:@feature_store, @store)
|
55
73
|
@config = updated_config
|
56
74
|
|
75
|
+
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProvider.new(@store, store_sink)
|
76
|
+
|
57
77
|
@big_segment_store_manager = Impl::BigSegmentStoreManager.new(config.big_segments, @config.logger)
|
58
78
|
@big_segment_store_status_provider = @big_segment_store_manager.status_provider
|
59
79
|
|
@@ -76,9 +96,20 @@ module LaunchDarkly
|
|
76
96
|
|
77
97
|
if @config.use_ldd?
|
78
98
|
@config.logger.info { "[LDClient] Started LaunchDarkly Client in LDD mode" }
|
99
|
+
@data_source = NullUpdateProcessor.new
|
79
100
|
return # requestor and update processor are not used in this mode
|
80
101
|
end
|
81
102
|
|
103
|
+
flag_tracker_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
|
104
|
+
@flag_tracker = LaunchDarkly::Impl::FlagTracker.new(flag_tracker_broadcaster, lambda { |key, context| variation(key, context, nil) })
|
105
|
+
|
106
|
+
data_source_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
|
107
|
+
|
108
|
+
# Make the update sink available on the config so that our data source factory can access the sink with a shared executor.
|
109
|
+
@config.data_source_update_sink = LaunchDarkly::Impl::DataSource::UpdateSink.new(@store, data_source_broadcaster, flag_tracker_broadcaster)
|
110
|
+
|
111
|
+
@data_source_status_provider = LaunchDarkly::Impl::DataSource::StatusProvider.new(data_source_broadcaster, @config.data_source_update_sink)
|
112
|
+
|
82
113
|
data_source_or_factory = @config.data_source || self.method(:create_default_data_source)
|
83
114
|
if data_source_or_factory.respond_to? :call
|
84
115
|
# Currently, data source factories take two parameters unless they need to be aware of diagnostic_accumulator, in
|
@@ -103,6 +134,23 @@ module LaunchDarkly
|
|
103
134
|
end
|
104
135
|
end
|
105
136
|
|
137
|
+
#
|
138
|
+
# Add a hook to the client. In order to register a hook before the client starts, please use the `hooks` property of
|
139
|
+
# {#LDConfig}.
|
140
|
+
#
|
141
|
+
# Hooks provide entrypoints which allow for observation of SDK functions.
|
142
|
+
#
|
143
|
+
# @param hook [Interfaces::Hooks::Hook]
|
144
|
+
#
|
145
|
+
def add_hook(hook)
|
146
|
+
unless hook.is_a?(Interfaces::Hooks::Hook)
|
147
|
+
@config.logger.error { "[LDClient] Attempted to add a hook that does not include the LaunchDarkly::Intefaces::Hooks::Hook mixin. Ignoring." }
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
@hooks.push(hook)
|
152
|
+
end
|
153
|
+
|
106
154
|
#
|
107
155
|
# Tells the client that all pending analytics events should be delivered as soon as possible.
|
108
156
|
#
|
@@ -127,7 +175,7 @@ module LaunchDarkly
|
|
127
175
|
# @return [String, nil] a hash string or nil if the provided context was invalid
|
128
176
|
#
|
129
177
|
def secure_mode_hash(context)
|
130
|
-
context = Impl::Context
|
178
|
+
context = Impl::Context.make_context(context)
|
131
179
|
unless context.valid?
|
132
180
|
@config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
|
133
181
|
return nil
|
@@ -167,10 +215,16 @@ module LaunchDarkly
|
|
167
215
|
# @param default the default value of the flag; this is used if there is an error
|
168
216
|
# condition making it impossible to find or evaluate the flag
|
169
217
|
#
|
170
|
-
# @return the variation for the provided context, or the default value if there's an
|
218
|
+
# @return the variation for the provided context, or the default value if there's an error
|
171
219
|
#
|
172
220
|
def variation(key, context, default)
|
173
|
-
|
221
|
+
context = Impl::Context::make_context(context)
|
222
|
+
result = evaluate_with_hooks(key, context, default, :variation) do
|
223
|
+
detail, _, _ = variation_with_flag(key, context, default)
|
224
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail)
|
225
|
+
end
|
226
|
+
|
227
|
+
result.evaluation_detail.value
|
174
228
|
end
|
175
229
|
|
176
230
|
#
|
@@ -197,7 +251,157 @@ module LaunchDarkly
|
|
197
251
|
# @return [EvaluationDetail] an object describing the result
|
198
252
|
#
|
199
253
|
def variation_detail(key, context, default)
|
200
|
-
|
254
|
+
context = Impl::Context::make_context(context)
|
255
|
+
result = evaluate_with_hooks(key, context, default, :variation_detail) do
|
256
|
+
detail, _, _ = evaluate_internal(key, context, default, true)
|
257
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail)
|
258
|
+
end
|
259
|
+
|
260
|
+
result.evaluation_detail
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# evaluate_with_hook will run the provided block, wrapping it with evaluation hook support.
|
265
|
+
#
|
266
|
+
# Example:
|
267
|
+
#
|
268
|
+
# ```ruby
|
269
|
+
# evaluate_with_hooks(key, context, default, method) do
|
270
|
+
# puts 'This is being wrapped with evaluation hooks'
|
271
|
+
# end
|
272
|
+
# ```
|
273
|
+
#
|
274
|
+
# @param key [String]
|
275
|
+
# @param context [LDContext]
|
276
|
+
# @param default [any]
|
277
|
+
# @param method [Symbol]
|
278
|
+
# @param &block [#call] Implicit passed block
|
279
|
+
#
|
280
|
+
# @return [LaunchDarkly::Impl::EvaluationWithHookResult]
|
281
|
+
#
|
282
|
+
private def evaluate_with_hooks(key, context, default, method)
|
283
|
+
return yield if @hooks.empty?
|
284
|
+
|
285
|
+
hooks, evaluation_series_context = prepare_hooks(key, context, default, method)
|
286
|
+
hook_data = execute_before_evaluation(hooks, evaluation_series_context)
|
287
|
+
evaluation_result = yield
|
288
|
+
execute_after_evaluation(hooks, evaluation_series_context, hook_data, evaluation_result.evaluation_detail)
|
289
|
+
|
290
|
+
evaluation_result
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Execute the :before_evaluation stage of the evaluation series.
|
295
|
+
#
|
296
|
+
# This method will return the results of each hook, indexed into an array in the same order as the hooks. If a hook
|
297
|
+
# raised an uncaught exception, the value will be nil.
|
298
|
+
#
|
299
|
+
# @param hooks [Array<Interfaces::Hooks::Hook>]
|
300
|
+
# @param evaluation_series_context [EvaluationSeriesContext]
|
301
|
+
#
|
302
|
+
# @return [Array<any>]
|
303
|
+
#
|
304
|
+
private def execute_before_evaluation(hooks, evaluation_series_context)
|
305
|
+
hooks.map do |hook|
|
306
|
+
try_execute_stage(:before_evaluation, hook.metadata.name) do
|
307
|
+
hook.before_evaluation(evaluation_series_context, {})
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Execute the :after_evaluation stage of the evaluation series.
|
314
|
+
#
|
315
|
+
# This method will return the results of each hook, indexed into an array in the same order as the hooks. If a hook
|
316
|
+
# raised an uncaught exception, the value will be nil.
|
317
|
+
#
|
318
|
+
# @param hooks [Array<Interfaces::Hooks::Hook>]
|
319
|
+
# @param evaluation_series_context [EvaluationSeriesContext]
|
320
|
+
# @param hook_data [Array<any>]
|
321
|
+
# @param evaluation_detail [EvaluationDetail]
|
322
|
+
#
|
323
|
+
# @return [Array<any>]
|
324
|
+
#
|
325
|
+
private def execute_after_evaluation(hooks, evaluation_series_context, hook_data, evaluation_detail)
|
326
|
+
hooks.zip(hook_data).reverse.map do |(hook, data)|
|
327
|
+
try_execute_stage(:after_evaluation, hook.metadata.name) do
|
328
|
+
hook.after_evaluation(evaluation_series_context, data, evaluation_detail)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
# Try to execute the provided block. If execution raises an exception, catch and log it, then move on with
|
335
|
+
# execution.
|
336
|
+
#
|
337
|
+
# @return [any]
|
338
|
+
#
|
339
|
+
private def try_execute_stage(method, hook_name)
|
340
|
+
begin
|
341
|
+
yield
|
342
|
+
rescue => e
|
343
|
+
@config.logger.error { "[LDClient] An error occurred in #{method} of the hook #{hook_name}: #{e}" }
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
#
|
349
|
+
# Return a copy of the existing hooks and a few instance of the EvaluationSeriesContext used for the evaluation series.
|
350
|
+
#
|
351
|
+
# @param key [String]
|
352
|
+
# @param context [LDContext]
|
353
|
+
# @param default [any]
|
354
|
+
# @param method [Symbol]
|
355
|
+
# @return [Array[Array<Interfaces::Hooks::Hook>, Interfaces::Hooks::EvaluationSeriesContext]]
|
356
|
+
#
|
357
|
+
private def prepare_hooks(key, context, default, method)
|
358
|
+
# Copy the hooks to use a consistent set during the evaluation series.
|
359
|
+
#
|
360
|
+
# Hooks can be added and we want to ensure all correct stages for a given hook execute. For example, we do not
|
361
|
+
# want to trigger the after_evaluation method without also triggering the before_evaluation method.
|
362
|
+
hooks = @hooks.dup
|
363
|
+
evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method)
|
364
|
+
|
365
|
+
[hooks, evaluation_series_context]
|
366
|
+
end
|
367
|
+
|
368
|
+
#
|
369
|
+
# This method returns the migration stage of the migration feature flag for the given evaluation context.
|
370
|
+
#
|
371
|
+
# This method returns the default stage if there is an error or the flag does not exist. If the default stage is not
|
372
|
+
# a valid stage, then a default stage of 'off' will be used instead.
|
373
|
+
#
|
374
|
+
# @param key [String]
|
375
|
+
# @param context [LDContext]
|
376
|
+
# @param default_stage [Symbol]
|
377
|
+
#
|
378
|
+
# @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
|
379
|
+
#
|
380
|
+
def migration_variation(key, context, default_stage)
|
381
|
+
unless Migrations::VALID_STAGES.include? default_stage
|
382
|
+
@config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
|
383
|
+
default_stage = Migrations::STAGE_OFF
|
384
|
+
end
|
385
|
+
|
386
|
+
context = Impl::Context::make_context(context)
|
387
|
+
result = evaluate_with_hooks(key, context, default_stage, :migration_variation) do
|
388
|
+
detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
|
389
|
+
|
390
|
+
stage = detail.value
|
391
|
+
stage = stage.to_sym if stage.respond_to? :to_sym
|
392
|
+
|
393
|
+
if Migrations::VALID_STAGES.include?(stage)
|
394
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
395
|
+
next LaunchDarkly::Impl::EvaluationWithHookResult.new(detail, {stage: stage, tracker: tracker})
|
396
|
+
end
|
397
|
+
|
398
|
+
detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
|
399
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
400
|
+
|
401
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail, {stage: default_stage, tracker: tracker})
|
402
|
+
end
|
403
|
+
|
404
|
+
[result.results[:stage], result.results[:tracker]]
|
201
405
|
end
|
202
406
|
|
203
407
|
#
|
@@ -260,6 +464,32 @@ module LaunchDarkly
|
|
260
464
|
@event_processor.record_custom_event(context, event_name, data, metric_value)
|
261
465
|
end
|
262
466
|
|
467
|
+
#
|
468
|
+
# Tracks the results of a migrations operation. This event includes measurements which can be used to enhance the
|
469
|
+
# observability of a migration within the LaunchDarkly UI.
|
470
|
+
#
|
471
|
+
# This event should be generated through {Interfaces::Migrations::OpTracker}. If you are using the
|
472
|
+
# {Interfaces::Migrations::Migrator} to handle migrations, this event will be created and emitted
|
473
|
+
# automatically.
|
474
|
+
#
|
475
|
+
# @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
|
476
|
+
#
|
477
|
+
def track_migration_op(tracker)
|
478
|
+
unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
|
479
|
+
@config.logger.error { "invalid op tracker received in track_migration_op" }
|
480
|
+
return
|
481
|
+
end
|
482
|
+
|
483
|
+
event = tracker.build
|
484
|
+
if event.is_a? String
|
485
|
+
@config.logger.error { "[LDClient] Error occurred generating migration op event; #{event}" }
|
486
|
+
return
|
487
|
+
end
|
488
|
+
|
489
|
+
|
490
|
+
@event_processor.record_migration_op_event(event)
|
491
|
+
end
|
492
|
+
|
263
493
|
#
|
264
494
|
# Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
|
265
495
|
# including the flag values and also metadata that can be used on the front end. This method does not
|
@@ -345,16 +575,53 @@ module LaunchDarkly
|
|
345
575
|
@event_processor.stop
|
346
576
|
@big_segment_store_manager.stop
|
347
577
|
@store.stop
|
578
|
+
@shared_executor.shutdown
|
348
579
|
end
|
349
580
|
|
350
581
|
#
|
351
582
|
# Returns an interface for tracking the status of a Big Segment store.
|
352
583
|
#
|
353
|
-
# The {BigSegmentStoreStatusProvider} has methods for checking whether the Big Segment store
|
584
|
+
# The {Interfaces::BigSegmentStoreStatusProvider} has methods for checking whether the Big Segment store
|
354
585
|
# is (as far as the SDK knows) currently operational and tracking changes in this status.
|
355
586
|
#
|
356
587
|
attr_reader :big_segment_store_status_provider
|
357
588
|
|
589
|
+
#
|
590
|
+
# Returns an interface for tracking the status of a persistent data store.
|
591
|
+
#
|
592
|
+
# The {LaunchDarkly::Interfaces::DataStore::StatusProvider} has methods for
|
593
|
+
# checking whether the data store is (as far as the SDK knows) currently
|
594
|
+
# operational, tracking changes in this status, and getting cache
|
595
|
+
# statistics. These are only relevant for a persistent data store; if you
|
596
|
+
# are using an in-memory data store, then this method will return a stub
|
597
|
+
# object that provides no information.
|
598
|
+
#
|
599
|
+
# @return [LaunchDarkly::Interfaces::DataStore::StatusProvider]
|
600
|
+
#
|
601
|
+
attr_reader :data_store_status_provider
|
602
|
+
|
603
|
+
#
|
604
|
+
# Returns an interface for tracking the status of the data source.
|
605
|
+
#
|
606
|
+
# The data source is the mechanism that the SDK uses to get feature flag
|
607
|
+
# configurations, such as a streaming connection (the default) or poll
|
608
|
+
# requests. The {LaunchDarkly::Interfaces::DataSource::StatusProvider} has
|
609
|
+
# methods for checking whether the data source is (as far as the SDK knows)
|
610
|
+
# currently operational and tracking changes in this status.
|
611
|
+
#
|
612
|
+
# @return [LaunchDarkly::Interfaces::DataSource::StatusProvider]
|
613
|
+
#
|
614
|
+
attr_reader :data_source_status_provider
|
615
|
+
|
616
|
+
#
|
617
|
+
# Returns an interface for tracking changes in feature flag configurations.
|
618
|
+
#
|
619
|
+
# The {LaunchDarkly::Interfaces::FlagTracker} contains methods for
|
620
|
+
# requesting notifications about feature flag changes using an event
|
621
|
+
# listener model.
|
622
|
+
#
|
623
|
+
attr_reader :flag_tracker
|
624
|
+
|
358
625
|
private
|
359
626
|
|
360
627
|
def create_default_data_source(sdk_key, config, diagnostic_accumulator)
|
@@ -372,24 +639,40 @@ module LaunchDarkly
|
|
372
639
|
end
|
373
640
|
end
|
374
641
|
|
375
|
-
#
|
376
|
-
# @
|
642
|
+
#
|
643
|
+
# @param key [String]
|
644
|
+
# @param context [LDContext]
|
645
|
+
# @param default [Object]
|
646
|
+
#
|
647
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
648
|
+
#
|
649
|
+
def variation_with_flag(key, context, default)
|
650
|
+
evaluate_internal(key, context, default, false)
|
651
|
+
end
|
652
|
+
|
653
|
+
#
|
654
|
+
# @param key [String]
|
655
|
+
# @param context [LDContext]
|
656
|
+
# @param default [Object]
|
657
|
+
# @param with_reasons [Boolean]
|
658
|
+
#
|
659
|
+
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
660
|
+
#
|
377
661
|
def evaluate_internal(key, context, default, with_reasons)
|
378
662
|
if @config.offline?
|
379
|
-
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
663
|
+
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default), nil, nil
|
380
664
|
end
|
381
665
|
|
382
666
|
if context.nil?
|
383
667
|
@config.logger.error { "[LDClient] Must specify context" }
|
384
668
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
385
|
-
return detail
|
669
|
+
return detail, nil, "no context provided"
|
386
670
|
end
|
387
671
|
|
388
|
-
context = Impl::Context::make_context(context)
|
389
672
|
unless context.valid?
|
390
|
-
@config.logger.error { "[LDClient] Context was invalid for flag
|
673
|
+
@config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
|
391
674
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
392
|
-
return detail
|
675
|
+
return detail, nil, context.error
|
393
676
|
end
|
394
677
|
|
395
678
|
unless initialized?
|
@@ -399,17 +682,21 @@ module LaunchDarkly
|
|
399
682
|
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
|
400
683
|
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
401
684
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
402
|
-
return detail
|
685
|
+
return detail, nil, "client not initialized"
|
403
686
|
end
|
404
687
|
end
|
405
688
|
|
406
|
-
|
689
|
+
begin
|
690
|
+
feature = @store.get(FEATURES, key)
|
691
|
+
rescue
|
692
|
+
# Ignored
|
693
|
+
end
|
407
694
|
|
408
695
|
if feature.nil?
|
409
696
|
@config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
|
410
697
|
detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
|
411
698
|
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
412
|
-
return detail
|
699
|
+
return detail, nil, "feature flag not found"
|
413
700
|
end
|
414
701
|
|
415
702
|
begin
|
@@ -424,12 +711,12 @@ module LaunchDarkly
|
|
424
711
|
detail = EvaluationDetail.new(default, nil, detail.reason)
|
425
712
|
end
|
426
713
|
record_flag_eval(feature, context, detail, default, with_reasons)
|
427
|
-
detail
|
714
|
+
[detail, feature, nil]
|
428
715
|
rescue => exn
|
429
716
|
Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
|
430
717
|
detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
|
431
718
|
record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
|
432
|
-
detail
|
719
|
+
[detail, feature, exn.to_s]
|
433
720
|
end
|
434
721
|
end
|
435
722
|
|
@@ -445,7 +732,9 @@ module LaunchDarkly
|
|
445
732
|
default,
|
446
733
|
add_experiment_data || flag[:trackEvents] || false,
|
447
734
|
flag[:debugEventsUntilDate],
|
448
|
-
nil
|
735
|
+
nil,
|
736
|
+
flag[:samplingRatio],
|
737
|
+
!!flag[:excludeFromSummaries]
|
449
738
|
)
|
450
739
|
end
|
451
740
|
|
@@ -461,13 +750,15 @@ module LaunchDarkly
|
|
461
750
|
nil,
|
462
751
|
add_experiment_data || prereq_flag[:trackEvents] || false,
|
463
752
|
prereq_flag[:debugEventsUntilDate],
|
464
|
-
prereq_of_flag[:key]
|
753
|
+
prereq_of_flag[:key],
|
754
|
+
prereq_flag[:samplingRatio],
|
755
|
+
!!prereq_flag[:excludeFromSummaries]
|
465
756
|
)
|
466
757
|
end
|
467
758
|
|
468
759
|
private def record_flag_eval_error(flag, context, default, reason, with_reasons)
|
469
760
|
@event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
|
470
|
-
flag[:trackEvents], flag[:debugEventsUntilDate], nil)
|
761
|
+
flag[:trackEvents], flag[:debugEventsUntilDate], nil, flag[:samplingRatio], !!flag[:excludeFromSummaries])
|
471
762
|
end
|
472
763
|
|
473
764
|
#
|
@@ -479,7 +770,7 @@ module LaunchDarkly
|
|
479
770
|
#
|
480
771
|
private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
|
481
772
|
@event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
|
482
|
-
false, nil, nil)
|
773
|
+
false, nil, nil, 1, false)
|
483
774
|
end
|
484
775
|
|
485
776
|
private def experiment?(flag, reason)
|