launchdarkly-server-sdk 5.6.2 → 5.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +4 -6
- data/launchdarkly-server-sdk.gemspec +1 -2
- data/lib/ldclient-rb/config.rb +64 -0
- data/lib/ldclient-rb/events.rb +94 -86
- data/lib/ldclient-rb/flags_state.rb +1 -1
- data/lib/ldclient-rb/impl/diagnostic_events.rb +130 -0
- data/lib/ldclient-rb/impl/event_sender.rb +72 -0
- data/lib/ldclient-rb/impl/util.rb +19 -0
- data/lib/ldclient-rb/ldclient.rb +17 -4
- data/lib/ldclient-rb/requestor.rb +1 -2
- data/lib/ldclient-rb/stream.rb +18 -5
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/diagnostic_events_spec.rb +163 -0
- data/spec/evaluation_spec.rb +1 -1
- data/spec/event_sender_spec.rb +179 -0
- data/spec/events_spec.rb +437 -524
- data/spec/file_data_source_spec.rb +1 -1
- data/spec/http_util.rb +15 -2
- data/spec/integrations/consul_feature_store_spec.rb +0 -2
- data/spec/integrations/dynamodb_feature_store_spec.rb +0 -2
- data/spec/ldclient_spec.rb +1 -1
- data/spec/polling_spec.rb +1 -1
- data/spec/redis_feature_store_spec.rb +0 -3
- data/spec/requestor_spec.rb +20 -4
- data/spec/spec_helper.rb +3 -0
- metadata +11 -19
- data/Rakefile +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78fa0fd5890ae19f95b19c915694bca33187ef23
|
4
|
+
data.tar.gz: 5dae22ebbc4a8c1d2e6deb3e206ff4d27937da02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 888c327731d50e3d4869b2c6fe72748a9c9bb7f2c80123194eb2d32f1b365a613dbf7bec78a38fcf4f35b16e2ec634725fde25681bad0225dbe9fee41ce674f8
|
7
|
+
data.tar.gz: b8fcaa392b4940838daa060d66eea7dc2413d458875de3bb36905e70b194b9e34f6707d1d014c6e297ac81d396123411060c18761fe1a05c6287419cd902ca27
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
## [5.6.2] - 2020-01-15
|
6
|
+
### Fixed:
|
7
|
+
- The SDK now specifies a uniquely identifiable request header when sending events to LaunchDarkly to ensure that events are only processed once, even if the SDK sends them two times due to a failed initial attempt.
|
8
|
+
|
5
9
|
## [5.6.1] - 2020-01-06
|
6
10
|
### Fixed:
|
7
11
|
- In rare circumstances (depending on the exact data in the flag configuration, the flag's salt value, and the user properties), a percentage rollout could fail and return a default value, logging the error "Data inconsistency in feature flag ... variation/rollout object with no variation or rollout". This would happen if the user's hashed value fell exactly at the end of the last "bucket" (the last variation defined in the rollout). This has been fixed so that the user will get the last variation.
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
launchdarkly-server-sdk (5.
|
4
|
+
launchdarkly-server-sdk (5.7.0)
|
5
5
|
concurrent-ruby (~> 1.0)
|
6
6
|
json (>= 1.8, < 3)
|
7
|
-
ld-eventsource (= 1.0.
|
7
|
+
ld-eventsource (= 1.0.2)
|
8
8
|
semantic (~> 1.6)
|
9
9
|
|
10
10
|
GEM
|
@@ -23,7 +23,7 @@ GEM
|
|
23
23
|
aws-sigv4 (1.0.3)
|
24
24
|
codeclimate-test-reporter (0.6.0)
|
25
25
|
simplecov (>= 0.7.1, < 1.0.0)
|
26
|
-
concurrent-ruby (1.1.
|
26
|
+
concurrent-ruby (1.1.6)
|
27
27
|
connection_pool (2.2.1)
|
28
28
|
diff-lcs (1.3)
|
29
29
|
diplomat (2.0.2)
|
@@ -40,7 +40,7 @@ GEM
|
|
40
40
|
jmespath (1.4.0)
|
41
41
|
json (1.8.6)
|
42
42
|
json (1.8.6-java)
|
43
|
-
ld-eventsource (1.0.
|
43
|
+
ld-eventsource (1.0.2)
|
44
44
|
concurrent-ruby (~> 1.0)
|
45
45
|
http_tools (~> 0.4.5)
|
46
46
|
socketry (~> 0.5.1)
|
@@ -49,7 +49,6 @@ GEM
|
|
49
49
|
rb-inotify (~> 0.9, >= 0.9.7)
|
50
50
|
ruby_dep (~> 1.2)
|
51
51
|
multipart-post (2.0.0)
|
52
|
-
rake (10.5.0)
|
53
52
|
rb-fsevent (0.10.3)
|
54
53
|
rb-inotify (0.9.10)
|
55
54
|
ffi (>= 0.5.0, < 2)
|
@@ -92,7 +91,6 @@ DEPENDENCIES
|
|
92
91
|
diplomat (>= 2.0.2)
|
93
92
|
launchdarkly-server-sdk!
|
94
93
|
listen (~> 3.0)
|
95
|
-
rake (~> 10.0)
|
96
94
|
redis (~> 3.3.5)
|
97
95
|
rspec (~> 3.2)
|
98
96
|
rspec_junit_formatter (~> 0.3.0)
|
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "diplomat", ">= 2.0.2"
|
29
29
|
spec.add_development_dependency "redis", "~> 3.3.5"
|
30
30
|
spec.add_development_dependency "connection_pool", ">= 2.1.2"
|
31
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
32
31
|
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
|
33
32
|
spec.add_development_dependency "timecop", "~> 0.9.1"
|
34
33
|
spec.add_development_dependency "listen", "~> 3.0" # see file_data_source.rb
|
@@ -36,5 +35,5 @@ Gem::Specification.new do |spec|
|
|
36
35
|
spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
|
37
36
|
spec.add_runtime_dependency "semantic", "~> 1.6"
|
38
37
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
39
|
-
spec.add_runtime_dependency "ld-eventsource", "1.0.
|
38
|
+
spec.add_runtime_dependency "ld-eventsource", "1.0.2"
|
40
39
|
end
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -37,6 +37,10 @@ module LaunchDarkly
|
|
37
37
|
# @option opts [Object] :data_source See {#data_source}.
|
38
38
|
# @option opts [Object] :update_processor Obsolete synonym for `data_source`.
|
39
39
|
# @option opts [Object] :update_processor_factory Obsolete synonym for `data_source`.
|
40
|
+
# @option opts [Boolean] :diagnostic_opt_out (false) See {#diagnostic_opt_out?}.
|
41
|
+
# @option opts [Float] :diagnostic_recording_interval (900) See {#diagnostic_recording_interval}.
|
42
|
+
# @option opts [String] :wrapper_name See {#wrapper_name}.
|
43
|
+
# @option opts [String] :wrapper_version See {#wrapper_version}.
|
40
44
|
#
|
41
45
|
def initialize(opts = {})
|
42
46
|
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
|
@@ -62,6 +66,11 @@ module LaunchDarkly
|
|
62
66
|
@data_source = opts[:data_source] || opts[:update_processor] || opts[:update_processor_factory]
|
63
67
|
@update_processor = opts[:update_processor]
|
64
68
|
@update_processor_factory = opts[:update_processor_factory]
|
69
|
+
@diagnostic_opt_out = opts.has_key?(:diagnostic_opt_out) && opts[:diagnostic_opt_out]
|
70
|
+
@diagnostic_recording_interval = opts.has_key?(:diagnostic_recording_interval) && opts[:diagnostic_recording_interval] > Config.minimum_diagnostic_recording_interval ?
|
71
|
+
opts[:diagnostic_recording_interval] : Config.default_diagnostic_recording_interval
|
72
|
+
@wrapper_name = opts[:wrapper_name]
|
73
|
+
@wrapper_version = opts[:wrapper_version]
|
65
74
|
end
|
66
75
|
|
67
76
|
#
|
@@ -257,6 +266,45 @@ module LaunchDarkly
|
|
257
266
|
# @deprecated This is replaced by {#data_source}.
|
258
267
|
attr_reader :update_processor_factory
|
259
268
|
|
269
|
+
#
|
270
|
+
# Set to true to opt out of sending diagnostics data.
|
271
|
+
#
|
272
|
+
# Unless `diagnostic_opt_out` is set to true, the client will send some diagnostics data to the LaunchDarkly servers
|
273
|
+
# in order to assist in the development of future SDK improvements. These diagnostics consist of an initial payload
|
274
|
+
# containing some details of the SDK in use, the SDK's configuration, and the platform the SDK is being run on, as
|
275
|
+
# well as periodic information on irregular occurrences such as dropped events.
|
276
|
+
# @return [Boolean]
|
277
|
+
#
|
278
|
+
def diagnostic_opt_out?
|
279
|
+
@diagnostic_opt_out
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# The interval at which periodic diagnostic data is sent, in seconds.
|
284
|
+
#
|
285
|
+
# The default is 900 (every 15 minutes) and the minimum value is 60 (every minute).
|
286
|
+
# @return [Float]
|
287
|
+
#
|
288
|
+
attr_reader :diagnostic_recording_interval
|
289
|
+
|
290
|
+
#
|
291
|
+
# For use by wrapper libraries to set an identifying name for the wrapper being used.
|
292
|
+
#
|
293
|
+
# This will be sent in User-Agent headers during requests to the LaunchDarkly servers to allow recording
|
294
|
+
# metrics on the usage of these wrapper libraries.
|
295
|
+
# @return [String]
|
296
|
+
#
|
297
|
+
attr_reader :wrapper_name
|
298
|
+
|
299
|
+
#
|
300
|
+
# For use by wrapper libraries to report the version of the library in use.
|
301
|
+
#
|
302
|
+
# If `wrapper_name` is not set, this field will be ignored. Otherwise the version string will be included in
|
303
|
+
# the User-Agent headers along with the `wrapper_name` during requests to the LaunchDarkly servers.
|
304
|
+
# @return [String]
|
305
|
+
#
|
306
|
+
attr_reader :wrapper_version
|
307
|
+
|
260
308
|
#
|
261
309
|
# The default LaunchDarkly client configuration. This configuration sets
|
262
310
|
# reasonable defaults for most users.
|
@@ -407,5 +455,21 @@ module LaunchDarkly
|
|
407
455
|
def self.default_user_keys_flush_interval
|
408
456
|
300
|
409
457
|
end
|
458
|
+
|
459
|
+
#
|
460
|
+
# The default value for {#diagnostic_recording_interval}.
|
461
|
+
# @return [Float] 900
|
462
|
+
#
|
463
|
+
def self.default_diagnostic_recording_interval
|
464
|
+
900
|
465
|
+
end
|
466
|
+
|
467
|
+
#
|
468
|
+
# The minimum value for {#diagnostic_recording_interval}.
|
469
|
+
# @return [Float] 60
|
470
|
+
#
|
471
|
+
def self.minimum_diagnostic_recording_interval
|
472
|
+
60
|
473
|
+
end
|
410
474
|
end
|
411
475
|
end
|
data/lib/ldclient-rb/events.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
require "ldclient-rb/impl/diagnostic_events"
|
2
|
+
require "ldclient-rb/impl/event_sender"
|
3
|
+
require "ldclient-rb/impl/util"
|
4
|
+
|
1
5
|
require "concurrent"
|
2
6
|
require "concurrent/atomics"
|
3
7
|
require "concurrent/executors"
|
4
|
-
require "securerandom"
|
5
8
|
require "thread"
|
6
9
|
require "time"
|
7
10
|
|
@@ -24,12 +27,10 @@ require "time"
|
|
24
27
|
|
25
28
|
module LaunchDarkly
|
26
29
|
MAX_FLUSH_WORKERS = 5
|
27
|
-
CURRENT_SCHEMA_VERSION = 3
|
28
30
|
USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
|
29
31
|
:avatar, :name ]
|
30
32
|
|
31
33
|
private_constant :MAX_FLUSH_WORKERS
|
32
|
-
private_constant :CURRENT_SCHEMA_VERSION
|
33
34
|
private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
|
34
35
|
|
35
36
|
# @private
|
@@ -60,6 +61,10 @@ module LaunchDarkly
|
|
60
61
|
class FlushUsersMessage
|
61
62
|
end
|
62
63
|
|
64
|
+
# @private
|
65
|
+
class DiagnosticEventMessage
|
66
|
+
end
|
67
|
+
|
63
68
|
# @private
|
64
69
|
class SynchronousMessage
|
65
70
|
def initialize
|
@@ -85,9 +90,9 @@ module LaunchDarkly
|
|
85
90
|
|
86
91
|
# @private
|
87
92
|
class EventProcessor
|
88
|
-
def initialize(sdk_key, config, client = nil)
|
93
|
+
def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test_properties = nil)
|
89
94
|
@logger = config.logger
|
90
|
-
@inbox = SizedQueue.new(config.capacity)
|
95
|
+
@inbox = SizedQueue.new(config.capacity < 100 ? 100 : config.capacity)
|
91
96
|
@flush_task = Concurrent::TimerTask.new(execution_interval: config.flush_interval) do
|
92
97
|
post_to_inbox(FlushMessage.new)
|
93
98
|
end
|
@@ -96,14 +101,29 @@ module LaunchDarkly
|
|
96
101
|
post_to_inbox(FlushUsersMessage.new)
|
97
102
|
end
|
98
103
|
@users_flush_task.execute
|
104
|
+
if !diagnostic_accumulator.nil?
|
105
|
+
interval = test_properties && test_properties.has_key?(:diagnostic_recording_interval) ?
|
106
|
+
test_properties[:diagnostic_recording_interval] :
|
107
|
+
config.diagnostic_recording_interval
|
108
|
+
@diagnostic_event_task = Concurrent::TimerTask.new(execution_interval: interval) do
|
109
|
+
post_to_inbox(DiagnosticEventMessage.new)
|
110
|
+
end
|
111
|
+
@diagnostic_event_task.execute
|
112
|
+
else
|
113
|
+
@diagnostic_event_task = nil
|
114
|
+
end
|
99
115
|
@stopped = Concurrent::AtomicBoolean.new(false)
|
100
116
|
@inbox_full = Concurrent::AtomicBoolean.new(false)
|
101
117
|
|
102
|
-
|
118
|
+
event_sender = test_properties && test_properties.has_key?(:event_sender) ?
|
119
|
+
test_properties[:event_sender] :
|
120
|
+
Impl::EventSender.new(sdk_key, config, client ? client : Util.new_http_client(config.events_uri, config))
|
121
|
+
|
122
|
+
EventDispatcher.new(@inbox, sdk_key, config, diagnostic_accumulator, event_sender)
|
103
123
|
end
|
104
124
|
|
105
125
|
def add_event(event)
|
106
|
-
event[:creationDate] =
|
126
|
+
event[:creationDate] = Impl::Util.current_time_millis
|
107
127
|
post_to_inbox(EventMessage.new(event))
|
108
128
|
end
|
109
129
|
|
@@ -117,6 +137,7 @@ module LaunchDarkly
|
|
117
137
|
if @stopped.make_true
|
118
138
|
@flush_task.shutdown
|
119
139
|
@users_flush_task.shutdown
|
140
|
+
@diagnostic_event_task.shutdown if !@diagnostic_event_task.nil?
|
120
141
|
# Note that here we are not calling post_to_inbox, because we *do* want to wait if the inbox
|
121
142
|
# is full; an orderly shutdown can't happen unless these messages are received.
|
122
143
|
@inbox << FlushMessage.new
|
@@ -152,34 +173,36 @@ module LaunchDarkly
|
|
152
173
|
|
153
174
|
# @private
|
154
175
|
class EventDispatcher
|
155
|
-
def initialize(inbox, sdk_key, config,
|
176
|
+
def initialize(inbox, sdk_key, config, diagnostic_accumulator, event_sender)
|
156
177
|
@sdk_key = sdk_key
|
157
178
|
@config = config
|
158
|
-
|
159
|
-
|
160
|
-
@client = client
|
161
|
-
else
|
162
|
-
@client = Util.new_http_client(@config.events_uri, @config)
|
163
|
-
end
|
179
|
+
@diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
|
180
|
+
@event_sender = event_sender
|
164
181
|
|
165
182
|
@user_keys = SimpleLRUCacheSet.new(config.user_keys_capacity)
|
166
183
|
@formatter = EventOutputFormatter.new(config)
|
167
184
|
@disabled = Concurrent::AtomicBoolean.new(false)
|
168
185
|
@last_known_past_time = Concurrent::AtomicReference.new(0)
|
169
|
-
|
186
|
+
@deduplicated_users = 0
|
187
|
+
@events_in_last_batch = 0
|
188
|
+
|
170
189
|
outbox = EventBuffer.new(config.capacity, config.logger)
|
171
190
|
flush_workers = NonBlockingThreadPool.new(MAX_FLUSH_WORKERS)
|
172
191
|
|
173
|
-
|
192
|
+
if !@diagnostic_accumulator.nil?
|
193
|
+
diagnostic_event_workers = NonBlockingThreadPool.new(1)
|
194
|
+
init_event = @diagnostic_accumulator.create_init_event(config)
|
195
|
+
send_diagnostic_event(init_event, diagnostic_event_workers)
|
196
|
+
else
|
197
|
+
diagnostic_event_workers = nil
|
198
|
+
end
|
199
|
+
|
200
|
+
Thread.new { main_loop(inbox, outbox, flush_workers, diagnostic_event_workers) }
|
174
201
|
end
|
175
202
|
|
176
203
|
private
|
177
204
|
|
178
|
-
def
|
179
|
-
(Time.now.to_f * 1000).to_i
|
180
|
-
end
|
181
|
-
|
182
|
-
def main_loop(inbox, outbox, flush_workers)
|
205
|
+
def main_loop(inbox, outbox, flush_workers, diagnostic_event_workers)
|
183
206
|
running = true
|
184
207
|
while running do
|
185
208
|
begin
|
@@ -191,11 +214,13 @@ module LaunchDarkly
|
|
191
214
|
trigger_flush(outbox, flush_workers)
|
192
215
|
when FlushUsersMessage
|
193
216
|
@user_keys.clear
|
217
|
+
when DiagnosticEventMessage
|
218
|
+
send_and_reset_diagnostics(outbox, diagnostic_event_workers)
|
194
219
|
when TestSyncMessage
|
195
|
-
synchronize_for_testing(flush_workers)
|
220
|
+
synchronize_for_testing(flush_workers, diagnostic_event_workers)
|
196
221
|
message.completed
|
197
222
|
when StopMessage
|
198
|
-
do_shutdown(flush_workers)
|
223
|
+
do_shutdown(flush_workers, diagnostic_event_workers)
|
199
224
|
running = false
|
200
225
|
message.completed
|
201
226
|
end
|
@@ -205,18 +230,23 @@ module LaunchDarkly
|
|
205
230
|
end
|
206
231
|
end
|
207
232
|
|
208
|
-
def do_shutdown(flush_workers)
|
233
|
+
def do_shutdown(flush_workers, diagnostic_event_workers)
|
209
234
|
flush_workers.shutdown
|
210
235
|
flush_workers.wait_for_termination
|
236
|
+
if !diagnostic_event_workers.nil?
|
237
|
+
diagnostic_event_workers.shutdown
|
238
|
+
diagnostic_event_workers.wait_for_termination
|
239
|
+
end
|
211
240
|
begin
|
212
241
|
@client.finish
|
213
242
|
rescue
|
214
243
|
end
|
215
244
|
end
|
216
245
|
|
217
|
-
def synchronize_for_testing(flush_workers)
|
246
|
+
def synchronize_for_testing(flush_workers, diagnostic_event_workers)
|
218
247
|
# Used only by unit tests. Wait until all active flush workers have finished.
|
219
248
|
flush_workers.wait_all
|
249
|
+
diagnostic_event_workers.wait_all if !diagnostic_event_workers.nil?
|
220
250
|
end
|
221
251
|
|
222
252
|
def dispatch_event(event, outbox)
|
@@ -260,7 +290,9 @@ module LaunchDarkly
|
|
260
290
|
if user.nil? || !user.has_key?(:key)
|
261
291
|
true
|
262
292
|
else
|
263
|
-
@user_keys.add(user[:key].to_s)
|
293
|
+
known = @user_keys.add(user[:key].to_s)
|
294
|
+
@deduplicated_users += 1 if known
|
295
|
+
known
|
264
296
|
end
|
265
297
|
end
|
266
298
|
|
@@ -268,7 +300,7 @@ module LaunchDarkly
|
|
268
300
|
debug_until = event[:debugEventsUntilDate]
|
269
301
|
if !debug_until.nil?
|
270
302
|
last_past = @last_known_past_time.value
|
271
|
-
debug_until > last_past && debug_until >
|
303
|
+
debug_until > last_past && debug_until > Impl::Util.current_time_millis
|
272
304
|
else
|
273
305
|
false
|
274
306
|
end
|
@@ -281,34 +313,44 @@ module LaunchDarkly
|
|
281
313
|
|
282
314
|
payload = outbox.get_payload
|
283
315
|
if !payload.events.empty? || !payload.summary.counters.empty?
|
316
|
+
count = payload.events.length + (payload.summary.counters.empty? ? 0 : 1)
|
317
|
+
@events_in_last_batch = count
|
284
318
|
# If all available worker threads are busy, success will be false and no job will be queued.
|
285
319
|
success = flush_workers.post do
|
286
320
|
begin
|
287
|
-
|
288
|
-
|
321
|
+
events_out = @formatter.make_output_events(payload.events, payload.summary)
|
322
|
+
result = @event_sender.send_event_data(events_out.to_json, false)
|
323
|
+
@disabled.value = true if result.must_shutdown
|
324
|
+
if !result.time_from_server.nil?
|
325
|
+
@last_known_past_time.value = (result.time_from_server.to_f * 1000).to_i
|
326
|
+
end
|
289
327
|
rescue => e
|
290
328
|
Util.log_exception(@config.logger, "Unexpected error in event processor", e)
|
291
329
|
end
|
292
330
|
end
|
293
331
|
outbox.clear if success # Reset our internal state, these events now belong to the flush worker
|
332
|
+
else
|
333
|
+
@events_in_last_batch = 0
|
294
334
|
end
|
295
335
|
end
|
296
336
|
|
297
|
-
def
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
337
|
+
def send_and_reset_diagnostics(outbox, diagnostic_event_workers)
|
338
|
+
return if @diagnostic_accumulator.nil?
|
339
|
+
dropped_count = outbox.get_and_clear_dropped_count
|
340
|
+
event = @diagnostic_accumulator.create_periodic_event_and_reset(dropped_count, @deduplicated_users, @events_in_last_batch)
|
341
|
+
@deduplicated_users = 0
|
342
|
+
@events_in_last_batch = 0
|
343
|
+
send_diagnostic_event(event, diagnostic_event_workers)
|
344
|
+
end
|
345
|
+
|
346
|
+
def send_diagnostic_event(event, diagnostic_event_workers)
|
347
|
+
return if diagnostic_event_workers.nil?
|
348
|
+
uri = URI(@config.events_uri + "/diagnostic")
|
349
|
+
diagnostic_event_workers.post do
|
350
|
+
begin
|
351
|
+
@event_sender.send_event_data(event.to_json, true)
|
352
|
+
rescue => e
|
353
|
+
Util.log_exception(@config.logger, "Unexpected error in event processor", e)
|
312
354
|
end
|
313
355
|
end
|
314
356
|
end
|
@@ -323,6 +365,7 @@ module LaunchDarkly
|
|
323
365
|
@capacity = capacity
|
324
366
|
@logger = logger
|
325
367
|
@capacity_exceeded = false
|
368
|
+
@dropped_events = 0
|
326
369
|
@events = []
|
327
370
|
@summarizer = EventSummarizer.new
|
328
371
|
end
|
@@ -333,6 +376,7 @@ module LaunchDarkly
|
|
333
376
|
@events.push(event)
|
334
377
|
@capacity_exceeded = false
|
335
378
|
else
|
379
|
+
@dropped_events += 1
|
336
380
|
if !@capacity_exceeded
|
337
381
|
@capacity_exceeded = true
|
338
382
|
@logger.warn { "[LDClient] Exceeded event queue capacity. Increase capacity to avoid dropping events." }
|
@@ -348,54 +392,18 @@ module LaunchDarkly
|
|
348
392
|
return FlushPayload.new(@events, @summarizer.snapshot)
|
349
393
|
end
|
350
394
|
|
395
|
+
def get_and_clear_dropped_count
|
396
|
+
ret = @dropped_events
|
397
|
+
@dropped_events = 0
|
398
|
+
ret
|
399
|
+
end
|
400
|
+
|
351
401
|
def clear
|
352
402
|
@events = []
|
353
403
|
@summarizer.clear
|
354
404
|
end
|
355
405
|
end
|
356
406
|
|
357
|
-
# @private
|
358
|
-
class EventPayloadSendTask
|
359
|
-
def run(sdk_key, config, client, payload, formatter)
|
360
|
-
events_out = formatter.make_output_events(payload.events, payload.summary)
|
361
|
-
res = nil
|
362
|
-
body = events_out.to_json
|
363
|
-
payload_id = SecureRandom.uuid
|
364
|
-
(0..1).each do |attempt|
|
365
|
-
if attempt > 0
|
366
|
-
config.logger.warn { "[LDClient] Will retry posting events after 1 second" }
|
367
|
-
sleep(1)
|
368
|
-
end
|
369
|
-
begin
|
370
|
-
client.start if !client.started?
|
371
|
-
config.logger.debug { "[LDClient] sending #{events_out.length} events: #{body}" }
|
372
|
-
uri = URI(config.events_uri + "/bulk")
|
373
|
-
req = Net::HTTP::Post.new(uri)
|
374
|
-
req.content_type = "application/json"
|
375
|
-
req.body = body
|
376
|
-
req["Authorization"] = sdk_key
|
377
|
-
req["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
378
|
-
req["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
|
379
|
-
req["X-LaunchDarkly-Payload-ID"] = payload_id
|
380
|
-
req["Connection"] = "keep-alive"
|
381
|
-
res = client.request(req)
|
382
|
-
rescue StandardError => exn
|
383
|
-
config.logger.warn { "[LDClient] Error flushing events: #{exn.inspect}." }
|
384
|
-
next
|
385
|
-
end
|
386
|
-
status = res.code.to_i
|
387
|
-
if status < 200 || status >= 300
|
388
|
-
if Util.http_error_recoverable?(status)
|
389
|
-
next
|
390
|
-
end
|
391
|
-
end
|
392
|
-
break
|
393
|
-
end
|
394
|
-
# used up our retries, return the last response if any
|
395
|
-
res
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
407
|
# @private
|
400
408
|
class EventOutputFormatter
|
401
409
|
def initialize(config)
|