launchdarkly-server-sdk 6.3.3 → 6.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e87643dab1105f65581f685f5fcbd51031d989fd8f2d782c9ba3cd8db81935c
4
- data.tar.gz: a4f896ba4a813edb23d265448ee809f6081b32c64c8487d3501137d0cde527c8
3
+ metadata.gz: 50934709e1ee8c075c56c149f75753f87498eb9e1f06e39597d2cd4514ea7196
4
+ data.tar.gz: e7f153366b7b50c3951f844bf9ce1a0b81093fc733818dbb02e8f0a888d9ac26
5
5
  SHA512:
6
- metadata.gz: 0b8b1d10daeb8a22fcaafc398a19c484776a4809d7ed8412880539c7176d3bd36c1137eb59180741ed948b94f58fac9fbafdd03ccaa1b3fe1d4725d48f581b0e
7
- data.tar.gz: a4caf7a74fd68f40f6f3ad05ba1999b2062eb2d11d7c077ef4cff78434cc23319493686e27f4cb987ab2fa5013dc69e6e71c55cacc419c13dbd7fcd304df51cb
6
+ metadata.gz: 2469e536ca482489eb46f3ff650aeb573eb691ab6c6c2b8ed669b7f8ba799bf65d68d40f6cabc51ec4b051dd56e309430f94e5dc4fbbd70193efa5156d1d1082
7
+ data.tar.gz: 454219746c7b296fd8c92ee5e266f96765f72527706b7d6f7360445eb1d245e2acb37e4cb722ed48bf73dce8e307d2121771270ea9bedb71e91d67f9a79b8ea0
@@ -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
@@ -4,6 +4,13 @@ require "ldclient-rb/impl/evaluator_operators"
4
4
 
5
5
  module LaunchDarkly
6
6
  module Impl
7
+ # Used internally to record that we evaluated a prerequisite flag.
8
+ PrerequisiteEvalRecord = Struct.new(
9
+ :prereq_flag, # the prerequisite flag that we evaluated
10
+ :prereq_of_flag, # the flag that it was a prerequisite of
11
+ :detail # the EvaluationDetail representing the evaluation result
12
+ )
13
+
7
14
  # Encapsulates the feature flag evaluation logic. The Evaluator has no knowledge of the rest of the SDK environment;
8
15
  # if it needs to retrieve flags or segments that are referenced by a flag, it does so through a simple function that
9
16
  # is provided in the constructor. It also produces feature requests as appropriate for any referenced prerequisite
@@ -22,7 +29,7 @@ module LaunchDarkly
22
29
  @get_big_segments_membership = get_big_segments_membership
23
30
  @logger = logger
24
31
  end
25
-
32
+
26
33
  # Used internally to hold an evaluation result and additional state that may be accumulated during an
27
34
  # evaluation. It's simpler and a bit more efficient to represent these as mutable properties rather than
28
35
  # trying to use a pure functional approach, and since we're not exposing this object to any application code
@@ -34,7 +41,7 @@ module LaunchDarkly
34
41
  # evaluation.
35
42
  EvalResult = Struct.new(
36
43
  :detail, # the EvaluationDetail representing the evaluation result
37
- :events, # an array of evaluation events generated by prerequisites, or nil
44
+ :prereq_evals, # an array of PrerequisiteEvalRecord instances, or nil
38
45
  :big_segments_status,
39
46
  :big_segments_membership
40
47
  )
@@ -50,17 +57,15 @@ module LaunchDarkly
50
57
  #
51
58
  # @param flag [Object] the flag
52
59
  # @param user [Object] the user properties
53
- # @param event_factory [EventFactory] called to construct a feature request event when a prerequisite flag is
54
- # evaluated; the caller is responsible for constructing the feature event for the top-level evaluation
55
60
  # @return [EvalResult] the evaluation result
56
- def evaluate(flag, user, event_factory)
61
+ def evaluate(flag, user)
57
62
  result = EvalResult.new
