statsig 2.2.2 → 2.3.0
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 +4 -4
- data/lib/config_result.rb +7 -1
- data/lib/evaluator.rb +2 -0
- data/lib/hash_utils.rb +45 -0
- data/lib/sdk_configs.rb +37 -0
- data/lib/spec_store.rb +4 -1
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +5 -3
- data/lib/statsig_logger.rb +102 -2
- data/lib/ttl_set.rb +36 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e52cea71e52cabc3730c8e9740d7208f1ba45378354a8ef1bd0639c1cfbf9a6
|
4
|
+
data.tar.gz: a2e74a859c70adace695bff8040bcf23e2e981cb35bcfeb5fa143369ceccd90f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b472daa96f6a6b6cf43874555c4421d6374c387d3f14683b0d8e7959ffc5b4d2a04d9f72144854ae54e4bcdfd87187e8542ed7b0abd2ac97605fb7e585f7ac0b
|
7
|
+
data.tar.gz: e1a9505ea5fc5fb27ea9f3a82056932538dc146bdeeaff08d3179bbfffac5cfa3abaf7838c986350b15d0bd1ed9f2392238beb1f5d139658b770659497dd3eb5
|
data/lib/config_result.rb
CHANGED
@@ -18,6 +18,8 @@ module Statsig
|
|
18
18
|
attr_accessor :disable_exposures
|
19
19
|
attr_accessor :config_version
|
20
20
|
attr_accessor :include_local_overrides
|
21
|
+
attr_accessor :forward_all_exposures
|
22
|
+
attr_accessor :sampling_rate
|
21
23
|
|
22
24
|
def initialize(
|
23
25
|
name:,
|
@@ -35,7 +37,9 @@ module Statsig
|
|
35
37
|
disable_evaluation_details: false,
|
36
38
|
disable_exposures: false,
|
37
39
|
config_version: nil,
|
38
|
-
include_local_overrides: true
|
40
|
+
include_local_overrides: true,
|
41
|
+
forward_all_exposures: false,
|
42
|
+
sampling_rate: nil
|
39
43
|
)
|
40
44
|
@name = name
|
41
45
|
@gate_value = gate_value
|
@@ -54,6 +58,8 @@ module Statsig
|
|
54
58
|
@disable_exposures = disable_exposures
|
55
59
|
@config_version = config_version
|
56
60
|
@include_local_overrides = include_local_overrides
|
61
|
+
@forward_all_exposures = forward_all_exposures
|
62
|
+
@sampling_rate = sampling_rate
|
57
63
|
end
|
58
64
|
|
59
65
|
def self.from_user_persisted_values(config_name, user_persisted_values)
|
data/lib/evaluator.rb
CHANGED
@@ -325,6 +325,7 @@ module Statsig
|
|
325
325
|
end_result.id_type = config[:idType]
|
326
326
|
end_result.target_app_ids = config[:targetAppIDs]
|
327
327
|
end_result.gate_value = did_pass
|
328
|
+
end_result.forward_all_exposures = config[:forwardAllExposures]
|
328
329
|
if config[:entity] == Const::TYPE_FEATURE_GATE
|
329
330
|
end_result.gate_value = did_pass ? rule[:returnValue] == true : config[:defaultValue] == true
|
330
331
|
end
|
@@ -340,6 +341,7 @@ module Statsig
|
|
340
341
|
end_result.group_name = rule[:groupName]
|
341
342
|
end_result.is_experiment_group = rule[:isExperimentGroup] == true
|
342
343
|
end_result.rule_id = rule[:id]
|
344
|
+
end_result.sampling_rate = rule[:samplingRate]
|
343
345
|
end
|
344
346
|
|
345
347
|
unless end_result.disable_evaluation_details
|
data/lib/hash_utils.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
TWO_TO_THE_63 = 1 << 63
|
5
|
+
TWO_TO_THE_64 = 1 << 64
|
2
6
|
module Statsig
|
3
7
|
class HashUtils
|
4
8
|
def self.djb2(input_str)
|
@@ -32,5 +36,46 @@ module Statsig
|
|
32
36
|
end
|
33
37
|
return dictionary
|
34
38
|
end
|
39
|
+
|
40
|
+
def self.bigquery_hash(string)
|
41
|
+
digest = Digest::SHA256.digest(string)
|
42
|
+
num = digest[0...8].unpack('Q>')[0]
|
43
|
+
|
44
|
+
if num >= TWO_TO_THE_63
|
45
|
+
num - TWO_TO_THE_64
|
46
|
+
else
|
47
|
+
num
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.is_hash_in_sampling_rate(key, sampling_rate)
|
52
|
+
hash_key = bigquery_hash(key)
|
53
|
+
hash_key % sampling_rate == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.compute_dedupe_key_for_gate(gate_name, rule_id, value, user_id, custom_ids = nil)
|
57
|
+
user_key = compute_user_key(user_id, custom_ids)
|
58
|
+
"n:#{gate_name};u:#{user_key}r:#{rule_id};v:#{value}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.compute_dedupe_key_for_config(config_name, rule_id, user_id, custom_ids = nil)
|
62
|
+
user_key = compute_user_key(user_id, custom_ids)
|
63
|
+
"n:#{config_name};u:#{user_key}r:#{rule_id}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.compute_dedupe_key_for_layer(layer_name, experiment_name, parameter_name, rule_id, user_id, custom_ids = nil)
|
67
|
+
user_key = compute_user_key(user_id, custom_ids)
|
68
|
+
"n:#{layer_name};e:#{experiment_name};p:#{parameter_name};u:#{user_key}r:#{rule_id}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.compute_user_key(user_id, custom_ids = nil)
|
72
|
+
user_key = "u:#{user_id};"
|
73
|
+
if custom_ids
|
74
|
+
custom_ids.each do |k, v|
|
75
|
+
user_key += "#{k}:#{v};"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
user_key
|
79
|
+
end
|
35
80
|
end
|
36
81
|
end
|
data/lib/sdk_configs.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'concurrent-ruby'
|
2
|
+
|
3
|
+
module Statsig
|
4
|
+
class SDKConfigs
|
5
|
+
def initialize
|
6
|
+
@configs = Concurrent::Hash.new
|
7
|
+
@flags = Concurrent::Hash.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_flags(new_flags)
|
11
|
+
@flags = new_flags || Concurrent::Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_configs(new_configs)
|
15
|
+
@configs = new_configs || Concurrent::Hash.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def on(flag)
|
19
|
+
@flags[flag.to_sym] == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_config_num_value(config)
|
23
|
+
value = @configs[config.to_sym]
|
24
|
+
value.is_a?(Numeric) ? value.to_f : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_config_string_value(config)
|
28
|
+
value = @configs[config.to_sym]
|
29
|
+
value.is_a?(String) ? value : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_config_int_value(config)
|
33
|
+
value = @configs[config.to_sym]
|
34
|
+
value.is_a?(Integer) ? value : nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/spec_store.rb
CHANGED
@@ -20,7 +20,7 @@ module Statsig
|
|
20
20
|
attr_accessor :hashed_sdk_keys_to_app_ids
|
21
21
|
attr_accessor :unsupported_configs
|
22
22
|
|
23
|
-
def initialize(network, options, error_callback, diagnostics, error_boundary, logger, secret_key)
|
23
|
+
def initialize(network, options, error_callback, diagnostics, error_boundary, logger, secret_key, sdk_config)
|
24
24
|
@init_reason = EvaluationReason::UNINITIALIZED
|
25
25
|
@network = network
|
26
26
|
@options = options
|
@@ -43,6 +43,7 @@ module Statsig
|
|
43
43
|
@logger = logger
|
44
44
|
@secret_key = secret_key
|
45
45
|
@unsupported_configs = Set.new
|
46
|
+
@sdk_configs = sdk_config
|
46
47
|
|
47
48
|
startTime = (Time.now.to_f * 1000).to_i
|
48
49
|
|
@@ -342,6 +343,8 @@ module Statsig
|
|
342
343
|
@experiment_to_layer = specs_json[:experiment_to_layer]
|
343
344
|
@sdk_keys_to_app_ids = specs_json[:sdk_keys_to_app_ids] || {}
|
344
345
|
@hashed_sdk_keys_to_app_ids = specs_json[:hashed_sdk_keys_to_app_ids] || {}
|
346
|
+
@sdk_configs.set_flags(specs_json[:sdk_flags])
|
347
|
+
@sdk_configs.set_configs(specs_json[:sdk_configs])
|
345
348
|
|
346
349
|
unless from_adapter
|
347
350
|
save_rulesets_to_storage_adapter(specs_string)
|
data/lib/statsig.rb
CHANGED
data/lib/statsig_driver.rb
CHANGED
@@ -13,6 +13,7 @@ require 'error_boundary'
|
|
13
13
|
require 'layer'
|
14
14
|
require 'memo'
|
15
15
|
require 'diagnostics'
|
16
|
+
require 'sdk_configs'
|
16
17
|
|
17
18
|
class StatsigDriver
|
18
19
|
|
@@ -27,15 +28,16 @@ class StatsigDriver
|
|
27
28
|
|
28
29
|
@err_boundary = Statsig::ErrorBoundary.new(secret_key, !options.nil? && options.local_mode)
|
29
30
|
@err_boundary.capture(caller: __method__) do
|
30
|
-
@diagnostics = Statsig::Diagnostics.new
|
31
|
+
@diagnostics = Statsig::Diagnostics.new
|
32
|
+
@sdk_configs = Statsig::SDKConfigs.new
|
31
33
|
tracker = @diagnostics.track('initialize', 'overall')
|
32
34
|
@options = options || StatsigOptions.new
|
33
35
|
@shutdown = false
|
34
36
|
@secret_key = secret_key
|
35
37
|
@net = Statsig::Network.new(secret_key, @options)
|
36
|
-
@logger = Statsig::StatsigLogger.new(@net, @options, @err_boundary)
|
38
|
+
@logger = Statsig::StatsigLogger.new(@net, @options, @err_boundary, @sdk_configs)
|
37
39
|
@persistent_storage_utils = Statsig::UserPersistentStorageUtils.new(@options)
|
38
|
-
@store = Statsig::SpecStore.new(@net, @options, error_callback, @diagnostics, @err_boundary, @logger, secret_key)
|
40
|
+
@store = Statsig::SpecStore.new(@net, @options, error_callback, @diagnostics, @err_boundary, @logger, secret_key, @sdk_configs)
|
39
41
|
@evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils)
|
40
42
|
tracker.end(success: true)
|
41
43
|
|
data/lib/statsig_logger.rb
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
require 'constants'
|
2
2
|
require 'statsig_event'
|
3
|
+
require 'ttl_set'
|
3
4
|
require 'concurrent-ruby'
|
5
|
+
require 'hash_utils'
|
4
6
|
|
5
7
|
$gate_exposure_event = 'statsig::gate_exposure'
|
6
8
|
$config_exposure_event = 'statsig::config_exposure'
|
7
9
|
$layer_exposure_event = 'statsig::layer_exposure'
|
8
10
|
$diagnostics_event = 'statsig::diagnostics'
|
9
11
|
$ignored_metadata_keys = [:serverTime, :configSyncTime, :initTime, :reason]
|
12
|
+
|
13
|
+
class EntityType
|
14
|
+
GATE = "gate"
|
15
|
+
CONFIG = "config"
|
16
|
+
LAYER = "layer"
|
17
|
+
end
|
18
|
+
|
10
19
|
module Statsig
|
11
20
|
class StatsigLogger
|
12
|
-
def initialize(network, options, error_boundary)
|
21
|
+
def initialize(network, options, error_boundary, sdk_configs)
|
13
22
|
@network = network
|
14
23
|
@events = []
|
15
24
|
@options = options
|
@@ -25,10 +34,12 @@ module Statsig
|
|
25
34
|
|
26
35
|
@error_boundary = error_boundary
|
27
36
|
@background_flush = periodic_flush
|
28
|
-
@deduper = Concurrent::Set.new
|
37
|
+
@deduper = Concurrent::Set.new
|
38
|
+
@sampling_key_set = Statsig::TTLSet.new
|
29
39
|
@interval = 0
|
30
40
|
@flush_mutex = Mutex.new
|
31
41
|
@debug_info = nil
|
42
|
+
@sdk_configs = sdk_configs
|
32
43
|
end
|
33
44
|
|
34
45
|
def log_event(event)
|
@@ -39,6 +50,8 @@ module Statsig
|
|
39
50
|
end
|
40
51
|
|
41
52
|
def log_gate_exposure(user, result, context = nil)
|
53
|
+
should_log, logged_sampling_rate, shadow_logged = determine_sampling(EntityType::GATE, result.name, result, user)
|
54
|
+
return unless should_log
|
42
55
|
event = StatsigEvent.new($gate_exposure_event)
|
43
56
|
event.user = user
|
44
57
|
metadata = {
|
@@ -54,15 +67,19 @@ module Statsig
|
|
54
67
|
end
|
55
68
|
return false if not is_unique_exposure(user, $gate_exposure_event, metadata)
|
56
69
|
event.metadata = metadata
|
70
|
+
event.statsig_metadata = {}
|
57
71
|
|
58
72
|
event.secondary_exposures = result.secondary_exposures.is_a?(Array) ? result.secondary_exposures : []
|
59
73
|
|
60
74
|
safe_add_eval_details(result.evaluation_details, event)
|
61
75
|
safe_add_exposure_context(context, event)
|
76
|
+
safe_add_sampling_metadata(event, logged_sampling_rate, shadow_logged)
|
62
77
|
log_event(event)
|
63
78
|
end
|
64
79
|
|
65
80
|
def log_config_exposure(user, result, context = nil)
|
81
|
+
should_log, logged_sampling_rate, shadow_logged = determine_sampling(EntityType::CONFIG, result.name, result, user)
|
82
|
+
return unless should_log
|
66
83
|
event = StatsigEvent.new($config_exposure_event)
|
67
84
|
event.user = user
|
68
85
|
metadata = {
|
@@ -79,9 +96,11 @@ module Statsig
|
|
79
96
|
return false if not is_unique_exposure(user, $config_exposure_event, metadata)
|
80
97
|
event.metadata = metadata
|
81
98
|
event.secondary_exposures = result.secondary_exposures.is_a?(Array) ? result.secondary_exposures : []
|
99
|
+
event.statsig_metadata = {}
|
82
100
|
|
83
101
|
safe_add_eval_details(result.evaluation_details, event)
|
84
102
|
safe_add_exposure_context(context, event)
|
103
|
+
safe_add_sampling_metadata(event, logged_sampling_rate, shadow_logged)
|
85
104
|
log_event(event)
|
86
105
|
end
|
87
106
|
|
@@ -94,6 +113,9 @@ module Statsig
|
|
94
113
|
exposures = config_evaluation.secondary_exposures
|
95
114
|
end
|
96
115
|
|
116
|
+
should_log, logged_sampling_rate, shadow_logged = determine_sampling(EntityType::LAYER, config_evaluation.name, config_evaluation, user, allocated_experiment, parameter_name)
|
117
|
+
return unless should_log
|
118
|
+
|
97
119
|
event = StatsigEvent.new($layer_exposure_event)
|
98
120
|
event.user = user
|
99
121
|
metadata = {
|
@@ -112,9 +134,11 @@ module Statsig
|
|
112
134
|
return false unless is_unique_exposure(user, $layer_exposure_event, metadata)
|
113
135
|
event.metadata = metadata
|
114
136
|
event.secondary_exposures = exposures.is_a?(Array) ? exposures : []
|
137
|
+
event.statsig_metadata = {}
|
115
138
|
|
116
139
|
safe_add_eval_details(config_evaluation.evaluation_details, event)
|
117
140
|
safe_add_exposure_context(context, event)
|
141
|
+
safe_add_sampling_metadata(event, logged_sampling_rate, shadow_logged)
|
118
142
|
log_event(event)
|
119
143
|
end
|
120
144
|
|
@@ -150,6 +174,7 @@ module Statsig
|
|
150
174
|
|
151
175
|
def shutdown
|
152
176
|
@background_flush&.exit
|
177
|
+
@sampling_key_set.shutdown
|
153
178
|
@logging_pool.shutdown
|
154
179
|
@logging_pool.wait_for_termination(timeout = 3)
|
155
180
|
flush
|
@@ -207,6 +232,18 @@ module Statsig
|
|
207
232
|
end
|
208
233
|
end
|
209
234
|
|
235
|
+
def safe_add_sampling_metadata(event, logged_sampling_rate = nil, shadow_logged = nil)
|
236
|
+
unless logged_sampling_rate.nil?
|
237
|
+
event.statsig_metadata["samplingRate"] = logged_sampling_rate
|
238
|
+
end
|
239
|
+
|
240
|
+
unless shadow_logged.nil?
|
241
|
+
event.statsig_metadata["shadowLogged"] = shadow_logged
|
242
|
+
end
|
243
|
+
|
244
|
+
event.statsig_metadata["samplingMode"] = @sdk_configs.get_config_string_value("sampling_mode")
|
245
|
+
end
|
246
|
+
|
210
247
|
def is_unique_exposure(user, event_name, metadata)
|
211
248
|
return true if user.nil?
|
212
249
|
@deduper.clear if @deduper.size > 10000
|
@@ -224,5 +261,68 @@ module Statsig
|
|
224
261
|
@deduper.add(key)
|
225
262
|
true
|
226
263
|
end
|
264
|
+
|
265
|
+
def determine_sampling(type, name, result, user, exp_name = "", param_name = "")
|
266
|
+
begin
|
267
|
+
shadow_should_log, logged_sampling_rate = true, nil
|
268
|
+
env = @options.environment&.dig(:tier)
|
269
|
+
sampling_mode = @sdk_configs.get_config_string_value("sampling_mode")
|
270
|
+
special_case_sampling_rate = @sdk_configs.get_config_int_value("special_case_sampling_rate")
|
271
|
+
special_case_rules = ["disabled", "default", ""]
|
272
|
+
|
273
|
+
if sampling_mode.nil? || sampling_mode == "none" || env != "production"
|
274
|
+
return true, nil, nil
|
275
|
+
end
|
276
|
+
|
277
|
+
return true, nil, nil if result.forward_all_exposures
|
278
|
+
return true, nil, nil if result.rule_id.end_with?(":override", ":id_override")
|
279
|
+
|
280
|
+
sampling_set_key = "#{name}_#{result.rule_id}"
|
281
|
+
unless @sampling_key_set.contains?(sampling_set_key)
|
282
|
+
@sampling_key_set.add(sampling_set_key)
|
283
|
+
return true, nil, nil
|
284
|
+
end
|
285
|
+
|
286
|
+
should_sample = result.sampling_rate || special_case_rules.include?(result.rule_id)
|
287
|
+
unless should_sample
|
288
|
+
return true, nil, nil
|
289
|
+
end
|
290
|
+
|
291
|
+
exposure_key = ""
|
292
|
+
case type
|
293
|
+
when EntityType::GATE
|
294
|
+
exposure_key = Statsig::HashUtils.compute_dedupe_key_for_gate(name, result.rule_id, result.gate_value, user.user_id, user.custom_ids)
|
295
|
+
when EntityType::CONFIG
|
296
|
+
exposure_key = Statsig::HashUtils.compute_dedupe_key_for_config(name, result.rule_id, user.user_id, user.custom_ids)
|
297
|
+
when EntityType::LAYER
|
298
|
+
exposure_key = Statsig::HashUtils.compute_dedupe_key_for_layer(name, exp_name, param_name, result.rule_id, user.user_id, user.custom_ids)
|
299
|
+
end
|
300
|
+
|
301
|
+
if result.sampling_rate
|
302
|
+
shadow_should_log = Statsig::HashUtils.is_hash_in_sampling_rate(exposure_key, result.sampling_rate)
|
303
|
+
logged_sampling_rate = result.sampling_rate
|
304
|
+
elsif special_case_rules.include?(result.rule_id) && special_case_sampling_rate
|
305
|
+
shadow_should_log = Statsig::HashUtils.is_hash_in_sampling_rate(exposure_key, special_case_sampling_rate)
|
306
|
+
logged_sampling_rate = special_case_sampling_rate
|
307
|
+
end
|
308
|
+
|
309
|
+
shadow_logged = if logged_sampling_rate.nil?
|
310
|
+
nil
|
311
|
+
else
|
312
|
+
shadow_should_log ? "logged" : "dropped"
|
313
|
+
end
|
314
|
+
if sampling_mode == "on"
|
315
|
+
return shadow_should_log, logged_sampling_rate, shadow_logged
|
316
|
+
elsif sampling_mode == "shadow"
|
317
|
+
return true, logged_sampling_rate, shadow_logged
|
318
|
+
end
|
319
|
+
|
320
|
+
return true, nil, nil
|
321
|
+
rescue => e
|
322
|
+
@error_boundary.log_exception(e, "__determine_sampling")
|
323
|
+
return true, nil, nil
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
227
327
|
end
|
228
328
|
end
|
data/lib/ttl_set.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'concurrent-ruby'
|
2
|
+
|
3
|
+
RESET_INTERVAL = 60
|
4
|
+
|
5
|
+
module Statsig
|
6
|
+
class TTLSet
|
7
|
+
def initialize
|
8
|
+
@store = Concurrent::Set.new
|
9
|
+
@reset_interval = RESET_INTERVAL
|
10
|
+
@background_reset = periodic_reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(key)
|
14
|
+
@store.add(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def contains?(key)
|
18
|
+
@store.include?(key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def shutdown
|
22
|
+
@background_reset&.exit
|
23
|
+
end
|
24
|
+
|
25
|
+
def periodic_reset
|
26
|
+
Thread.new do
|
27
|
+
loop do
|
28
|
+
sleep @reset_interval
|
29
|
+
@store.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
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: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Statsig, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -340,6 +340,7 @@ files:
|
|
340
340
|
- lib/layer.rb
|
341
341
|
- lib/memo.rb
|
342
342
|
- lib/network.rb
|
343
|
+
- lib/sdk_configs.rb
|
343
344
|
- lib/spec_store.rb
|
344
345
|
- lib/statsig.rb
|
345
346
|
- lib/statsig_driver.rb
|
@@ -348,6 +349,7 @@ files:
|
|
348
349
|
- lib/statsig_logger.rb
|
349
350
|
- lib/statsig_options.rb
|
350
351
|
- lib/statsig_user.rb
|
352
|
+
- lib/ttl_set.rb
|
351
353
|
- lib/ua_parser.rb
|
352
354
|
- lib/user_persistent_storage_utils.rb
|
353
355
|
homepage: https://rubygems.org/gems/statsig
|