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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -4
  3. data/lib/ldclient-rb/config.rb +50 -70
  4. data/lib/ldclient-rb/context.rb +65 -50
  5. data/lib/ldclient-rb/evaluation_detail.rb +5 -1
  6. data/lib/ldclient-rb/events.rb +81 -8
  7. data/lib/ldclient-rb/impl/big_segments.rb +1 -1
  8. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  9. data/lib/ldclient-rb/impl/context.rb +3 -3
  10. data/lib/ldclient-rb/impl/context_filter.rb +30 -9
  11. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  12. data/lib/ldclient-rb/impl/data_store.rb +59 -0
  13. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  14. data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
  15. data/lib/ldclient-rb/impl/event_sender.rb +1 -0
  16. data/lib/ldclient-rb/impl/event_types.rb +61 -3
  17. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  18. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
  19. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
  20. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
  21. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +19 -2
  22. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  23. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  24. data/lib/ldclient-rb/impl/model/feature_flag.rb +25 -3
  25. data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
  26. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  27. data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
  28. data/lib/ldclient-rb/in_memory_store.rb +7 -0
  29. data/lib/ldclient-rb/integrations/file_data.rb +1 -1
  30. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
  31. data/lib/ldclient-rb/integrations/test_data.rb +3 -3
  32. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
  33. data/lib/ldclient-rb/interfaces.rb +671 -0
  34. data/lib/ldclient-rb/ldclient.rb +313 -22
  35. data/lib/ldclient-rb/migrations.rb +230 -0
  36. data/lib/ldclient-rb/polling.rb +51 -5
  37. data/lib/ldclient-rb/reference.rb +11 -0
  38. data/lib/ldclient-rb/requestor.rb +5 -5
  39. data/lib/ldclient-rb/stream.rb +91 -29
  40. data/lib/ldclient-rb/util.rb +89 -0
  41. data/lib/ldclient-rb/version.rb +1 -1
  42. data/lib/ldclient-rb.rb +1 -0
  43. metadata +44 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e05b79a3532bc82bf8768c8179790c05f33a2a9fb8ac3efdb810d81174d3e3d
4
- data.tar.gz: 0bdcc30cee7cb2095c00fc0cf2f0d0154ef5766d83b1614f0f19a82c89609bdb
3
+ metadata.gz: c04f38ba70b95c1446fc0aaffd2f3020e208501e9ede2313b559848792c1bf81
4
+ data.tar.gz: 6dd37514e2588d78c995702af1d66d6f00cdc3762ce4908690a2a4e13ab5c46d
5
5
  SHA512:
