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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c04f38ba70b95c1446fc0aaffd2f3020e208501e9ede2313b559848792c1bf81
|
4
|
+
data.tar.gz: 6dd37514e2588d78c995702af1d66d6f00cdc3762ce4908690a2a4e13ab5c46d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
[![
|
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
|
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
|
-
|
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
|
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -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] ||
|
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] ||
|
81
|
-
@context_keys_flush_interval = opts[:context_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]
|
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]
|
data/lib/ldclient-rb/context.rb
CHANGED
@@ -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
|
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
|
data/lib/ldclient-rb/events.rb
CHANGED
@@ -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)
|
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[:
|
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.
|
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
|