launchdarkly-server-sdk 7.0.2 → 8.4.2
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/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)
|