6
- metadata.gz: b0134064ce4e118e7ca0744df9271845bb9591ed381a95401664da234154e83ce27459d87960afac786c7e3d9813c33fb582c645dd0d4b6e7fdeb94e3f831f4b
7
- data.tar.gz: 106e2a13a3ae0f08d7ba838851a5c3147d529643afba67753b0c2284901385965ef536185d2011743751beb6e7206df1f10e30b56a2368a2d31d5cb754b589ee
6
+ metadata.gz: 40f20870dfa777687da11f7611255ee7f15dde800c7ac9860e5a673643c9d7d19841932162fcabef1d57e6a9ab7710fb3d06ea72567336a5c26ecb0cdd220f74
7
+ data.tar.gz: bbf303fc081003f0a5e71f2fd38dc48f707958c5c33f6f450982db6c3fecb7076f287460d597414b52c8e4181d95c55babd599fca7877bebe4ff9d82d38e6dd5
data/README.md CHANGED
@@ -3,13 +3,13 @@ LaunchDarkly Server-side SDK for Ruby
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/launchdarkly-server-sdk.svg)](http://badge.fury.io/rb/launchdarkly-server-sdk)
5
5
 
6
- [![Circle CI](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/main.svg?style=svg)](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/main)
6
+ [![Run CI](https://github.com/launchdarkly/ruby-server-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/launchdarkly/ruby-server-sdk/actions/workflows/ci.yml)
7
7
  [![RubyDoc](https://img.shields.io/static/v1?label=docs+-+all+versions&message=reference&color=00add8)](https://www.rubydoc.info/gems/launchdarkly-server-sdk)
8
8
  [![GitHub Pages](https://img.shields.io/static/v1?label=docs+-+latest&message=reference&color=00add8)](https://launchdarkly.github.io/ruby-server-sdk)
9
9
 
10
10
  LaunchDarkly overview
11
11
  -------------------------
12
- [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
12
+ [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
13
13
 
14
14
  [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly)
15
15
 
@@ -26,7 +26,7 @@ Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/server-side/r
26
26
  Learn more
27
27
  -----------
28
28
 
29
- Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for this SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
29
+ Read our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for this SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
30
30
 
31
31
  Generated API documentation for all versions of the SDK is on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk). The API documentation for the latest version is also on [GitHub Pages](https://launchdarkly.github.io/ruby-server-sdk).
32
32
 
@@ -39,7 +39,12 @@ Contributing
39
39
  ------------
40
40
 
41
41
  We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK.
42
-
42
+
43
+ Verifying SDK build provenance with the SLSA framework
44
+ ------------
45
+
46
+ LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
47
+
43
48
  About LaunchDarkly
44
49
  -----------
45
50
 
@@ -13,18 +13,6 @@ module LaunchDarkly
13
13
  #
14
14
  # Constructor for creating custom LaunchDarkly configurations.
15
15
  #
16
- # `user_keys_capacity` and `user_keys_flush_interval` are deprecated
17
- # configuration options. They exist to maintain backwards compatibility
18
- # with previous configurations. Newer code should prefer their replacement
19
- # options -- `context_keys_capacity` and `context_keys_flush_interval`.
20
- #
21
- # In the event both the user and context variations are provided, the
22
- # context specific configuration option will take precedence.
23
- #
24
- # Similarly, `private_attribute_names` is deprecated. Newer code should
25
- # prefer `private_attributes`. If both are provided, `private_attributes`
26
- # will take precedence.
27
- #
28
16
  # @param opts [Hash] the configuration options
29
17
  # @option opts [Logger] :logger See {#logger}.
30
18
  # @option opts [String] :base_uri ("https://sdk.launchdarkly.com") See {#base_uri}.
@@ -42,12 +30,9 @@ module LaunchDarkly
42
30
  # @option opts [Float] :poll_interval (30) See {#poll_interval}.
43
31
  # @option opts [Boolean] :stream (true) See {#stream?}.
44
32
  # @option opts [Boolean] all_attributes_private (false) See {#all_attributes_private}.
45
- # @option opts [Array] :private_attribute_names See {#private_attribute_names}.
46
33
  # @option opts [Array] :private_attributes See {#private_attributes}.
47
34
  # @option opts [Boolean] :send_events (true) See {#send_events}.
48
- # @option opts [Integer] :user_keys_capacity (1000) See {#user_keys_capacity}.
49
35
  # @option opts [Integer] :context_keys_capacity (1000) See {#context_keys_capacity}.
50
- # @option opts [Float] :user_keys_flush_interval (300) See {#user_keys_flush_interval}.
51
36
  # @option opts [Float] :context_keys_flush_interval (300) See {#context_keys_flush_interval}.
52
37
  # @option opts [Object] :data_source See {#data_source}.
53
38
  # @option opts [Boolean] :diagnostic_opt_out (false) See {#diagnostic_opt_out?}.
@@ -57,6 +42,8 @@ module LaunchDarkly
57
42
  # @option opts [#open] :socket_factory See {#socket_factory}.
58
43
  # @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
59
44
  # @option opts [Hash] :application See {#application}
45
+ # @option opts [String] :payload_filter_key See {#payload_filter_key}
46
+ # @option hooks [Array<Interfaces::Hooks::Hook]
60
47
  #
61
48
  def initialize(opts = {})
62
49
  @base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -75,10 +62,10 @@ module LaunchDarkly
75
62
  @offline = opts.has_key?(:offline) ? opts[:offline] : Config.default_offline
76
63
  @poll_interval = opts.has_key?(:poll_interval) && opts[:poll_interval] > Config.default_poll_interval ? opts[:poll_interval] : Config.default_poll_interval
77
64
  @all_attributes_private = opts[:all_attributes_private] || false
78
- @private_attributes = opts[:private_attributes] || opts[:private_attribute_names] || []
65
+ @private_attributes = opts[:private_attributes] || []
79
66
  @send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
80
- @context_keys_capacity = opts[:context_keys_capacity] || opts[:user_keys_capacity] || Config.default_context_keys_capacity
81
- @context_keys_flush_interval = opts[:context_keys_flush_interval] || opts[:user_keys_flush_interval] || Config.default_user_keys_flush_interval
67
+ @context_keys_capacity = opts[:context_keys_capacity] || Config.default_context_keys_capacity
68
+ @context_keys_flush_interval = opts[:context_keys_flush_interval] || Config.default_context_keys_flush_interval
82
69
  @data_source = opts[:data_source]
83
70
  @diagnostic_opt_out = opts.has_key?(:diagnostic_opt_out) && opts[:diagnostic_opt_out]
84
71
  @diagnostic_recording_interval = opts.has_key?(:diagnostic_recording_interval) && opts[:diagnostic_recording_interval] > Config.minimum_diagnostic_recording_interval ?
@@ -88,8 +75,25 @@ module LaunchDarkly
88
75
  @socket_factory = opts[:socket_factory]
89
76
  @big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
90
77
  @application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
78
+ @payload_filter_key = opts[:payload_filter_key]
79
+ @hooks = (opts[:hooks] || []).keep_if { |hook| hook.is_a? Interfaces::Hooks::Hook }
80
+ @data_source_update_sink = nil
91
81
  end
92
82
 
83
+ #
84
+ # Returns the component that allows a data source to push data into the SDK.
85
+ #
86
+ # This property should only be set by the SDK. Long term access of this
87
+ # property is not supported; it is temporarily being exposed to maintain
88
+ # backwards compatibility while the SDK structure is updated.
89
+ #
90
+ # Custom data source implementations should integrate with this sink if
91
+ # they want to provide support for data source status listeners.
92
+ #
93
+ # @private
94
+ #
95
+ attr_accessor :data_source_update_sink
96
+
93
97
  #
94
98
  # The base URL for the LaunchDarkly server. This is configurable mainly for testing
95
99
  # purposes; most users should use the default value.
@@ -241,14 +245,6 @@ module LaunchDarkly
241
245
  #
242
246
  attr_reader :private_attributes
243
247
 
244
- #
245
- # @deprecated Backwards compatibility alias for #private_attributes.
246
- #
247
- # @return [Integer]
248
- # @see #private_attributes
249
- #
250
- alias :private_attribute_names :private_attributes
251
-
252
248
  #
253
249
  # Whether to send events back to LaunchDarkly. This differs from {#offline?} in that it affects
254
250
  # only the sending of client-side events, not streaming or polling for events from the server.
@@ -264,14 +260,6 @@ module LaunchDarkly
264
260
  #
265
261
  attr_reader :context_keys_capacity
266
262
 
267
- #
268
- # @deprecated Backwards compatibility alias for #context_keys_capacity.
269
- #
270
- # @return [Integer]
271
- # @see #context_keys_flush_interval
272
- #
273
- alias :user_keys_capacity :context_keys_capacity
274
-
275
263
  #
276
264
  # The interval in seconds at which the event processor will reset its set of known context keys.
277
265
  # @return [Float]
@@ -279,14 +267,6 @@ module LaunchDarkly
279
267
  #
280
268
  attr_reader :context_keys_flush_interval
281
269
 
282
- #
283
- # @deprecated Backwards compatibility alias for #context_keys_flush_interval.
284
- #
285
- # @return [Integer]
286
- # @see #context_keys_flush_interval
287
- #
288
- alias :user_keys_flush_interval :context_keys_flush_interval
289
-
290
270
  #
291
271
  # An object that is responsible for receiving feature flag data from LaunchDarkly. By default,
292
272
  # the client uses its standard polling or streaming implementation; this is customizable for
@@ -330,6 +310,21 @@ module LaunchDarkly
330
310
  #
331
311
  attr_reader :application
332
312
 
313
+ #
314
+ # LaunchDarkly Server SDKs historically downloaded all flag configuration and segments for a particular environment
315
+ # during initialization.
316
+ #
317
+ # For some customers, this is an unacceptably large amount of data, and has contributed to performance issues within
318
+ # their products.
319
+ #
320
+ # Filtered environments aim to solve this problem. By allowing customers to specify subsets of an environment's
321
+ # flags using a filter key, SDKs will initialize faster and use less memory.
322
+ #
323
+ # This payload filter key only applies to the default streaming and polling data sources. It will not affect TestData or FileData
324
+ # data sources, nor will it be applied to any data source provided through the {#data_source} config property.
325
+ #
326
+ attr_reader :payload_filter_key
327
+
333
328
  #
334
329
  # Set to true to opt out of sending diagnostics data.
335
330
  #
@@ -379,6 +374,17 @@ module LaunchDarkly
379
374
  #
380
375
  attr_reader :socket_factory
381
376
 
377
+ #
378
+ # Initial set of hooks for the client.
379
+ #
380
+ # Hooks provide entrypoints which allow for observation of SDK functions.
381
+ #
382
+ # LaunchDarkly provides integration packages, and most applications will not
383
+ # need to implement their own hooks. Refer to the `launchdarkly-server-sdk-otel` gem
384
+ # for instrumentation.
385
+ #
386
+ attr_reader :hooks
387
+
382
388
  #
383
389
  # The default LaunchDarkly client configuration. This configuration sets
384
390
  # reasonable defaults for most users.
@@ -454,7 +460,7 @@ module LaunchDarkly
454
460
 
455
461
  #
456
462
  # The default value for {#connect_timeout}.
457
- # @return [Float] 10
463
+ # @return [Float] 2
458
464
  #
459
465
  def self.default_connect_timeout
460
466
  2
@@ -465,7 +471,7 @@ module LaunchDarkly
465
471
  # @return [Logger] the Rails logger if in Rails, or a default Logger at WARN level otherwise
466
472
  #
467
473
  def self.default_logger
468
- if defined?(Rails) && Rails.respond_to?(:logger)
474
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
469
475
  Rails.logger
470
476
  else
471
477
  log = ::Logger.new($stdout)
@@ -538,18 +544,6 @@ module LaunchDarkly
538
544
  300
539
545
  end
540
546
 
541
- class << self
542
- #
543
- # @deprecated Backwards compatibility alias for #default_context_keys_capacity
544
- #
545
- alias :default_user_keys_capacity :default_context_keys_capacity
546
-
547
- #
548
- # @deprecated Backwards compatibility alias for #default_context_keys_flush_interval
549
- #
550
- alias :default_user_keys_flush_interval :default_context_keys_flush_interval
551
- end
552
-
553
547
  #
554
548
  # The default value for {#diagnostic_recording_interval}.
555
549
  # @return [Float] 900
@@ -615,25 +609,11 @@ module LaunchDarkly
615
609
  # @return [Integer]
616
610
  attr_reader :context_cache_size
617
611
 
618
- #
619
- # @deprecated Backwards compatibility alias for #context_cache_size
620
- #
621
- # @return [Integer]
622
- #
623
- alias :user_cache_size :context_cache_size
624
-
625
612
  # The maximum length of time (in seconds) that the Big Segment state for a context will be cached
626
613
  # by the SDK.
627
614
  # @return [Float]
628
615
  attr_reader :context_cache_time
629
616
 
630
- #
631
- # @deprecated Backwards compatibility alias for #context_cache_time
632
- #
633
- # @return [Float]
634
- #
635
- alias :user_cache_time :context_cache_time
636
-
637
617
  # The interval (in seconds) at which the SDK will poll the Big Segment store to make sure it is
638
618
  # available and to determine how long ago it was updated.
639
619
  # @return [Float]
@@ -47,9 +47,6 @@ module LaunchDarkly
47
47
  # @return [String, nil] Returns the error associated with this LDContext if invalid
48
48
  attr_reader :error
49
49
 
50
- # @return [Array<Reference>] Returns the private attributes associated with this LDContext
51
- attr_reader :private_attributes
52
-
53
50
  #
54
51
  # @private
55
52
  # @param key [String, nil]
@@ -69,10 +66,10 @@ module LaunchDarkly
69
66
  @name = name
70
67
  @anonymous = anonymous || false
71
68
  @attributes = attributes
72
- @private_attributes = []
69
+ @private_attributes = Set.new
73
70
  (private_attributes || []).each do |attribute|
74
71
  reference = Reference.create(attribute)
75
- @private_attributes << reference if reference.error.nil?
72
+ @private_attributes.add(reference) if reference.error.nil?
76
73
  end
77
74
  @error = error
78
75
  @contexts = contexts
@@ -80,6 +77,16 @@ module LaunchDarkly
80
77
  end
81
78
  private_class_method :new
82
79
 
80
+ protected attr_reader :name, :anonymous, :attributes
81
+
82
+ #
83
+ # @return [Array<Reference>] Returns the private attributes associated with this LDContext
84
+ #
85
+ def private_attributes
86
+ # TODO(sc-227265): Return a set instead of an array.
87
+ @private_attributes.to_a
88
+ end
89
+
83
90
  #
84
91
  # @return [Boolean] Is this LDContext a multi-kind context?
85
92
  #
@@ -274,6 +281,59 @@ module LaunchDarkly
274
281
  nil
275
282
  end
276
283
 
284
+ #
285
+ # An LDContext can be compared to other LDContexts or to a hash object. If
286
+ # a hash is provided, it is first converted to an LDContext using the
287
+ # `LDContext.create` method.
288
+ #
289
+ # @param other [LDContext, Hash]
290
+ # @return [Boolean]
291
+ #
292
+ def ==(other)
293
+ other = LDContext.create(other) if other.is_a? Hash
294
+ return false unless other.is_a? LDContext
295
+
296
+ return false unless self.kind == other.kind
297
+ return false unless self.valid? == other.valid?
298
+ return false unless self.error == other.error
299
+
300
+ return false unless self.individual_context_count == other.individual_context_count
301
+
302
+ if self.multi_kind?
303
+ self.kinds.each do |kind|
304
+ return false unless self.individual_context(kind) == other.individual_context(kind)
305
+ end
306
+
307
+ return true
308
+ end
309
+
310
+ return false unless self.key == other.key
311
+ return false unless self.name == other.name
312
+ return false unless self.anonymous == other.anonymous
313
+ return false unless self.attributes == other.attributes
314
+
315
+ # TODO(sc-227265): Calling .to_set is unnecessary once private_attributes are sets.
316
+ return false unless self.private_attributes.to_set == other.private_attributes.to_set
317
+
318
+ true
319
+ end
320
+ alias eql? ==
321
+
322
+ #
323
+ # For a single-kind context, the provided key will return the attribute value specified. This is the same as calling
324
+ # `LDCotnext.get_value`.
325
+ #
326
+ # For multi-kind contexts, the key will be interpreted as a context kind. If the multi-kind context has an
327
+ # individual context of that kind, it will be returned. Otherwise, this method will return nil. This behaves the
328
+ # same as calling `LDContext.individual_context`.
329
+ #
330
+ # @param key [Symbol, String]
331
+ #
332
+ def [](key)
333
+ return nil unless key.is_a? Symbol or key.is_a? String
334
+ multi_kind? ? individual_context(key.to_s) : get_value(key)
335
+ end
336
+
277
337
  #
278
338
  # Retrieve the value of any top level, addressable attribute.
279
339
  #
@@ -322,7 +382,6 @@ module LaunchDarkly
322
382
  #
323
383
  def self.create(data)
324
384
  return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash)
325
- return create_legacy_context(data) unless data.has_key?(:kind)
326
385
 
327
386
  kind = data[:kind]
328
387
  if kind == KIND_MULTI
@@ -392,50 +451,6 @@ module LaunchDarkly
392
451
  new(nil, nil, nil, nil, false, nil, nil, error)
393
452
  end
394
453
 
395
- #
396
- # @param data [Hash]
397
- # @return [LDContext]
398
- #
399
- private_class_method def self.create_legacy_context(data)
400
- key = data[:key]
401
-
402
- # Legacy users are allowed to have "" as a key but they cannot have nil as a key.
403
- return create_invalid_context(ERR_KEY_EMPTY) if key.nil?
404
-
405
- name = data[:name]
406
- name_error = LaunchDarkly::Impl::Context.validate_name(name)
407
- return create_invalid_context(name_error) unless name_error.nil?
408
-
409
- anonymous = data[:anonymous]
410
- anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, true)
411
- return create_invalid_context(anonymous_error) unless anonymous_error.nil?
412
-
413
- custom = data[:custom]
414
- unless custom.nil? || custom.is_a?(Hash)
415
- return create_invalid_context(ERR_CUSTOM_NON_HASH)
416
- end
417
-
418
- # We only need to create an attribute hash if one of these keys exist.
419
- # Everything else is stored in dedicated instance variables.
420
- attributes = custom.clone
421
- data.each do |k, v|
422
- case k
423
- when :ip, :email, :avatar, :firstName, :lastName, :country
424
- attributes ||= {}
425
- attributes[k] = v.clone
426
- else
427
- next
428
- end
429
- end
430
-
431
- private_attributes = data[:privateAttributeNames]
432
- if private_attributes && !private_attributes.is_a?(Array)
433
- return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
434
- end
435
-
436
- new(key.to_s, key.to_s, KIND_DEFAULT, name, anonymous, attributes, private_attributes)
437
- end
438
-
439
454
  #
440
455
  # @param data [Hash]
441
456
  # @param kind [String]
@@ -1,4 +1,3 @@
1
-
2
1
  module LaunchDarkly
3
2
  # An object returned by {LDClient#variation_detail}, combining the result of a flag evaluation with
4
3
  # an explanation of how it was calculated.
@@ -13,6 +12,7 @@ module LaunchDarkly
13
12
  def initialize(value, variation_index, reason)
14
13
  raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
15
14
  raise ArgumentError.new("reason must be an EvaluationReason") unless reason.is_a? EvaluationReason
15
+
16
16
  @value = value
17
17
  @variation_index = variation_index
18
18
  @reason = reason
@@ -100,6 +100,10 @@ module LaunchDarkly
100
100
  # a rule specified a nonexistent variation. An error message will always be logged in this case.
101
101
  ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
102
102
 
103
+ # Value for {#error_kind} indicating that there was an inconsistency between the expected type of the flag, and the
104
+ # actual type of the variation evaluated.
105
+ ERROR_WRONG_TYPE = :WRONG_TYPE
106
+
103
107
  # Value for {#error_kind} indicating that the caller passed `nil` for the context parameter, or the
104
108
  # context was invalid.
105
109
  ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED
@@ -40,7 +40,9 @@ module LaunchDarkly
40
40
  default = nil,
41
41
  track_events = false,
42
42
  debug_until = nil,
43
- prereq_of = nil
43
+ prereq_of = nil,
44
+ sampling_ratio = nil,
45
+ exclude_from_summaries = false
44
46
  )
45
47
  end
46
48
 
@@ -55,6 +57,9 @@ module LaunchDarkly
55
57
  )
56
58
  end
57
59
 
60
+ def record_migration_op_event(event)
61
+ end
62
+
58
63
  def flush
59
64
  end
60
65
 
@@ -153,10 +158,12 @@ module LaunchDarkly
153
158
  default = nil,
154
159
  track_events = false,
155
160
  debug_until = nil,
156
- prereq_of = nil
161
+ prereq_of = nil,
162
+ sampling_ratio = nil,
163
+ exclude_from_summaries = false
157
164
  )
158
165
  post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, context, key, version, variation, value, reason,
159
- default, track_events, debug_until, prereq_of))
166
+ default, track_events, debug_until, prereq_of, sampling_ratio, exclude_from_summaries))
160
167
  end
