launchdarkly-server-sdk 6.3.2 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70c2bfa2af852ab863bdd25cceedb4ac6eca57bfb4d6ccd9df70653b73a862e0
4
- data.tar.gz: 38d1bef24f2ce6cced2342264e0b70dde44ee79dcdfb4199ff90b9b4319303eb
3
+ metadata.gz: 147fe8142bffa0d8e754e86669f524f6d787930e8bc6b8a14d30429933a036e3
4
+ data.tar.gz: e4a404cb7bc911f7c156c3fe5059ee9a4a9d7ebfeacba4c52f44ceb116276835
5
5
  SHA512:
6
- metadata.gz: f44affa6d4f7beb1c21be04f91caa39acd708a44fc642768c5c42419e8a2295f59592531c1e07f5ed8e407bbb1a97817b42c6be1e54ef2a80e20b1e014b57836
7
- data.tar.gz: fe229a2d10add2afbc5fe539ca494fe8e73ae9b586571436e9bae9a3275721735ac755b35b40b6e432b69fec91803191bfe2c3bff1b876f30f3ef9de950af3eb
6
+ metadata.gz: '039348a1a0c9284e79e641b69f11bdc4e8ba57c4cad3320eea6f8bb8caf8fc0f52a02c427a2b4f26584945e5750b7728706855cfaf6bdc22a1863b55bcf934a6'
7
+ data.tar.gz: 452c574e6ff6323a74dfdcf6a5c936ff38bfaa0efc3b58ff190068322ea82e25b15dc20868eb22bf93361d3daa699bd51fa362e97f30f4fc6bcf402f9f5597d9
data/README.md CHANGED
@@ -3,7 +3,7 @@ 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/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/master)
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)
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
 
@@ -44,6 +44,7 @@ module LaunchDarkly
44
44
  # @option opts [String] :wrapper_version See {#wrapper_version}.
45
45
  # @option opts [#open] :socket_factory See {#socket_factory}.
46
46
  # @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
47
+ # @option opts [Hash] :application See {#application}
47
48
  #
48
49
  def initialize(opts = {})
49
50
  @base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -77,6 +78,7 @@ module LaunchDarkly
77
78
  @wrapper_version = opts[:wrapper_version]
78
79
  @socket_factory = opts[:socket_factory]
79
80
  @big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
81
+ @application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
80
82
  end
81
83
 
82
84
  #
@@ -284,6 +286,24 @@ module LaunchDarkly
284
286
  #
285
287
  attr_reader :big_segments
286
288
 
289
+ #
290
+ # An object that allows configuration of application metadata.
291
+ #
292
+ # Application metadata may be used in LaunchDarkly analytics or other product features, but does not affect feature flag evaluations.
293
+ #
294
+ # If you want to set non-default values for any of these fields, provide the appropriately configured hash to the {Config} object.
295
+ #
296
+ # @example Configuring application information
297
+ # opts[:application] = {
298
+ # id: "MY APPLICATION ID",
299
+ # version: "MY APPLICATION VERSION"
300
+ # }
301
+ # config = LDConfig.new(opts)
302
+ #
303
+ # @return [Hash]
304
+ #
305
+ attr_reader :application
306
+
287
307
  # @deprecated This is replaced by {#data_source}.
288
308
  attr_reader :update_processor
289
309
 
@@ -1,5 +1,7 @@
1
1
  require "ldclient-rb/impl/diagnostic_events"
2
2
  require "ldclient-rb/impl/event_sender"
3
+ require "ldclient-rb/impl/event_summarizer"
4
+ require "ldclient-rb/impl/event_types"
3
5
  require "ldclient-rb/impl/util"
4
6
 
5
7
  require "concurrent"
@@ -26,16 +28,33 @@ require "time"
26
28
  #
27
29
 
28
30
  module LaunchDarkly