58
63
  if user.nil? || user[:key].nil?
59
64
  result.detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED)
60
65
  return result
61
66
  end
62
67
 
63
- detail = eval_internal(flag, user, result, event_factory)
68
+ detail = eval_internal(flag, user, result)
64
69
  if !result.big_segments_status.nil?
65
70
  # If big_segments_status is non-nil at the end of the evaluation, it means a query was done at
66
71
  # some point and we will want to include the status in the evaluation reason.
@@ -80,12 +85,12 @@ module LaunchDarkly
80
85
 
81
86
  private
82
87
 
83
- def eval_internal(flag, user, state, event_factory)
88
+ def eval_internal(flag, user, state)
84
89
  if !flag[:on]
85
90
  return get_off_value(flag, EvaluationReason::off)
86
91
  end
87
92
 
88
- prereq_failure_reason = check_prerequisites(flag, user, state, event_factory)
93
+ prereq_failure_reason = check_prerequisites(flag, user, state)
89
94
  if !prereq_failure_reason.nil?
90
95
  return get_off_value(flag, prereq_failure_reason)
91
96
  end
@@ -118,7 +123,7 @@ module LaunchDarkly
118
123
  return EvaluationDetail.new(nil, nil, EvaluationReason::fallthrough)
119
124
  end
120
125
 
121
- def check_prerequisites(flag, user, state, event_factory)
126
+ def check_prerequisites(flag, user, state)
122
127
  (flag[:prerequisites] || []).each do |prerequisite|
123
128
  prereq_ok = true
124
129
  prereq_key = prerequisite[:key]
@@ -129,15 +134,15 @@ module LaunchDarkly
129
134
  prereq_ok = false
130
135
  else
131
136
  begin
132
- prereq_res = eval_internal(prereq_flag, user, state, event_factory)
137
+ prereq_res = eval_internal(prereq_flag, user, state)
133
138
  # Note that if the prerequisite flag is off, we don't consider it a match no matter what its
134
139
  # off variation was. But we still need to evaluate it in order to generate an event.
135
140
  if !prereq_flag[:on] || prereq_res.variation_index != prerequisite[:variation]
136
141
  prereq_ok = false
137
142
  end
138
- event = event_factory.new_eval_event(prereq_flag, user, prereq_res, nil, flag)
139
- state.events = [] if state.events.nil?
140
- state.events.push(event)
143
+ prereq_eval = PrerequisiteEvalRecord.new(prereq_flag, flag, prereq_res)
144
+ state.prereq_evals = [] if state.prereq_evals.nil?
145
+ state.prereq_evals.push(prereq_eval)
141
146
  rescue => exn
142
147
  Util.log_exception(@logger, "Error evaluating prerequisite flag \"#{prereq_key}\" for flag \"#{flag[:key]}\"", exn)
143
148
  prereq_ok = false