161
168
 
162
169
  def record_identify_event(context)
@@ -167,6 +174,10 @@ module LaunchDarkly
167
174
  post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, context, key, data, metric_value))
168
175
  end
169
176
 
177
+ def record_migration_op_event(event)
178
+ post_to_inbox(event)
179
+ end
180
+
170
181
  def flush
171
182
  # flush is done asynchronously
172
183
  post_to_inbox(FlushMessage.new)
@@ -220,6 +231,7 @@ module LaunchDarkly
220
231
  @config = config
221
232
  @diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
222
233
  @event_sender = event_sender
234
+ @sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
223
235
 
224
236
  @context_keys = SimpleLRUCacheSet.new(config.context_keys_capacity)
225
237
  @formatter = EventOutputFormatter.new(config)
@@ -292,7 +304,7 @@ module LaunchDarkly
292
304
  return if @disabled.value
293
305
 
294
306
  # Always record the event in the summary.
295
- outbox.add_to_summary(event)
307
+ outbox.add_to_summary(event) unless event.exclude_from_summaries
296
308
 
297
309
  # Decide whether to add the event to the payload. Feature events may be added twice, once for
298
310
  # the event (if tracked) and once for debugging.
@@ -309,12 +321,12 @@ module LaunchDarkly
309
321
 