29
- MAX_FLUSH_WORKERS = 5
30
- USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
31
- :avatar, :name ]
31
+ module EventProcessorMethods
32
+ def record_eval_event(
33
+ user,
34
+ key,
35
+ version = nil,
36
+ variation = nil,
37
+ value = nil,
38
+ reason = nil,
39
+ default = nil,
40
+ track_events = false,
41
+ debug_until = nil,
42
+ prereq_of = nil
43
+ )
44
+ end
32
45
 
33
- private_constant :MAX_FLUSH_WORKERS
34
- private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
46
+ def record_identify_event(user)
47
+ end
35
48
 
36
- # @private
37
- class NullEventProcessor
38
- def add_event(event)
49
+ def record_custom_event(
50
+ user,
51
+ key,
52
+ data = nil,
53
+ metric_value = nil
54
+ )
55
+ end
56
+
57
+ def record_alias_event(user, previous_user)
39
58
  end
40
59
 
41
60
  def flush
@@ -45,12 +64,16 @@ module LaunchDarkly
45
64
  end
46
65
  end
47
66
 
67
+ MAX_FLUSH_WORKERS = 5
68
+ USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
69
+ :avatar, :name ]
70
+
71
+ private_constant :MAX_FLUSH_WORKERS
72
+ private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
73
+
48
74
  # @private
49
- class EventMessage
50
- def initialize(event)
51
- @event = event
52
- end
53
- attr_reader :event
75
+ class NullEventProcessor
76
+ include EventProcessorMethods
54
77
  end
55
78
 
56
79
  # @private
@@ -90,6 +113,8 @@ module LaunchDarkly
90
113
 
91
114
  # @private
92
115
  class EventProcessor
116
+ include EventProcessorMethods
117
+
93
118
  def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test_properties = nil)
94
119
  raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
95
120
  @logger = config.logger
@@ -116,16 +141,46 @@ module LaunchDarkly
116
141
  @stopped = Concurrent::AtomicBoolean.new(false)
117
142
  @inbox_full = Concurrent::AtomicBoolean.new(false)
118
143
 
119
- event_sender = test_properties && test_properties.has_key?(:event_sender) ?
120
- test_properties[:event_sender] :
144
+ event_sender = (test_properties || {})[:event_sender] ||
121
145
  Impl::EventSender.new(sdk_key, config, client ? client : Util.new_http_client(config.events_uri, config))
122
146
 
147
+ @timestamp_fn = (test_properties || {})[:timestamp_fn] || proc { Impl::Util.current_time_millis }
148
+
123
149
  EventDispatcher.new(@inbox, sdk_key, config, diagnostic_accumulator, event_sender)
124
150
  end
125
151
 
126
- def add_event(event)
127
- event[:creationDate] = Impl::Util.current_time_millis
128
- post_to_inbox(EventMessage.new(event))
152
+ def record_eval_event(
153
+ user,
154
+ key,
155
+ version = nil,
156
+ variation = nil,
157
+ value = nil,
158
+ reason = nil,
159
+ default = nil,
160
+ track_events = false,
161
+ debug_until = nil,
162
+ prereq_of = nil
163
+ )
164
+ post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, user, key, version, variation, value, reason,
165
+ default, track_events, debug_until, prereq_of))
166
+ end
167
+
168
+ def record_identify_event(user)
169
+ post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, user))
170
+ end
171
+
172
+ def record_custom_event(user, key, data = nil, metric_value = nil)
173
+ post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, user, key, data, metric_value))
174
+ end
175
+
176
+ def record_alias_event(user, previous_user)
177
+ post_to_inbox(LaunchDarkly::Impl::AliasEvent.new(
178
+ timestamp,
179
+ user.nil? ? nil : user[:key],
180
+ user_to_context_kind(user),
181
+ previous_user.nil? ? nil : previous_user[:key],
182
+ user_to_context_kind(previous_user)
183
+ ))
129
184
  end
130
185
 
131
186
  def flush
@@ -155,9 +210,11 @@ module LaunchDarkly
155
210
  sync_msg.wait_for_completion
156
211
  end
157
212
 