@@ -0,0 +1,63 @@
1
+ require "ldclient-rb/impl/event_types"
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ EventSummary = Struct.new(:start_date, :end_date, :counters)
6
+
7
+ EventSummaryFlagInfo = Struct.new(:default, :versions)
8
+
9
+ EventSummaryFlagVariationCounter = Struct.new(:value, :count)
10
+
11
+ # Manages the state of summarizable information for the EventProcessor, including the
12
+ # event counters and user deduplication. Note that the methods of this class are
13
+ # deliberately not thread-safe; the EventProcessor is responsible for enforcing
14
+ # synchronization across both the summarizer and the event queue.
15
+ class EventSummarizer
16
+ class Counter
17
+ end
18
+
19
+ def initialize
20
+ clear
21
+ end
22
+
23
+ # Adds this event to our counters, if it is a type of event we need to count.
24
+ def summarize_event(event)
25
+ return if !event.is_a?(LaunchDarkly::Impl::EvalEvent)
26
+
27
+ counters_for_flag = @counters[event.key]
28
+ if counters_for_flag.nil?
29
+ counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new)
30
+ @counters[event.key] = counters_for_flag
31
+ end
32
+ counters_for_flag_version = counters_for_flag.versions[event.version]
33
+ if counters_for_flag_version.nil?
34
+ counters_for_flag_version = Hash.new
35
+ counters_for_flag.versions[event.version] = counters_for_flag_version
36
+ end
37
+ variation_counter = counters_for_flag_version[event.variation]
38
+ if variation_counter.nil?
39
+ counters_for_flag_version[event.variation] = EventSummaryFlagVariationCounter.new(event.value, 1)
40
+ else
41
+ variation_counter.count = variation_counter.count + 1
42
+ end
43
+ time = event.timestamp
44
+ if !time.nil?
45
+ @start_date = time if @start_date == 0 || time < @start_date
46
+ @end_date = time if time > @end_date
47
+ end
48
+ end
49
+
50
+ # Returns a snapshot of the current summarized event data, and resets this state.
51
+ def snapshot
52
+ ret = EventSummary.new(@start_date, @end_date, @counters)
53
+ ret
54
+ end
55
+
56
+ def clear
57
+ @start_date = 0
58
+ @end_date = 0
59
+ @counters = {}
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,90 @@
1
+ module LaunchDarkly
2
+ module Impl
3
+ class Event
4
+ def initialize(timestamp, user)
5
+ @timestamp = timestamp
6
+ @user = user
7
+ end
8
+
9
+ attr_reader :timestamp
10
+ attr_reader :kind
11
+ attr_reader :user
12
+ end
13
+
14
+ class EvalEvent < Event
15
+ def initialize(timestamp, user, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
16
+ track_events = false, debug_until = nil, prereq_of = nil)
17
+ super(timestamp, user)
18
+ @key = key
19
+ @version = version
20
+ @variation = variation
21
+ @value = value
22
+ @reason = reason
23
+ @default = default
24
+ # avoid setting rarely-used attributes if they have no value - this saves a little space per instance
25
+ @track_events = track_events if track_events
26
+ @debug_until = debug_until if debug_until
27
+ @prereq_of = prereq_of if prereq_of
28
+ end
29
+
30
+ attr_reader :key
31
+ attr_reader :version
32
+ attr_reader :variation
33
+ attr_reader :value
34
+ attr_reader :reason
35
+ attr_reader :default
36
+ attr_reader :track_events
37
+ attr_reader :debug_until
38
+ attr_reader :prereq_of
39
+ end
40
+
41
+ class IdentifyEvent < Event
42
+ def initialize(timestamp, user)
43
+ super(timestamp, user)
44
+ end
45
+ end
46
+
47
+ class CustomEvent < Event
48
+ def initialize(timestamp, user, key, data = nil, metric_value = nil)
49
+ super(timestamp, user)
50
+ @key = key
51
+ @data = data if !data.nil?
52
+ @metric_value = metric_value if !metric_value.nil?
53
+ end
54
+
55
+ attr_reader :key
56
+ attr_reader :data
57
+ attr_reader :metric_value
58
+ end
59
+
60
+ class AliasEvent < Event
61
+ def initialize(timestamp, key, context_kind, previous_key, previous_context_kind)
62
+ super(timestamp, nil)
63
+ @key = key
64
+ @context_kind = context_kind
65
+ @previous_key = previous_key
66
+ @previous_context_kind = previous_context_kind
67
+ end
68
+
69
+ attr_reader :key
70
+ attr_reader :context_kind
71
+ attr_reader :previous_key
72
+ attr_reader :previous_context_kind
73
+ end
74
+
75
+ class IndexEvent < Event
76
+ def initialize(timestamp, user)
77
+ super(timestamp, user)
78
+ end
79
+ end
80
+
81
+ class DebugEvent < Event
82
+ def initialize(eval_event)
83
+ super(eval_event.timestamp, eval_event.user)
84
+ @eval_event = eval_event
85
+ end
86
+
87
+ attr_reader :eval_event
88
+ end
89
+ end
90
+ end
@@ -1,7 +1,6 @@
1
1
  require "ldclient-rb/impl/big_segments"