310
322
  # For each context we haven't seen before, we add an index event - unless this is already
311
323
  # an identify event for that context.
312
- if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
324
+ if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent) && !event.is_a?(LaunchDarkly::Impl::MigrationOpEvent)
313
325
  outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.context))
314
326
  end
315
327
 
316
- outbox.add_event(event) if will_add_full_event
317
- outbox.add_event(debug_event) unless debug_event.nil?
328
+ outbox.add_event(event) if will_add_full_event && @sampler.sample(event.sampling_ratio.nil? ? 1 : event.sampling_ratio)
329
+ outbox.add_event(debug_event) if !debug_event.nil? && @sampler.sample(event.sampling_ratio.nil? ? 1 : event.sampling_ratio)
318
330
  end
319
331
 
320
332
  #
@@ -443,6 +455,7 @@ module LaunchDarkly
443
455
  CUSTOM_KIND = 'custom'
444
456
  INDEX_KIND = 'index'
445
457
  DEBUG_KIND = 'debug'
458
+ MIGRATION_OP_KIND = 'migration_op'
446
459
  SUMMARY_KIND = 'summary'
447
460
 
448
461
  def initialize(config)
@@ -468,12 +481,72 @@ module LaunchDarkly
468
481
  key: event.key,
469
482
  value: event.value,