158
- private
213
+ private def timestamp
214
+ @timestamp_fn.call()
215
+ end
159
216
 
160
- def post_to_inbox(message)
217
+ private def post_to_inbox(message)
161
218
  begin
162
219
  @inbox.push(message, non_block=true)
163
220
  rescue ThreadError
@@ -170,6 +227,10 @@ module LaunchDarkly
170
227
  end
171
228
  end
172
229
  end
230
+
231
+ private def user_to_context_kind(user)
232
+ (user.nil? || !user[:anonymous]) ? 'user' : 'anonymousUser'
233
+ end
173
234
  end
174
235
 
175
236
  # @private
@@ -209,8 +270,6 @@ module LaunchDarkly
209
270
  begin
210
271
  message = inbox.pop
211
272
  case message
212
- when EventMessage
213
- dispatch_event(message.event, outbox)
214
273
  when FlushMessage
215
274
  trigger_flush(outbox, flush_workers)
216
275
  when FlushUsersMessage
@@ -224,6 +283,8 @@ module LaunchDarkly
224
283
  do_shutdown(flush_workers, diagnostic_event_workers)
225
284
  running = false
226
285
  message.completed
286
+ else
287
+ dispatch_event(message, outbox)
227
288
  end
228
289
  rescue => e
229
290
  Util.log_exception(@config.logger, "Unexpected error in event processor", e)
@@ -257,11 +318,10 @@ module LaunchDarkly
257
318
  # the event (if tracked) and once for debugging.
258
319
  will_add_full_event = false
259
320
  debug_event = nil
260
- if event[:kind] == "feature"
261
- will_add_full_event = event[:trackEvents]
321
+ if event.is_a?(LaunchDarkly::Impl::EvalEvent)
322
+ will_add_full_event = event.track_events
262
323
  if should_debug_event(event)
263
- debug_event = event.clone
264
- debug_event[:debug] = true
324
+ debug_event = LaunchDarkly::Impl::DebugEvent.new(event)
265
325
  end
266
326
  else
267
327
  will_add_full_event = true
@@ -270,12 +330,8 @@ module LaunchDarkly
270
330
  # For each user we haven't seen before, we add an index event - unless this is already
271
331
  # an identify event for that user.
272
332
  if !(will_add_full_event && @config.inline_users_in_events)
273
- if event.has_key?(:user) && !notice_user(event[:user]) && event[:kind] != "identify"
274
- outbox.add_event({
275
- kind: "index",
276
- creationDate: event[:creationDate],
277
- user: event[:user]
278
- })
333
+ if !event.user.nil? && !notice_user(event.user) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
334
+ outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.user))
279
335
  end
280
336
  end
281
337
 
@@ -295,7 +351,7 @@ module LaunchDarkly
295
351
  end
296
352
 
297
353
  def should_debug_event(event)
298
- debug_until = event[:debugEventsUntilDate]
354
+ debug_until = event.debug_until
299
355
  if !debug_until.nil?
300
356
  last_past = @last_known_past_time.value
301
357
  debug_until > last_past && debug_until > Impl::Util.current_time_millis
@@ -365,12 +421,11 @@ module LaunchDarkly
365
421
  @capacity_exceeded = false
366
422
  @dropped_events = 0
367
423
  @events = []
368
- @summarizer = EventSummarizer.new
424
+ @summarizer = LaunchDarkly::Impl::EventSummarizer.new
369
425
  end
370
426
 
371
427
  def add_event(event)
372
428
  if @events.length < @capacity
373
- @logger.debug { "[LDClient] Enqueueing event: #{event.to_json}" }
374
429
  @events.push(event)
375
430
  @capacity_exceeded = false
376
431
  else
@@ -404,6 +459,15 @@ module LaunchDarkly
404
459
 
405
460
  # @private
406
461
  class EventOutputFormatter