2
2
  require "ldclient-rb/impl/diagnostic_events"
3
3
  require "ldclient-rb/impl/evaluator"
4
- require "ldclient-rb/impl/event_factory"
5
4
  require "ldclient-rb/impl/store_client_wrapper"
6
5
  require "concurrent/atomics"
7
6
  require "digest/sha1"
@@ -46,9 +45,6 @@ module LaunchDarkly
46
45
 
47
46
  @sdk_key = sdk_key
48
47
 
49
- @event_factory_default = EventFactory.new(false)
50
- @event_factory_with_reasons = EventFactory.new(true)
51
-
52
48
  # We need to wrap the feature store object with a FeatureStoreClientWrapper in order to add
53
49
  # some necessary logic around updates. Unfortunately, we have code elsewhere that accesses
54
50
  # the feature store through the Config object, so we need to make a new Config that uses
@@ -202,7 +198,7 @@ module LaunchDarkly
202
198
  # @return the variation to show the user, or the default value if there's an an error
203
199
  #
204
200
  def variation(key, user, default)
205
- evaluate_internal(key, user, default, @event_factory_default).value
201
+ evaluate_internal(key, user, default, false).value
206
202
  end
207
203
 
208
204
  #
@@ -229,7 +225,7 @@ module LaunchDarkly
229
225
  # @return [EvaluationDetail] an object describing the result
230
226
  #
231
227
  def variation_detail(key, user, default)
232
- evaluate_internal(key, user, default, @event_factory_with_reasons)
228
+ evaluate_internal(key, user, default, true)
233
229
  end
234
230
 
235
231
  #
@@ -253,7 +249,7 @@ module LaunchDarkly
253
249
  return
254
250
  end
255
251
  sanitize_user(user)
256
- @event_processor.add_event(@event_factory_default.new_identify_event(user))
252
+ @event_processor.record_identify_event(user)
257
253
  end
258
254
 
259
255
  #
@@ -284,7 +280,7 @@ module LaunchDarkly
284
280
  return
285
281
  end
286
282
  sanitize_user(user)
287
- @event_processor.add_event(@event_factory_default.new_custom_event(event_name, user, data, metric_value))
283
+ @event_processor.record_custom_event(user, event_name, data, metric_value)
288
284
  end
289
285
 
290
286
  #
@@ -301,7 +297,7 @@ module LaunchDarkly
301
297
  end
302
298
  sanitize_user(current_context)
303
299
  sanitize_user(previous_context)
304
- @event_processor.add_event(@event_factory_default.new_alias_event(current_context, previous_context))
300
+ @event_processor.record_alias_event(current_context, previous_context)
305
301
  end
306
302
 
307
303
  #
@@ -368,13 +364,13 @@ module LaunchDarkly
368
364
  next
369
365
  end
370
366
  begin
371
- detail = @evaluator.evaluate(f, user, @event_factory_default).detail
367
+ detail = @evaluator.evaluate(f, user).detail
372
368
  rescue => exn
373
369
  detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
374
370
  Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
375
371
  end
376
372
 