470
483
  }
484
+
471
485
  out[:default] = event.default unless event.default.nil?
472
486
  out[:variation] = event.variation unless event.variation.nil?
473
487
  out[:version] = event.version unless event.version.nil?
474
488
  out[:prereqOf] = event.prereq_of unless event.prereq_of.nil?
475
- out[:contextKeys] = event.context.keys
489
+ out[:context] = @context_filter.filter_redact_anonymous(event.context)
476
490
  out[:reason] = event.reason unless event.reason.nil?
491
+
492
+ out
493
+
494
+ when LaunchDarkly::Impl::MigrationOpEvent
495
+ out = {
496
+ kind: MIGRATION_OP_KIND,
497
+ creationDate: event.timestamp,
498
+ contextKeys: event.context.keys,
499
+ operation: event.operation.to_s,
500
+ evaluation: {
501
+ key: event.key,
502
+ value: event.evaluation.value,
503
+ },
504
+ }
505
+
506
+ out[:evaluation][:version] = event.version unless event.version.nil?
507
+ out[:evaluation][:default] = event.default unless event.default.nil?
508
+ out[:evaluation][:variation] = event.evaluation.variation_index unless event.evaluation.variation_index.nil?
509
+ out[:evaluation][:reason] = event.evaluation.reason unless event.evaluation.reason.nil?
510
+ out[:samplingRatio] = event.sampling_ratio unless event.sampling_ratio.nil? || event.sampling_ratio == 1
511
+
512
+ measurements = []
513
+
514
+ unless event.invoked.empty?
515
+ measurements << {
516
+ "key": "invoked",
517
+ "values": event.invoked.map { |origin| [origin, true] }.to_h,
518
+ }
519
+ end
520
+
521
+ unless event.consistency_check.nil?
522
+ measurement = {
523
+ "key": "consistent",
524
+ "value": event.consistency_check,
525
+ }
526
+
527
+ unless event.consistency_check_ratio.nil? || event.consistency_check_ratio == 1
528
+ measurement[:samplingRatio] = event.consistency_check_ratio
529
+ end
530
+
531
+ measurements << measurement
532
+ end
533
+
534
+
535
+ unless event.latencies.empty?
536
+ measurements << {
537
+ "key": "latency_ms",
538
+ "values": event.latencies,
539
+ }
540
+ end
541
+
542
+ unless event.errors.empty?
543
+ measurements << {
544
+ "key": "error",
545
+ "values": event.errors.map { |origin| [origin, true] }.to_h,
546
+ }
547
+ end
548
+ out[:measurements] = measurements unless measurements.empty?
549
+
477
550
  out
478
551
 
479
552
  when LaunchDarkly::Impl::IdentifyEvent
@@ -23,7 +23,7 @@ module LaunchDarkly
23
23
  @last_status = nil
24
24
 
25
25
  unless @store.nil?
26
- @cache = ExpiringCache.new(big_segments_config.user_cache_size, big_segments_config.user_cache_time)
26
+ @cache = ExpiringCache.new(big_segments_config.context_cache_size, big_segments_config.context_cache_time)
27
27
  @poll_worker = RepeatingTask.new(big_segments_config.status_poll_interval, 0, -> { poll_store_and_update_status }, logger)
28
28
  @poll_worker.start
29
29
  end