462
+ FEATURE_KIND = 'feature'
463
+ IDENTIFY_KIND = 'identify'
464
+ CUSTOM_KIND = 'custom'
465
+ ALIAS_KIND = 'alias'
466
+ INDEX_KIND = 'index'
467
+ DEBUG_KIND = 'debug'
468
+ SUMMARY_KIND = 'summary'
469
+ ANONYMOUS_USER_CONTEXT_KIND = 'anonymousUser'
470
+
407
471
  def initialize(config)
408
472
  @inline_users = config.inline_users_in_events
409
473
  @user_filter = UserFilter.new(config)
@@ -418,100 +482,130 @@ module LaunchDarkly
418
482
  events_out
419
483
  end
420
484
 
421
- private
422
-
423
- def process_user(event)
424
- filtered = @user_filter.transform_user_props(event[:user])
425
- Util.stringify_attrs(filtered, USER_ATTRS_TO_STRINGIFY_FOR_EVENTS)
426
- end
427
-
428
- def make_output_event(event)
429
- case event[:kind]
430
- when "feature"
431
- is_debug = event[:debug]
485
+ private def make_output_event(event)
486
+ case event
487
+
488
+ when LaunchDarkly::Impl::EvalEvent
432
489
  out = {
433
- kind: is_debug ? "debug" : "feature",
434
- creationDate: event[:creationDate],
435
- key: event[:key],
436
- value: event[:value]
490
+ kind: FEATURE_KIND,
491
+ creationDate: event.timestamp,
492
+ key: event.key,
493
+ value: event.value
437
494
  }
438
- out[:default] = event[:default] if event.has_key?(:default)
439
- out[:variation] = event[:variation] if event.has_key?(:variation)
440
- out[:version] = event[:version] if event.has_key?(:version)
441
- out[:prereqOf] = event[:prereqOf] if event.has_key?(:prereqOf)
442
- out[:contextKind] = event[:contextKind] if event.has_key?(:contextKind)
443
- if @inline_users || is_debug
444
- out[:user] = process_user(event)
445
- else
446
- out[:userKey] = event[:user][:key]
447
- end
448
- out[:reason] = event[:reason] if !event[:reason].nil?
495
+ out[:default] = event.default if !event.default.nil?
496
+ out[:variation] = event.variation if !event.variation.nil?
497
+ out[:version] = event.version if !event.version.nil?
498
+ out[:prereqOf] = event.prereq_of if !event.prereq_of.nil?
499
+ set_opt_context_kind(out, event.user)
500
+ set_user_or_user_key(out, event.user)
501
+ out[:reason] = event.reason if !event.reason.nil?
449
502
  out
450
- when "identify"
503
+
504
+ when LaunchDarkly::Impl::IdentifyEvent
451
505
  {
452
- kind: "identify",
453
- creationDate: event[:creationDate],
454
- key: event[:user][:key].to_s,
455
- user: process_user(event)
506
+ kind: IDENTIFY_KIND,
507
+ creationDate: event.timestamp,
508
+ key: event.user[:key].to_s,
509
+ user: process_user(event.user)
456
510
  }
457
- when "custom"
511
+
512
+ when LaunchDarkly::Impl::CustomEvent
458
513
  out = {
459
- kind: "custom",
460
- creationDate: event[:creationDate],
461
- key: event[:key]
514
+ kind: CUSTOM_KIND,
515
+ creationDate: event.timestamp,
516
+ key: event.key
462
517
  }
463
- out[:data] = event[:data] if event.has_key?(:data)
464
- if @inline_users
465
- out[:user] = process_user(event)
466
- else
467
- out[:userKey] = event[:user][:key]
468
- end
469
- out[:metricValue] = event[:metricValue] if event.has_key?(:metricValue)
470
- out[:contextKind] = event[:contextKind] if event.has_key?(:contextKind)
518
+ out[:data] = event.data if !event.data.nil?
519
+ set_user_or_user_key(out, event.user)
520
+ out[:metricValue] = event.metric_value if !event.metric_value.nil?
521
+ set_opt_context_kind(out, event.user)
471
522
  out