377
- requires_experiment_data = EventFactory.is_experiment(f, detail.reason)
373
+ requires_experiment_data = is_experiment(f, detail.reason)
378
374
  flag_state = {
379
375
  key: f[:key],
380
376
  value: detail.value,
@@ -430,7 +426,7 @@ module LaunchDarkly
430
426
  end
431
427
 
432
428
  # @return [EvaluationDetail]
433
- def evaluate_internal(key, user, default, event_factory)
429
+ def evaluate_internal(key, user, default, with_reasons)
434
430
  if @config.offline?
435
431
  return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
436
432
  end
@@ -453,7 +449,7 @@ module LaunchDarkly
453
449
  else
454
450
  @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
455
451
  detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
456
- @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
452
+ record_unknown_flag_eval(key, user, default, detail.reason, with_reasons)
457
453
  return detail
458
454
  end
459
455
  end
@@ -463,32 +459,94 @@ module LaunchDarkly
463
459
  if feature.nil?
464
460
  @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
465
461
  detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
466
- @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
462
+ record_unknown_flag_eval(key, user, default, detail.reason, with_reasons)
467
463
  return detail
468
464
  end
469
465
 
470
466
  begin
471
- res = @evaluator.evaluate(feature, user, event_factory)
472
- if !res.events.nil?
473
- res.events.each do |event|
474
- @event_processor.add_event(event)
467
+ res = @evaluator.evaluate(feature, user)
468
+ if !res.prereq_evals.nil?
469
+ res.prereq_evals.each do |prereq_eval|
470
+ record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag, user, prereq_eval.detail, with_reasons)
475
471
  end
476
472
  end
477
473
  detail = res.detail
478
474
  if detail.default_value?
479
475
  detail = EvaluationDetail.new(default, nil, detail.reason)
480
476
  end
481
- @event_processor.add_event(event_factory.new_eval_event(feature, user, detail, default))
477
+ record_flag_eval(feature, user, detail, default, with_reasons)
482
478
  return detail
483
479
  rescue => exn
484
480
  Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
485
481
  detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
486
- @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
482
+ record_flag_eval_error(feature, user, default, detail.reason, with_reasons)
487
483
  return detail
488
484
  end
489
485
  end
490
486
 
491
- def sanitize_user(user)
487
+ private def record_flag_eval(flag, user, detail, default, with_reasons)
488
+ add_experiment_data = is_experiment(flag, detail.reason)
489
+ @event_processor.record_eval_event(
490
+ user,
491
+ flag[:key],
492
+ flag[:version],
493
+ detail.variation_index,
494
+ detail.value,
495
+ (add_experiment_data || with_reasons) ? detail.reason : nil,
496
+ default,
497
+ add_experiment_data || flag[:trackEvents] || false,
498
+ flag[:debugEventsUntilDate],
499
+ nil
500
+ )
501
+ end
502
+
503
+ private def record_prereq_flag_eval(prereq_flag, prereq_of_flag, user, detail, with_reasons)
504
+ add_experiment_data = is_experiment(prereq_flag, detail.reason)
505
+ @event_processor.record_eval_event(
506
+ user,
507
+ prereq_flag[:key],
508
+ prereq_flag[:version],
509
+ detail.variation_index,
510
+ detail.value,
511
+ (add_experiment_data || with_reasons) ? detail.reason : nil,
512
+ nil,
513
+ add_experiment_data || prereq_flag[:trackEvents] || false,
514
+ prereq_flag[:debugEventsUntilDate],
515
+ prereq_of_flag[:key]
516
+ )
517
+ end
518
+
519
+ private def record_flag_eval_error(flag, user, default, reason, with_reasons)
520
+ @event_processor.record_eval_event(user, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
521
+ flag[:trackEvents], flag[:debugEventsUntilDate], nil)
522
+ end
523
+
524
+ private def record_unknown_flag_eval(flag_key, user, default, reason, with_reasons)
525
+ @event_processor.record_eval_event(user, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
526
+ false, nil, nil)
527
+ end
528
+
529
+ private def is_experiment(flag, reason)
530
+ return false if !reason
531
+
532
+ if reason.in_experiment
533
+ return true
534
+ end
535
+
536
+ case reason[:kind]
537
+ when 'RULE_MATCH'
538
+ index = reason[:ruleIndex]
539
+ if !index.nil?
540
+ rules = flag[:rules] || []
541
+ return index >= 0 && index < rules.length && rules[index][:trackEvents]
542
+ end
543
+ when 'FALLTHROUGH'
544
+ return !!flag[:trackEventsFallthrough]
545
+ end
546
+ false
547
+ end
548
+
549
+ private def sanitize_user(user)
492
550
  if user[:key]
