launchdarkly-server-sdk 6.3.3 → 6.3.4

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: 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