472
- when "index"
523
+
524
+ when LaunchDarkly::Impl::AliasEvent
525
+ {
526
+ kind: ALIAS_KIND,
527
+ creationDate: event.timestamp,
528
+ key: event.key,
529
+ contextKind: event.context_kind,
530
+ previousKey: event.previous_key,
531
+ previousContextKind: event.previous_context_kind
532
+ }
533
+
534
+ when LaunchDarkly::Impl::IndexEvent
473
535
  {
474
- kind: "index",
475
- creationDate: event[:creationDate],
476
- user: process_user(event)
536
+ kind: INDEX_KIND,
537
+ creationDate: event.timestamp,
538
+ user: process_user(event.user)
477
539
  }
540
+
541
+ when LaunchDarkly::Impl::DebugEvent
542
+ original = event.eval_event
543
+ out = {
544
+ kind: DEBUG_KIND,
545
+ creationDate: original.timestamp,
546
+ key: original.key,
547
+ user: process_user(original.user),
548
+ value: original.value
549
+ }
550
+ out[:default] = original.default if !original.default.nil?
551
+ out[:variation] = original.variation if !original.variation.nil?
552
+ out[:version] = original.version if !original.version.nil?
553
+ out[:prereqOf] = original.prereq_of if !original.prereq_of.nil?
554
+ set_opt_context_kind(out, original.user)
555
+ out[:reason] = original.reason if !original.reason.nil?
556
+ out
557
+
478
558
  else
479
- event
559
+ nil
480
560
  end
481
561
  end
482
562
 
483
563
  # Transforms the summary data into the format used for event sending.
484
- def make_summary_event(summary)
564
+ private def make_summary_event(summary)
485
565
  flags = {}
486
- summary[:counters].each { |ckey, cval|
487
- flag = flags[ckey[:key]]
488
- if flag.nil?
489
- flag = {
490
- default: cval[:default],
491
- counters: []
492
- }
493
- flags[ckey[:key]] = flag
494
- end
495
- c = {
496
- value: cval[:value],
497
- count: cval[:count]
498
- }
499
- if !ckey[:variation].nil?
500
- c[:variation] = ckey[:variation]
501
- end
502
- if ckey[:version].nil?
503
- c[:unknown] = true
504
- else
505
- c[:version] = ckey[:version]
566
+ summary.counters.each do |flagKey, flagInfo|
567
+ counters = []
568
+ flagInfo.versions.each do |version, variations|
569
+ variations.each do |variation, counter|
570
+ c = {
571
+ value: counter.value,
572
+ count: counter.count
573
+ }
574
+ c[:variation] = variation if !variation.nil?
575
+ if version.nil?
576
+ c[:unknown] = true
577
+ else
578
+ c[:version] = version
579
+ end
580
+ counters.push(c)
581
+ end
506
582
  end
507
- flag[:counters].push(c)
508
- }
583
+ flags[flagKey] = { default: flagInfo.default, counters: counters }
584
+ end
509
585
  {
510
- kind: "summary",
586
+ kind: SUMMARY_KIND,
511
587
  startDate: summary[:start_date],
512
588
  endDate: summary[:end_date],
513
589
  features: flags
514
590
  }
515
591
  end
592
+
593
+ private def set_opt_context_kind(out, user)
594
+ out[:contextKind] = ANONYMOUS_USER_CONTEXT_KIND if !user.nil? && user[:anonymous]
595
+ end
596
+
597
+ private def set_user_or_user_key(out, user)
598
+ if @inline_users
599
+ out[:user] = process_user(user)
600
+ else
601
+ key = user[:key]
602
+ out[:userKey] = key.is_a?(String) ? key : key.to_s
603
+ end
604
+ end
605
+
606
+ private def process_user(user)
607
+ filtered = @user_filter.transform_user_props(user)
608
+ Util.stringify_attrs(filtered, USER_ATTRS_TO_STRINGIFY_FOR_EVENTS)
609
+ end
516
610
  end
517
611
  end