493
551
  user[:key] = user[:key].to_s
494
552
  end
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "6.3.3"
2
+ VERSION = "6.3.4"
3
3
  end
data/lib/ldclient-rb.rb CHANGED
@@ -21,7 +21,6 @@ require "ldclient-rb/polling"
21
21
  require "ldclient-rb/user_filter"
22
22
  require "ldclient-rb/simple_lru_cache"
23
23
  require "ldclient-rb/non_blocking_thread_pool"
24
- require "ldclient-rb/event_summarizer"
25
24
  require "ldclient-rb/events"
26
25
  require "ldclient-rb/requestor"
27
26
  require "ldclient-rb/file_data_source"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.3
4
+ version: 6.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-15 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -254,7 +254,6 @@ files:
254
254
  - lib/ldclient-rb/cache_store.rb
255
255
  - lib/ldclient-rb/config.rb
256
256
  - lib/ldclient-rb/evaluation_detail.rb
257
- - lib/ldclient-rb/event_summarizer.rb
258
257
  - lib/ldclient-rb/events.rb
259
258
  - lib/ldclient-rb/expiring_cache.rb
260
259
  - lib/ldclient-rb/file_data_source.rb
@@ -265,8 +264,9 @@ files:
265
264
  - lib/ldclient-rb/impl/evaluator.rb
266
265
  - lib/ldclient-rb/impl/evaluator_bucketing.rb
267
266
  - lib/ldclient-rb/impl/evaluator_operators.rb
268
- - lib/ldclient-rb/impl/event_factory.rb
269
267
  - lib/ldclient-rb/impl/event_sender.rb
268
+ - lib/ldclient-rb/impl/event_summarizer.rb
269
+ - lib/ldclient-rb/impl/event_types.rb
270
270
  - lib/ldclient-rb/impl/integrations/consul_impl.rb
271
271
  - lib/ldclient-rb/impl/integrations/dynamodb_impl.rb
272
272
  - lib/ldclient-rb/impl/integrations/file_data_source.rb
@@ -319,7 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
319
319
  - !ruby/object:Gem::Version
320
320
  version: '0'
321
321
  requirements: []
322
- rubygems_version: 3.3.16
322
+ rubygems_version: 3.3.17
323
323
  signing_key:
324
324
  specification_version: 4
325
325
  summary: LaunchDarkly SDK for Ruby
