statsig 1.33.1 → 1.33.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/api_config.rb +80 -63
- data/lib/client_initialize_helpers.rb +1 -0
- data/lib/diagnostics.rb +6 -1
- data/lib/error_boundary.rb +3 -3
- data/lib/evaluation_helpers.rb +6 -14
- data/lib/evaluator.rb +16 -7
- data/lib/hash_utils.rb +4 -0
- data/lib/memo.rb +23 -0
- data/lib/spec_store.rb +6 -6
- data/lib/statsig.rb +8 -1
- data/lib/statsig_driver.rb +124 -103
- data/lib/statsig_event.rb +3 -1
- data/lib/statsig_logger.rb +20 -12
- data/lib/statsig_user.rb +94 -11
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b2947b68bfada4686360dc5aab60a460129a3ca5e4c63d6f2107af68788d2a8
|
4
|
+
data.tar.gz: 98e56828d1f31ec18afc0a2f46dbcab90c2a976dc95ef1db9ead5060b240952f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1f11434fb5ec030500429cdfd11146fe845568498cded4917e1f412ca50a86beb03f937febcaeceaf8523dacaa550b9024384dc58b0b3c9b2619d37041e0df0
|
7
|
+
data.tar.gz: d64fd8980ca648626ecdcd0095d4964aa531d24d7acaa7c07dd70013a32d0f7c5c548602e9578044c0567c0c2ac948f5dd8bd499c2178323b0ae176d0925a82c
|
data/lib/api_config.rb
CHANGED
@@ -8,22 +8,6 @@ module Statsig
|
|
8
8
|
attr_accessor :name, :type, :is_active, :salt, :default_value, :enabled,
|
9
9
|
:rules, :id_type, :entity, :explicit_parameters, :has_shared_params, :target_app_ids
|
10
10
|
|
11
|
-
def initialize(name:, type:, is_active:, salt:, default_value:, enabled:, rules:, id_type:, entity:,
|
12
|
-
explicit_parameters: nil, has_shared_params: nil, target_app_ids: nil)
|
13
|
-
@name = name
|
14
|
-
@type = type.to_sym unless entity.nil?
|
15
|
-
@is_active = is_active
|
16
|
-
@salt = salt
|
17
|
-
@default_value = JSON.parse(JSON.generate(default_value))
|
18
|
-
@enabled = enabled
|
19
|
-
@rules = rules
|
20
|
-
@id_type = id_type
|
21
|
-
@entity = entity.to_sym unless entity.nil?
|
22
|
-
@explicit_parameters = explicit_parameters
|
23
|
-
@has_shared_params = has_shared_params
|
24
|
-
@target_app_ids = target_app_ids
|
25
|
-
end
|
26
|
-
|
27
11
|
def self.from_json(json)
|
28
12
|
new(
|
29
13
|
name: json[:name],
|
@@ -42,28 +26,30 @@ module Statsig
|
|
42
26
|
target_app_ids: json[:targetAppIDs]
|
43
27
|
)
|
44
28
|
end
|
45
|
-
end
|
46
|
-
end
|
47
29
|
|
48
|
-
|
49
|
-
class APIRule
|
30
|
+
private
|
50
31
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def initialize(name:, pass_percentage:, return_value:, id:, salt:, conditions:, id_type:,
|
55
|
-
group_name: nil, config_delegate: nil, is_experiment_group: nil)
|
32
|
+
def initialize(name:, type:, is_active:, salt:, default_value:, enabled:, rules:, id_type:, entity:,
|
33
|
+
explicit_parameters: nil, has_shared_params: nil, target_app_ids: nil)
|
56
34
|
@name = name
|
57
|
-
@
|
58
|
-
@
|
59
|
-
@id = id
|
35
|
+
@type = type.to_sym unless type.nil?
|
36
|
+
@is_active = is_active
|
60
37
|
@salt = salt
|
61
|
-
@
|
38
|
+
@default_value = JSON.parse(JSON.generate(default_value))
|
39
|
+
@enabled = enabled
|
40
|
+
@rules = rules
|
62
41
|
@id_type = id_type
|
63
|
-
@
|
64
|
-
@
|
65
|
-
@
|
42
|
+
@entity = entity.to_sym unless entity.nil?
|
43
|
+
@explicit_parameters = explicit_parameters
|
44
|
+
@has_shared_params = has_shared_params
|
45
|
+
@target_app_ids = target_app_ids
|
66
46
|
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class APIRule
|
50
|
+
|
51
|
+
attr_accessor :name, :pass_percentage, :return_value, :id, :salt,
|
52
|
+
:conditions, :id_type, :group_name, :config_delegate, :is_experiment_group
|
67
53
|
|
68
54
|
def self.from_json(json)
|
69
55
|
new(
|
@@ -81,48 +67,79 @@ module Statsig
|
|
81
67
|
is_experiment_group: json[:isExperimentGroup]
|
82
68
|
)
|
83
69
|
end
|
84
|
-
end
|
85
|
-
end
|
86
70
|
|
87
|
-
|
88
|
-
class APICondition
|
89
|
-
|
90
|
-
attr_accessor :type, :target_value, :operator, :field, :additional_values, :id_type
|
71
|
+
private
|
91
72
|
|
92
|
-
def initialize(
|
93
|
-
|
94
|
-
@
|
95
|
-
@
|
96
|
-
@
|
97
|
-
@
|
73
|
+
def initialize(name:, pass_percentage:, return_value:, id:, salt:, conditions:, id_type:,
|
74
|
+
group_name: nil, config_delegate: nil, is_experiment_group: nil)
|
75
|
+
@name = name
|
76
|
+
@pass_percentage = pass_percentage.to_f
|
77
|
+
@return_value = JSON.parse(JSON.generate(return_value))
|
78
|
+
@id = id
|
79
|
+
@salt = salt
|
80
|
+
@conditions = conditions
|
98
81
|
@id_type = id_type
|
82
|
+
@group_name = group_name
|
83
|
+
@config_delegate = config_delegate
|
84
|
+
@is_experiment_group = is_experiment_group
|
99
85
|
end
|
86
|
+
end
|
100
87
|
|
88
|
+
class APICondition
|
89
|
+
|
90
|
+
attr_accessor :type, :target_value, :operator, :field, :additional_values, :id_type, :hash
|
101
91
|
def self.from_json(json)
|
102
|
-
|
103
|
-
|
104
|
-
operator = operator
|
105
|
-
unless
|
106
|
-
|
92
|
+
hash = Statsig::HashUtils.md5(json.to_s).to_sym
|
93
|
+
return Statsig::Memo.for_global(:api_condition_from_json, hash) do
|
94
|
+
operator = json[:operator]
|
95
|
+
unless operator.nil?
|
96
|
+
operator = operator&.downcase&.to_sym
|
97
|
+
unless Const::SUPPORTED_OPERATORS.include?(operator)
|
98
|
+
raise UnsupportedConfigException
|
99
|
+
end
|
107
100
|
end
|
108
|
-
end
|
109
101
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
102
|
+
type = json[:type]
|
103
|
+
unless type.nil?
|
104
|
+
type = type&.downcase&.to_sym
|
105
|
+
unless Const::SUPPORTED_CONDITION_TYPES.include?(type)
|
106
|
+
raise UnsupportedConfigException
|
107
|
+
end
|
115
108
|
end
|
109
|
+
|
110
|
+
new(
|
111
|
+
type: json[:type],
|
112
|
+
target_value: json[:targetValue],
|
113
|
+
operator: json[:operator],
|
114
|
+
field: json[:field],
|
115
|
+
additional_values: json[:additionalValues],
|
116
|
+
id_type: json[:idType],
|
117
|
+
hash: hash
|
118
|
+
)
|
116
119
|
end
|
120
|
+
end
|
117
121
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
122
|
+
private
|
123
|
+
|
124
|
+
def initialize(type:, target_value:, operator:, field:, additional_values:, id_type:, hash:)
|
125
|
+
@hash = hash
|
126
|
+
|
127
|
+
@type = type.to_sym unless type.nil?
|
128
|
+
if operator == "any_case_sensitive" || operator == "none_case_sensitive"
|
129
|
+
if target_value.is_a?(Array)
|
130
|
+
target_value = target_value.map { |item| [item.to_s, true] }.to_h
|
131
|
+
end
|
132
|
+
end
|
133
|
+
if operator == "any" || operator == "none"
|
134
|
+
if target_value.is_a?(Array)
|
135
|
+
target_value = target_value.map { |item| [item.to_s.downcase, true] }.to_h
|
136
|
+
end
|
137
|
+
end
|
138
|
+
@target_value = target_value
|
139
|
+
@operator = operator.to_sym unless operator.nil?
|
140
|
+
@field = field
|
141
|
+
@additional_values = additional_values || {}
|
142
|
+
@id_type = id_type
|
126
143
|
end
|
127
144
|
end
|
128
145
|
end
|
@@ -25,6 +25,7 @@ module Statsig
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.to_response(config_name, config_spec, evaluator, user, client_sdk_key, hash_algo, include_exposures, include_local_overrides)
|
28
|
+
|
28
29
|
target_app_id = evaluator.spec_store.get_app_id_for_sdk_key(client_sdk_key)
|
29
30
|
config_target_apps = config_spec.target_app_ids
|
30
31
|
|
data/lib/diagnostics.rb
CHANGED
@@ -58,7 +58,12 @@ module Statsig
|
|
58
58
|
rand * 10_000 < rate_over_ten_thousand
|
59
59
|
end
|
60
60
|
|
61
|
-
API_CALL_KEYS =
|
61
|
+
API_CALL_KEYS = {
|
62
|
+
:check_gate => true,
|
63
|
+
:get_config => true,
|
64
|
+
:get_experiment => true,
|
65
|
+
:get_layer => true
|
66
|
+
}.freeze
|
62
67
|
|
63
68
|
class Tracker
|
64
69
|
def initialize(diagnostics, context, key, step, tags = {})
|
data/lib/error_boundary.rb
CHANGED
@@ -10,9 +10,9 @@ module Statsig
|
|
10
10
|
@seen = Set.new
|
11
11
|
end
|
12
12
|
|
13
|
-
def capture(
|
13
|
+
def capture(recover: -> {}, caller: nil)
|
14
14
|
begin
|
15
|
-
res =
|
15
|
+
res = yield
|
16
16
|
rescue StandardError, SystemStackError => e
|
17
17
|
if e.is_a?(Statsig::UninitializedError) || e.is_a?(Statsig::ValueError)
|
18
18
|
raise e
|
@@ -20,7 +20,7 @@ module Statsig
|
|
20
20
|
|
21
21
|
puts '[Statsig]: An unexpected exception occurred.'
|
22
22
|
puts e.message
|
23
|
-
log_exception(e, tag: caller)
|
23
|
+
log_exception(e, tag: caller&.to_s)
|
24
24
|
res = recover.call
|
25
25
|
end
|
26
26
|
return res
|
data/lib/evaluation_helpers.rb
CHANGED
@@ -10,7 +10,6 @@ module EvaluationHelpers
|
|
10
10
|
def self.match_string_in_array(array, value, ignore_case, func)
|
11
11
|
str_value = value.to_s
|
12
12
|
str_value_downcased = nil
|
13
|
-
|
14
13
|
return false if array.nil?
|
15
14
|
|
16
15
|
return array.any? do |item|
|
@@ -26,22 +25,15 @@ module EvaluationHelpers
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def self.equal_string_in_array(array, value, ignore_case)
|
29
|
-
str_value = value.to_s
|
30
|
-
str_value_downcased = nil
|
31
|
-
|
32
28
|
return false if array.nil?
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
item_str = item.to_s
|
37
|
-
|
38
|
-
next false unless item_str.length == str_value.length
|
39
|
-
|
40
|
-
return true if item_str == str_value
|
41
|
-
next false unless ignore_case
|
30
|
+
str_value = value.to_s
|
31
|
+
str_value_downcased = nil
|
42
32
|
|
43
|
-
|
44
|
-
|
33
|
+
if ignore_case
|
34
|
+
return array.has_key?(value.to_s.downcase)
|
35
|
+
else
|
36
|
+
return array.has_key?(value.to_s)
|
45
37
|
end
|
46
38
|
end
|
47
39
|
|
data/lib/evaluator.rb
CHANGED
@@ -209,8 +209,8 @@ module Statsig
|
|
209
209
|
|
210
210
|
if user.custom_ids.nil? == false
|
211
211
|
evaluated_keys[:customIDs] = user.custom_ids
|
212
|
-
end
|
213
|
-
|
212
|
+
end
|
213
|
+
|
214
214
|
{
|
215
215
|
feature_gates: Statsig::ResponseFormatter
|
216
216
|
.get_responses(@spec_store.gates, self, user, client_sdk_key, hash_algo, include_local_overrides: include_local_overrides),
|
@@ -331,8 +331,19 @@ module Statsig
|
|
331
331
|
def eval_rule(user, rule, end_result)
|
332
332
|
pass = true
|
333
333
|
i = 0
|
334
|
+
|
335
|
+
memo = user.get_memo
|
334
336
|
until i >= rule.conditions.length
|
335
|
-
|
337
|
+
|
338
|
+
condition = rule.conditions[i]
|
339
|
+
|
340
|
+
if condition.type == :fail_gate || condition.type == :pass_gate
|
341
|
+
result = eval_condition(user, condition, end_result)
|
342
|
+
else
|
343
|
+
result = Memo.for(memo, :eval_rule, condition.hash) do
|
344
|
+
eval_condition(user, condition, end_result)
|
345
|
+
end
|
346
|
+
end
|
336
347
|
|
337
348
|
pass = false if result != true
|
338
349
|
i += 1
|
@@ -370,7 +381,6 @@ module Statsig
|
|
370
381
|
return true
|
371
382
|
when :fail_gate, :pass_gate
|
372
383
|
check_gate(user, target, end_result)
|
373
|
-
|
374
384
|
gate_value = end_result.gate_value
|
375
385
|
|
376
386
|
unless end_result.disable_exposures
|
@@ -527,8 +537,7 @@ module Statsig
|
|
527
537
|
def get_value_from_user(user, field)
|
528
538
|
return nil unless field.is_a?(String)
|
529
539
|
|
530
|
-
value = get_value_from_user_field(user, field)
|
531
|
-
value ||= get_value_from_user_field(user, field.downcase)
|
540
|
+
value = get_value_from_user_field(user, field.downcase)
|
532
541
|
|
533
542
|
if value.nil?
|
534
543
|
value = user.custom[field] if user.custom.is_a?(Hash)
|
@@ -584,8 +593,8 @@ module Statsig
|
|
584
593
|
return nil unless field.is_a?(String)
|
585
594
|
|
586
595
|
ua = get_value_from_user(user, Const::USER_AGENT)
|
587
|
-
return nil unless ua.is_a?(String)
|
588
596
|
|
597
|
+
return nil unless ua.is_a?(String)
|
589
598
|
case field.downcase
|
590
599
|
when Const::OSNAME, Const::OS_NAME
|
591
600
|
os = UAParser.parse_os(ua)
|
data/lib/hash_utils.rb
CHANGED
@@ -19,6 +19,10 @@ module Statsig
|
|
19
19
|
return Digest::SHA256.base64digest(input_str)
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.md5(input_str)
|
23
|
+
return Digest::MD5.base64digest(input_str)
|
24
|
+
end
|
25
|
+
|
22
26
|
def self.sortHash(input_hash)
|
23
27
|
dictionary = input_hash.clone.sort_by { |key| key }.to_h;
|
24
28
|
input_hash.each do |key, value|
|
data/lib/memo.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Statsig
|
2
|
+
class Memo
|
3
|
+
|
4
|
+
@global_memo = {}
|
5
|
+
|
6
|
+
def self.for(hash, method, key)
|
7
|
+
method_hash = hash[method]
|
8
|
+
unless method_hash
|
9
|
+
method_hash = hash[method] = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
return method_hash[key] if method_hash.key?(key)
|
13
|
+
|
14
|
+
method_hash[key] = yield
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for_global(method, key)
|
18
|
+
return self.for(@global_memo, method, key) do
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/spec_store.rb
CHANGED
@@ -161,10 +161,10 @@ module Statsig
|
|
161
161
|
|
162
162
|
def maybe_restart_background_threads
|
163
163
|
if @config_sync_thread.nil? || !@config_sync_thread.alive?
|
164
|
-
@config_sync_thread =
|
164
|
+
@config_sync_thread = spawn_sync_config_specs_thread
|
165
165
|
end
|
166
166
|
if @id_lists_sync_thread.nil? || !@id_lists_sync_thread.alive?
|
167
|
-
@id_lists_sync_thread =
|
167
|
+
@id_lists_sync_thread = spawn_sync_id_lists_thread
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
@@ -218,12 +218,12 @@ module Statsig
|
|
218
218
|
end
|
219
219
|
|
220
220
|
Thread.new do
|
221
|
-
@error_boundary.capture(
|
221
|
+
@error_boundary.capture() do
|
222
222
|
loop do
|
223
223
|
sleep @options.rulesets_sync_interval
|
224
224
|
sync_config_specs
|
225
225
|
end
|
226
|
-
|
226
|
+
end
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
@@ -233,12 +233,12 @@ module Statsig
|
|
233
233
|
end
|
234
234
|
|
235
235
|
Thread.new do
|
236
|
-
@error_boundary.capture(
|
236
|
+
@error_boundary.capture() do
|
237
237
|
loop do
|
238
238
|
sleep @id_lists_sync_interval
|
239
239
|
sync_id_lists
|
240
240
|
end
|
241
|
-
|
241
|
+
end
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
data/lib/statsig.rb
CHANGED
@@ -331,6 +331,13 @@ module Statsig
|
|
331
331
|
@shared_instance&.clear_config_overrides
|
332
332
|
end
|
333
333
|
|
334
|
+
##
|
335
|
+
# @param [HashTable] debug information log with exposure events
|
336
|
+
def self.set_debug_info(debug_info)
|
337
|
+
ensure_initialized
|
338
|
+
@shared_instance&.set_debug_info(debug_info)
|
339
|
+
end
|
340
|
+
|
334
341
|
##
|
335
342
|
# Gets all evaluated values for the given user.
|
336
343
|
# These values can then be given to a Statsig Client SDK via bootstrapping.
|
@@ -356,7 +363,7 @@ module Statsig
|
|
356
363
|
def self.get_statsig_metadata
|
357
364
|
{
|
358
365
|
'sdkType' => 'ruby-server',
|
359
|
-
'sdkVersion' => '1.33.
|
366
|
+
'sdkVersion' => '1.33.4',
|
360
367
|
'languageVersion' => RUBY_VERSION
|
361
368
|
}
|
362
369
|
end
|
data/lib/statsig_driver.rb
CHANGED
@@ -11,7 +11,7 @@ require 'dynamic_config'
|
|
11
11
|
require 'feature_gate'
|
12
12
|
require 'error_boundary'
|
13
13
|
require 'layer'
|
14
|
-
|
14
|
+
require 'memo'
|
15
15
|
require 'diagnostics'
|
16
16
|
|
17
17
|
class StatsigDriver
|
@@ -26,7 +26,7 @@ class StatsigDriver
|
|
26
26
|
end
|
27
27
|
|
28
28
|
@err_boundary = Statsig::ErrorBoundary.new(secret_key)
|
29
|
-
@err_boundary.capture(
|
29
|
+
@err_boundary.capture(caller: __method__) do
|
30
30
|
@diagnostics = Statsig::Diagnostics.new()
|
31
31
|
tracker = @diagnostics.track('initialize', 'overall')
|
32
32
|
@options = options || StatsigOptions.new
|
@@ -40,7 +40,7 @@ class StatsigDriver
|
|
40
40
|
tracker.end(success: true)
|
41
41
|
|
42
42
|
@logger.log_diagnostics_event(@diagnostics, 'initialize')
|
43
|
-
|
43
|
+
end
|
44
44
|
end
|
45
45
|
|
46
46
|
def get_gate_impl(
|
@@ -56,34 +56,38 @@ class StatsigDriver
|
|
56
56
|
return FeatureGate.new(gate_name) if gate.nil?
|
57
57
|
return FeatureGate.new(gate.name, target_app_ids: gate.target_app_ids)
|
58
58
|
end
|
59
|
+
|
59
60
|
user = verify_inputs(user, gate_name, 'gate_name')
|
61
|
+
return Statsig::Memo.for(user.get_memo(), :get_gate_impl, gate_name) do
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
+
res = Statsig::ConfigResult.new(name: gate_name, disable_exposures: disable_log_exposure, disable_evaluation_details: disable_evaluation_details)
|
64
|
+
@evaluator.check_gate(user, gate_name, res, ignore_local_overrides: ignore_local_overrides)
|
63
65
|
|
64
|
-
|
66
|
+
unless disable_log_exposure
|
65
67
|
@logger.log_gate_exposure(
|
66
|
-
|
67
|
-
|
68
|
+
user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details
|
69
|
+
)
|
70
|
+
end
|
71
|
+
FeatureGate.from_config_result(res)
|
68
72
|
end
|
69
|
-
FeatureGate.from_config_result(res)
|
70
73
|
end
|
71
74
|
|
75
|
+
|
72
76
|
def get_gate(user, gate_name, options = nil)
|
73
|
-
@err_boundary.capture(
|
74
|
-
run_with_diagnostics(
|
77
|
+
@err_boundary.capture(caller: __method__, recover: -> {false}) do
|
78
|
+
run_with_diagnostics(caller: :get_gate) do
|
75
79
|
get_gate_impl(user, gate_name,
|
76
80
|
disable_log_exposure: options&.disable_log_exposure == true,
|
77
81
|
skip_evaluation: options&.skip_evaluation == true,
|
78
82
|
disable_evaluation_details: options&.disable_evaluation_details == true
|
79
83
|
)
|
80
|
-
|
81
|
-
|
84
|
+
end
|
85
|
+
end
|
82
86
|
end
|
83
87
|
|
84
88
|
def check_gate(user, gate_name, options = nil)
|
85
|
-
@err_boundary.capture(
|
86
|
-
run_with_diagnostics(
|
89
|
+
@err_boundary.capture(caller: __method__, recover: -> {false}) do
|
90
|
+
run_with_diagnostics(caller: :check_gate) do
|
87
91
|
get_gate_impl(
|
88
92
|
user,
|
89
93
|
gate_name,
|
@@ -91,22 +95,22 @@ class StatsigDriver
|
|
91
95
|
disable_evaluation_details: options&.disable_evaluation_details == true,
|
92
96
|
ignore_local_overrides: options&.ignore_local_overrides == true
|
93
97
|
).value
|
94
|
-
|
95
|
-
|
98
|
+
end
|
99
|
+
end
|
96
100
|
end
|
97
101
|
|
98
102
|
def manually_log_gate_exposure(user, gate_name)
|
99
|
-
@err_boundary.capture(
|
103
|
+
@err_boundary.capture(caller: __method__) do
|
100
104
|
res = Statsig::ConfigResult.new(name: gate_name)
|
101
105
|
@evaluator.check_gate(user, gate_name, res)
|
102
106
|
context = { :is_manual_exposure => true }
|
103
107
|
@logger.log_gate_exposure(user, gate_name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
104
|
-
|
108
|
+
end
|
105
109
|
end
|
106
110
|
|
107
111
|
def get_config(user, dynamic_config_name, options = nil)
|
108
|
-
@err_boundary.capture(
|
109
|
-
run_with_diagnostics(
|
112
|
+
@err_boundary.capture(caller: __method__, recover: -> { DynamicConfig.new(dynamic_config_name) }) do
|
113
|
+
run_with_diagnostics(caller: :get_config) do
|
110
114
|
user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
|
111
115
|
get_config_impl(
|
112
116
|
user,
|
@@ -115,13 +119,13 @@ class StatsigDriver
|
|
115
119
|
disable_evaluation_details: options&.disable_evaluation_details == true,
|
116
120
|
ignore_local_overrides: options&.ignore_local_overrides == true
|
117
121
|
)
|
118
|
-
|
119
|
-
|
122
|
+
end
|
123
|
+
end
|
120
124
|
end
|
121
125
|
|
122
126
|
def get_experiment(user, experiment_name, options = nil)
|
123
|
-
@err_boundary.capture(
|
124
|
-
run_with_diagnostics(
|
127
|
+
@err_boundary.capture(caller: __method__, recover: -> { DynamicConfig.new(experiment_name) }) do
|
128
|
+
run_with_diagnostics(caller: :get_experiment) do
|
125
129
|
user = verify_inputs(user, experiment_name, "experiment_name")
|
126
130
|
get_config_impl(
|
127
131
|
user,
|
@@ -131,63 +135,65 @@ class StatsigDriver
|
|
131
135
|
disable_evaluation_details: options&.disable_evaluation_details == true,
|
132
136
|
ignore_local_overrides: options&.ignore_local_overrides == true
|
133
137
|
)
|
134
|
-
|
135
|
-
|
138
|
+
end
|
139
|
+
end
|
136
140
|
end
|
137
141
|
|
138
142
|
def manually_log_config_exposure(user, config_name)
|
139
|
-
@err_boundary.capture(
|
143
|
+
@err_boundary.capture(caller: __method__) do
|
140
144
|
res = Statsig::ConfigResult.new(name: config_name)
|
141
145
|
@evaluator.get_config(user, config_name, res)
|
142
146
|
|
143
147
|
context = { :is_manual_exposure => true }
|
144
148
|
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
145
|
-
|
149
|
+
end
|
146
150
|
end
|
147
151
|
|
148
152
|
def get_user_persisted_values(user, id_type)
|
149
|
-
@err_boundary.capture(
|
153
|
+
@err_boundary.capture(caller: __method__,) do
|
150
154
|
persisted_values = @persistent_storage_utils.get_user_persisted_values(user, id_type)
|
151
155
|
return {} if persisted_values.nil?
|
152
156
|
|
153
157
|
persisted_values
|
154
|
-
|
158
|
+
end
|
155
159
|
end
|
156
160
|
|
157
161
|
def get_layer(user, layer_name, options = nil)
|
158
|
-
@err_boundary.capture(
|
159
|
-
run_with_diagnostics(
|
162
|
+
@err_boundary.capture(caller: __method__, recover: -> { Layer.new(layer_name) }) do
|
163
|
+
run_with_diagnostics(caller: :get_layer) do
|
160
164
|
user = verify_inputs(user, layer_name, "layer_name")
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
165
|
+
Statsig::Memo.for(user.get_memo(), :get_layer, layer_name) do
|
166
|
+
exposures_disabled = options&.disable_log_exposure == true
|
167
|
+
res = Statsig::ConfigResult.new(
|
168
|
+
name: layer_name,
|
169
|
+
disable_exposures: exposures_disabled,
|
170
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
171
|
+
)
|
172
|
+
@evaluator.get_layer(user, layer_name, res)
|
173
|
+
|
174
|
+
exposure_log_func = !exposures_disabled ? lambda { |layer, parameter_name|
|
175
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res)
|
176
|
+
} : nil
|
177
|
+
|
178
|
+
Layer.new(res.name, res.json_value, res.rule_id, res.group_name, res.config_delegate, exposure_log_func)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
176
182
|
end
|
177
183
|
|
178
184
|
def manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
179
|
-
@err_boundary.capture(
|
185
|
+
@err_boundary.capture(caller: __method__) do
|
180
186
|
res = Statsig::ConfigResult.new(name: layer_name)
|
181
187
|
@evaluator.get_layer(user, layer_name, res)
|
182
188
|
|
183
189
|
layer = Layer.new(layer_name, res.json_value, res.rule_id, res.group_name, res.config_delegate)
|
184
190
|
context = { :is_manual_exposure => true }
|
185
191
|
@logger.log_layer_exposure(user, layer, parameter_name, res, context)
|
186
|
-
|
192
|
+
end
|
187
193
|
end
|
188
194
|
|
189
195
|
def log_event(user, event_name, value = nil, metadata = nil)
|
190
|
-
@err_boundary.capture(
|
196
|
+
@err_boundary.capture(caller: __method__) do
|
191
197
|
if !user.nil? && !user.instance_of?(StatsigUser)
|
192
198
|
raise Statsig::ValueError.new('Must provide a valid StatsigUser or nil')
|
193
199
|
end
|
@@ -200,93 +206,99 @@ class StatsigDriver
|
|
200
206
|
event.value = value
|
201
207
|
event.metadata = metadata
|
202
208
|
@logger.log_event(event)
|
203
|
-
|
209
|
+
end
|
204
210
|
end
|
205
211
|
|
206
212
|
def manually_sync_rulesets
|
207
|
-
@err_boundary.capture(
|
213
|
+
@err_boundary.capture(caller: __method__) do
|
208
214
|
@evaluator.spec_store.sync_config_specs
|
209
|
-
|
215
|
+
end
|
210
216
|
end
|
211
217
|
|
212
218
|
def manually_sync_idlists
|
213
|
-
@err_boundary.capture(
|
219
|
+
@err_boundary.capture(caller: __method__) do
|
214
220
|
@evaluator.spec_store.sync_id_lists
|
215
|
-
|
221
|
+
end
|
216
222
|
end
|
217
223
|
|
218
224
|
def list_gates
|
219
|
-
@err_boundary.capture(
|
225
|
+
@err_boundary.capture(caller: __method__) do
|
220
226
|
@evaluator.list_gates
|
221
|
-
|
227
|
+
end
|
222
228
|
end
|
223
229
|
|
224
230
|
def list_configs
|
225
|
-
@err_boundary.capture(
|
231
|
+
@err_boundary.capture(caller: __method__) do
|
226
232
|
@evaluator.list_configs
|
227
|
-
|
233
|
+
end
|
228
234
|
end
|
229
235
|
|
230
236
|
def list_experiments
|
231
|
-
@err_boundary.capture(
|
237
|
+
@err_boundary.capture(caller: __method__) do
|
232
238
|
@evaluator.list_experiments
|
233
|
-
|
239
|
+
end
|
234
240
|
end
|
235
241
|
|
236
242
|
def list_autotunes
|
237
|
-
@err_boundary.capture(
|
243
|
+
@err_boundary.capture(caller: __method__) do
|
238
244
|
@evaluator.list_autotunes
|
239
|
-
|
245
|
+
end
|
240
246
|
end
|
241
247
|
|
242
248
|
def list_layers
|
243
|
-
@err_boundary.capture(
|
249
|
+
@err_boundary.capture(caller: __method__) do
|
244
250
|
@evaluator.list_layers
|
245
|
-
|
251
|
+
end
|
246
252
|
end
|
247
253
|
|
248
254
|
def shutdown
|
249
|
-
@err_boundary.capture(
|
255
|
+
@err_boundary.capture(caller: __method__) do
|
250
256
|
@shutdown = true
|
251
257
|
@logger.shutdown
|
252
258
|
@evaluator.shutdown
|
253
|
-
|
259
|
+
end
|
254
260
|
end
|
255
261
|
|
256
262
|
def override_gate(gate_name, gate_value)
|
257
|
-
@err_boundary.capture(
|
263
|
+
@err_boundary.capture(caller: __method__) do
|
258
264
|
@evaluator.override_gate(gate_name, gate_value)
|
259
|
-
|
265
|
+
end
|
260
266
|
end
|
261
267
|
|
262
268
|
def remove_gate_override(gate_name)
|
263
|
-
@err_boundary.capture(
|
269
|
+
@err_boundary.capture(caller: __method__) do
|
264
270
|
@evaluator.remove_gate_override(gate_name)
|
265
|
-
|
271
|
+
end
|
266
272
|
end
|
267
273
|
|
268
274
|
def clear_gate_overrides
|
269
|
-
@err_boundary.capture(
|
275
|
+
@err_boundary.capture(caller: __method__) do
|
270
276
|
@evaluator.clear_gate_overrides
|
271
|
-
|
277
|
+
end
|
272
278
|
end
|
273
279
|
|
274
280
|
def override_config(config_name, config_value)
|
275
|
-
@err_boundary.capture(
|
281
|
+
@err_boundary.capture(caller: __method__) do
|
276
282
|
@evaluator.override_config(config_name, config_value)
|
277
|
-
|
283
|
+
end
|
278
284
|
end
|
279
285
|
|
280
286
|
def remove_config_override(config_name)
|
281
|
-
@err_boundary.capture(
|
287
|
+
@err_boundary.capture(caller: __method__) do
|
282
288
|
@evaluator.remove_config_override(config_name)
|
283
|
-
|
289
|
+
end
|
284
290
|
end
|
285
291
|
|
286
292
|
def clear_config_overrides
|
287
|
-
@err_boundary.capture(
|
293
|
+
@err_boundary.capture(caller: __method__) do
|
288
294
|
@evaluator.clear_config_overrides
|
289
|
-
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def set_debug_info(debug_info)
|
299
|
+
@err_boundary.capture(caller: __method__) do
|
300
|
+
@logger.set_debug_info(debug_info)
|
301
|
+
end
|
290
302
|
end
|
291
303
|
|
292
304
|
# @param [StatsigUser] user
|
@@ -294,11 +306,11 @@ class StatsigDriver
|
|
294
306
|
# @param [Boolean] include_local_overrides
|
295
307
|
# @return [Hash]
|
296
308
|
def get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
|
297
|
-
@err_boundary.capture(
|
309
|
+
@err_boundary.capture(caller: __method__, recover: -> { nil }) do
|
298
310
|
validate_user(user)
|
299
311
|
normalize_user(user)
|
300
312
|
@evaluator.get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
|
301
|
-
|
313
|
+
end
|
302
314
|
end
|
303
315
|
|
304
316
|
def maybe_restart_background_threads
|
@@ -306,22 +318,24 @@ class StatsigDriver
|
|
306
318
|
return
|
307
319
|
end
|
308
320
|
|
309
|
-
@err_boundary.capture(
|
321
|
+
@err_boundary.capture(caller: __method__) do
|
310
322
|
@evaluator.maybe_restart_background_threads
|
311
323
|
@logger.maybe_restart_background_threads
|
312
|
-
|
324
|
+
end
|
313
325
|
end
|
314
326
|
|
315
327
|
private
|
316
328
|
|
317
|
-
def run_with_diagnostics(
|
318
|
-
|
319
|
-
|
320
|
-
diagnostics = Statsig::Diagnostics.new()
|
321
|
-
tracker = diagnostics.track('api_call', caller)
|
329
|
+
def run_with_diagnostics(caller:)
|
330
|
+
if !Statsig::Diagnostics::API_CALL_KEYS[caller] || !Statsig::Diagnostics.sample(1)
|
331
|
+
return yield
|
322
332
|
end
|
333
|
+
|
334
|
+
diagnostics = Statsig::Diagnostics.new()
|
335
|
+
tracker = diagnostics.track('api_call', caller.to_s)
|
336
|
+
|
323
337
|
begin
|
324
|
-
res =
|
338
|
+
res = yield
|
325
339
|
tracker&.end(success: true)
|
326
340
|
rescue StandardError => e
|
327
341
|
tracker&.end(success: false)
|
@@ -334,28 +348,35 @@ class StatsigDriver
|
|
334
348
|
|
335
349
|
def verify_inputs(user, config_name, variable_name)
|
336
350
|
validate_user(user)
|
351
|
+
user = Statsig::Memo.for(user.get_memo(), :verify_inputs, 0) do
|
352
|
+
user = normalize_user(user)
|
353
|
+
check_shutdown
|
354
|
+
maybe_restart_background_threads
|
355
|
+
user
|
356
|
+
end
|
357
|
+
|
337
358
|
if !config_name.is_a?(String) || config_name.empty?
|
338
359
|
raise Statsig::ValueError.new("Invalid #{variable_name} provided")
|
339
360
|
end
|
340
361
|
|
341
|
-
|
342
|
-
maybe_restart_background_threads
|
343
|
-
normalize_user(user)
|
362
|
+
user
|
344
363
|
end
|
345
364
|
|
346
365
|
def get_config_impl(user, config_name, disable_log_exposure, user_persisted_values: nil, disable_evaluation_details: false, ignore_local_overrides: false)
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
366
|
+
return Statsig::Memo.for(user.get_memo(), :get_config_impl, config_name) do
|
367
|
+
res = Statsig::ConfigResult.new(
|
368
|
+
name: config_name,
|
369
|
+
disable_exposures: disable_log_exposure,
|
370
|
+
disable_evaluation_details: disable_evaluation_details
|
371
|
+
)
|
372
|
+
@evaluator.get_config(user, config_name, res, user_persisted_values: user_persisted_values, ignore_local_overrides: ignore_local_overrides)
|
353
373
|
|
354
|
-
|
355
|
-
|
356
|
-
|
374
|
+
unless disable_log_exposure
|
375
|
+
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
376
|
+
end
|
357
377
|
|
358
|
-
|
378
|
+
DynamicConfig.new(res.name, res.json_value, res.rule_id, res.group_name, res.id_type, res.evaluation_details)
|
379
|
+
end
|
359
380
|
end
|
360
381
|
|
361
382
|
def validate_user(user)
|
data/lib/statsig_event.rb
CHANGED
data/lib/statsig_logger.rb
CHANGED
@@ -28,6 +28,7 @@ module Statsig
|
|
28
28
|
@deduper = Concurrent::Set.new()
|
29
29
|
@interval = 0
|
30
30
|
@flush_mutex = Mutex.new
|
31
|
+
@debug_info = nil
|
31
32
|
end
|
32
33
|
|
33
34
|
def log_event(event)
|
@@ -45,6 +46,9 @@ module Statsig
|
|
45
46
|
gateValue: value.to_s,
|
46
47
|
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
47
48
|
}
|
49
|
+
if @debug_info != nil
|
50
|
+
metadata[:debugInfo] = @debug_info
|
51
|
+
end
|
48
52
|
return false if not is_unique_exposure(user, $gate_exposure_event, metadata)
|
49
53
|
event.metadata = metadata
|
50
54
|
|
@@ -62,6 +66,9 @@ module Statsig
|
|
62
66
|
config: config_name,
|
63
67
|
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
64
68
|
}
|
69
|
+
if @debug_info != nil
|
70
|
+
metadata[:debugInfo] = @debug_info
|
71
|
+
end
|
65
72
|
return false if not is_unique_exposure(user, $config_exposure_event, metadata)
|
66
73
|
event.metadata = metadata
|
67
74
|
event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
|
@@ -89,6 +96,9 @@ module Statsig
|
|
89
96
|
parameterName: parameter_name,
|
90
97
|
isExplicitParameter: String(is_explicit)
|
91
98
|
}
|
99
|
+
if @debug_info != nil
|
100
|
+
metadata[:debugInfo] = @debug_info
|
101
|
+
end
|
92
102
|
return false unless is_unique_exposure(user, $layer_exposure_event, metadata)
|
93
103
|
event.metadata = metadata
|
94
104
|
event.secondary_exposures = exposures.is_a?(Array) ? exposures : []
|
@@ -117,14 +127,14 @@ module Statsig
|
|
117
127
|
|
118
128
|
def periodic_flush
|
119
129
|
Thread.new do
|
120
|
-
@error_boundary.capture(
|
130
|
+
@error_boundary.capture() do
|
121
131
|
loop do
|
122
132
|
sleep @options.logging_interval_seconds
|
123
133
|
flush_async
|
124
134
|
@interval += 1
|
125
135
|
@deduper.clear if @interval % 2 == 0
|
126
136
|
end
|
127
|
-
|
137
|
+
end
|
128
138
|
end
|
129
139
|
end
|
130
140
|
|
@@ -160,6 +170,10 @@ module Statsig
|
|
160
170
|
end
|
161
171
|
end
|
162
172
|
|
173
|
+
def set_debug_info(debug_info)
|
174
|
+
@debug_info = debug_info
|
175
|
+
end
|
176
|
+
|
163
177
|
private
|
164
178
|
|
165
179
|
def safe_add_eval_details(eval_details, event)
|
@@ -186,21 +200,15 @@ module Statsig
|
|
186
200
|
def is_unique_exposure(user, event_name, metadata)
|
187
201
|
return true if user.nil?
|
188
202
|
@deduper.clear if @deduper.size > 10000
|
189
|
-
|
190
|
-
|
191
|
-
custom_id_key = user.custom_ids.values.join(',')
|
192
|
-
end
|
203
|
+
|
204
|
+
user_key = user.user_key
|
193
205
|
|
194
206
|
metadata_key = ''
|
195
207
|
if metadata.is_a?(Hash)
|
196
208
|
metadata_key = metadata.reject { |key, _| $ignored_metadata_keys.include?(key) }.values.join(',')
|
197
209
|
end
|
198
|
-
|
199
|
-
|
200
|
-
unless user.user_id.nil?
|
201
|
-
user_id_key = user.user_id
|
202
|
-
end
|
203
|
-
key = [user_id_key, custom_id_key, event_name, metadata_key].join(',')
|
210
|
+
|
211
|
+
key = [user_key, event_name, metadata_key].join(',')
|
204
212
|
|
205
213
|
return false if @deduper.include?(key)
|
206
214
|
@deduper.add(key)
|
data/lib/statsig_user.rb
CHANGED
@@ -1,39 +1,78 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'constants'
|
3
|
-
|
4
3
|
##
|
5
4
|
# The user object to be evaluated against your Statsig configurations (gates/experiments/dynamic configs).
|
6
5
|
class StatsigUser
|
7
6
|
|
8
7
|
# An identifier for this user. Evaluated against the User ID criteria. (https://docs.statsig.com/feature-gates/conditions#userid)
|
9
|
-
|
8
|
+
attr_reader :user_id
|
9
|
+
def user_id=(value)
|
10
|
+
value_changed()
|
11
|
+
@user_id = value
|
12
|
+
end
|
10
13
|
|
11
14
|
# An identifier for this user. Evaluated against the Email criteria. (https://docs.statsig.com/feature-gates/conditions#email)
|
12
|
-
|
15
|
+
attr_reader :email
|
16
|
+
def email=(value)
|
17
|
+
value_changed()
|
18
|
+
@email = value
|
19
|
+
end
|
13
20
|
|
14
21
|
# An IP address associated with this user. Evaluated against the IP Address criteria. (https://docs.statsig.com/feature-gates/conditions#ip)
|
15
|
-
|
22
|
+
attr_reader :ip
|
23
|
+
def ip=(value)
|
24
|
+
value_changed()
|
25
|
+
@ip = value
|
26
|
+
end
|
16
27
|
|
17
28
|
# A user agent string associated with this user. Evaluated against Browser Version and Name (https://docs.statsig.com/feature-gates/conditions#browser-version)
|
18
|
-
|
29
|
+
attr_reader :user_agent
|
30
|
+
def user_agent=(value)
|
31
|
+
value_changed()
|
32
|
+
@user_agent = value
|
33
|
+
end
|
19
34
|
|
20
35
|
# The country code associated with this user (e.g New Zealand => NZ). Evaluated against the Country criteria. (https://docs.statsig.com/feature-gates/conditions#country)
|
21
|
-
|
36
|
+
attr_reader :country
|
37
|
+
def country=(value)
|
38
|
+
value_changed()
|
39
|
+
@country = value
|
40
|
+
end
|
22
41
|
|
23
42
|
# An locale for this user.
|
24
|
-
|
43
|
+
attr_reader :locale
|
44
|
+
def locale=(value)
|
45
|
+
value_changed()
|
46
|
+
@locale = value
|
47
|
+
end
|
25
48
|
|
26
49
|
# The current app version the user is interacting with. Evaluated against the App Version criteria. (https://docs.statsig.com/feature-gates/conditions#app-version)
|
27
|
-
|
50
|
+
attr_reader :app_version
|
51
|
+
def app_version=(value)
|
52
|
+
value_changed()
|
53
|
+
@app_version = value
|
54
|
+
end
|
28
55
|
|
29
56
|
# A Hash you can use to set environment variables that apply to this user. e.g. { "tier" => "development" }
|
30
|
-
|
57
|
+
attr_reader :statsig_environment
|
58
|
+
def statsig_environment=(value)
|
59
|
+
value_changed()
|
60
|
+
@statsig_environment = value
|
61
|
+
end
|
31
62
|
|
32
63
|
# Any Custom IDs to associated with the user. (See https://docs.statsig.com/guides/experiment-on-custom-id-types)
|
33
|
-
|
64
|
+
attr_reader :custom_ids
|
65
|
+
def custom_ids=(value)
|
66
|
+
value_changed()
|
67
|
+
@custom_ids = value
|
68
|
+
end
|
34
69
|
|
35
70
|
# Any value you wish to use in evaluation, but do not want logged with events, can be stored in this field.
|
36
|
-
|
71
|
+
attr_reader :private_attributes
|
72
|
+
def private_attributes=(value)
|
73
|
+
value_changed()
|
74
|
+
@private_attributes = value
|
75
|
+
end
|
37
76
|
|
38
77
|
def custom
|
39
78
|
@custom
|
@@ -41,9 +80,12 @@ class StatsigUser
|
|
41
80
|
|
42
81
|
# Any custom fields for this user. Evaluated against the Custom criteria. (https://docs.statsig.com/feature-gates/conditions#custom)
|
43
82
|
def custom=(value)
|
83
|
+
value_changed()
|
44
84
|
@custom = value.is_a?(Hash) ? value : Hash.new
|
45
85
|
end
|
46
86
|
|
87
|
+
attr_accessor :memo_timeout
|
88
|
+
|
47
89
|
def initialize(user_hash)
|
48
90
|
the_hash = user_hash
|
49
91
|
begin
|
@@ -63,6 +105,9 @@ class StatsigUser
|
|
63
105
|
@private_attributes = from_hash(the_hash, [:private_attributes, :privateAttributes], Hash)
|
64
106
|
@custom_ids = from_hash(the_hash, [:custom_ids, :customIDs], Hash)
|
65
107
|
@statsig_environment = from_hash(the_hash, [:statsig_environment, :statsigEnvironment], Hash)
|
108
|
+
@memo = {}
|
109
|
+
@dirty = true
|
110
|
+
@memo_timeout = 2
|
66
111
|
end
|
67
112
|
|
68
113
|
def serialize(for_logging)
|
@@ -138,7 +183,45 @@ class StatsigUser
|
|
138
183
|
@user_id
|
139
184
|
end
|
140
185
|
|
186
|
+
def get_memo
|
187
|
+
current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
188
|
+
|
189
|
+
if @dirty || current_time - (@memo_access_time ||= current_time) > @memo_timeout
|
190
|
+
if @memo.size() > 0
|
191
|
+
@memo.clear
|
192
|
+
end
|
193
|
+
@dirty = false
|
194
|
+
@memo_access_time = current_time
|
195
|
+
end
|
196
|
+
|
197
|
+
@memo
|
198
|
+
end
|
199
|
+
|
200
|
+
def clear_memo
|
201
|
+
@memo.clear
|
202
|
+
@dirty = false
|
203
|
+
@memo_access_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
204
|
+
end
|
205
|
+
|
206
|
+
def user_key
|
207
|
+
unless !@dirty && defined? @_user_key
|
208
|
+
custom_id_key = ''
|
209
|
+
if self.custom_ids.is_a?(Hash)
|
210
|
+
custom_id_key = self.custom_ids.values.join(',')
|
211
|
+
end
|
212
|
+
user_id_key = ''
|
213
|
+
unless self.user_id.nil?
|
214
|
+
user_id_key = self.user_id.to_s
|
215
|
+
end
|
216
|
+
@_user_key = user_id_key + ',' + custom_id_key.to_s
|
217
|
+
end
|
218
|
+
@_user_key
|
219
|
+
end
|
220
|
+
|
141
221
|
private
|
222
|
+
def value_changed
|
223
|
+
@dirty = true
|
224
|
+
end
|
142
225
|
|
143
226
|
# Pulls fields from the user hash via Symbols and Strings
|
144
227
|
def from_hash(user_hash, keys, type)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statsig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.33.
|
4
|
+
version: 1.33.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Statsig, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -310,6 +310,7 @@ files:
|
|
310
310
|
- lib/interfaces/data_store.rb
|
311
311
|
- lib/interfaces/user_persistent_storage.rb
|
312
312
|
- lib/layer.rb
|
313
|
+
- lib/memo.rb
|
313
314
|
- lib/network.rb
|
314
315
|
- lib/spec_store.rb
|
315
316
|
- lib/statsig.rb
|