@@ -1,55 +0,0 @@
1
-
2
- module LaunchDarkly
3
- # @private
4
- EventSummary = Struct.new(:start_date, :end_date, :counters)
5
-
6
- # Manages the state of summarizable information for the EventProcessor, including the
7
- # event counters and user deduplication. Note that the methods of this class are
8
- # deliberately not thread-safe; the EventProcessor is responsible for enforcing
9
- # synchronization across both the summarizer and the event queue.
10
- #
11
- # @private
12
- class EventSummarizer
13
- def initialize
14
- clear
15
- end
16
-
17
- # Adds this event to our counters, if it is a type of event we need to count.
18
- def summarize_event(event)
19
- if event[:kind] == "feature"
20
- counter_key = {
21
- key: event[:key],
22
- version: event[:version],
23
- variation: event[:variation]
24
- }
25
- c = @counters[counter_key]
26
- if c.nil?
27
- @counters[counter_key] = {
28
- value: event[:value],
29
- default: event[:default],
30
- count: 1
31
- }
32
- else
33
- c[:count] = c[:count] + 1
34
- end
35
- time = event[:creationDate]
36
- if !time.nil?
37
- @start_date = time if @start_date == 0 || time < @start_date
38
- @end_date = time if time > @end_date
39
- end
40
- end
41
- end
42
-
43
- # Returns a snapshot of the current summarized event data, and resets this state.
44
- def snapshot
45
- ret = EventSummary.new(@start_date, @end_date, @counters)
46
- ret
47
- end
48
-
49
- def clear
50
- @start_date = 0
51
- @end_date = 0
52
- @counters = {}
53
- end
54
- end
55
- end
@@ -1,123 +0,0 @@
1
-
2
- module LaunchDarkly
3
- module Impl
4
- # Event constructors are centralized here to avoid mistakes and repetitive logic.
5
- # The LDClient owns two instances of EventFactory: one that always embeds evaluation reasons
6
- # in the events (for when variation_detail is called) and one that doesn't.
7
- #
8
- # Note that these methods do not set the "creationDate" property, because in the Ruby client,
9
- # that is done by EventProcessor.add_event().
10
- class EventFactory
11
- def initialize(with_reasons)
12
- @with_reasons = with_reasons
13
- end
14
-
15
- def new_eval_event(flag, user, detail, default_value, prereq_of_flag = nil)
16
- add_experiment_data = self.class.is_experiment(flag, detail.reason)
17
- e = {
18
- kind: 'feature',
19
- key: flag[:key],
20
- user: user,
21
- variation: detail.variation_index,
22
- value: detail.value,
23
- default: default_value,
24
- version: flag[:version]
25
- }
26
- # the following properties are handled separately so we don't waste bandwidth on unused keys
27
- e[:trackEvents] = true if add_experiment_data || flag[:trackEvents]
28
- e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
29
- e[:prereqOf] = prereq_of_flag[:key] if !prereq_of_flag.nil?
30
- e[:reason] = detail.reason if add_experiment_data || @with_reasons
31
- e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
32
- e
33
- end
34
-
35
- def new_default_event(flag, user, default_value, reason)
36
- e = {
37
- kind: 'feature',
38
- key: flag[:key],
39
- user: user,
40
- value: default_value,
41
- default: default_value,
42
- version: flag[:version]
43
- }
44
- e[:trackEvents] = true if flag[:trackEvents]
45
- e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
46
- e[:reason] = reason if @with_reasons
47
- e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
48
- e
49
- end
50
-
51
- def new_unknown_flag_event(key, user, default_value, reason)
52
- e = {
53
- kind: 'feature',
54
- key: key,
55
- user: user,
56
- value: default_value,
57
- default: default_value
58
- }
59
- e[:reason] = reason if @with_reasons
60
- e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
61
- e
62
- end
63
-
64
- def new_identify_event(user)
65
- {
66
- kind: 'identify',
67
- key: user[:key],
68
- user: user
69
- }
70
- end
71
-
72
- def new_alias_event(current_context, previous_context)
73
- {
74
- kind: 'alias',
75
- key: current_context[:key],
76
- contextKind: context_to_context_kind(current_context),
77
- previousKey: previous_context[:key],
78
- previousContextKind: context_to_context_kind(previous_context)
79
- }
80
- end
81
-
82
- def new_custom_event(event_name, user, data, metric_value)
83
- e = {
84
- kind: 'custom',
85
- key: event_name,
86
- user: user
87
- }
88
- e[:data] = data if !data.nil?
89
- e[:metricValue] = metric_value if !metric_value.nil?
90
- e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
91
- e
92
- end
93
-
94
- def self.is_experiment(flag, reason)
95
- return false if !reason
96
-
97
- if reason.in_experiment
98
- return true
99
- end
100
-
101
- case reason[:kind]
102
- when 'RULE_MATCH'
103
- index = reason[:ruleIndex]
104
- if !index.nil?
105
- rules = flag[:rules] || []
106
- return index >= 0 && index < rules.length && rules[index][:trackEvents]
107
- end
108
- when 'FALLTHROUGH'
109
- return !!flag[:trackEventsFallthrough]
110
- end
111
- false
112
- end
113
-
114
- private def context_to_context_kind(user)
115
- if !user.nil? && user[:anonymous]
116
- return "anonymousUser"
117
- else
118
- return "user"
119
- end
120
- end
121
- end
122
- end
123
- end