statsig 2.1.0 → 2.2.1
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 +4 -1
- data/lib/evaluator.rb +6 -2
- data/lib/spec_store.rb +63 -40
- data/lib/statsig.rb +12 -5
- data/lib/statsig_driver.rb +8 -6
- data/lib/statsig_logger.rb +21 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b9e465d382f7e7e2a4640eaf549bc3e882b0aac3c2333c9874fc6855a6af1cc
|
4
|
+
data.tar.gz: 844845547c6b1914908e38438dc932486e3ea5740ef32c3b59eb1cc67a4274ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a61ad75521667fd2dc1d1a2df7f653e770b0ce111b66302c16302d4b59394719ac2f3617968adb4dce303bbd3791ecd7aa3da7698ad9cf448dc522bc3564ac00
|
7
|
+
data.tar.gz: 79a119ba9af5c7c2da630ce9501596dc0e230adeff7e8c022b5904e7c2e5e5475efc3a1ee968d23be105940e4ab60e3da1154d3ce1ec8e9dd8ef2547f9be1a84
|
data/lib/config_result.rb
CHANGED
@@ -16,6 +16,7 @@ module Statsig
|
|
16
16
|
attr_accessor :target_app_ids
|
17
17
|
attr_accessor :disable_evaluation_details
|
18
18
|
attr_accessor :disable_exposures
|
19
|
+
attr_accessor :config_version
|
19
20
|
|
20
21
|
def initialize(
|
21
22
|
name:,
|
@@ -31,7 +32,8 @@ module Statsig
|
|
31
32
|
id_type: nil,
|
32
33
|
target_app_ids: nil,
|
33
34
|
disable_evaluation_details: false,
|
34
|
-
disable_exposures: false
|
35
|
+
disable_exposures: false,
|
36
|
+
config_version: nil
|
35
37
|
)
|
36
38
|
@name = name
|
37
39
|
@gate_value = gate_value
|
@@ -48,6 +50,7 @@ module Statsig
|
|
48
50
|
@target_app_ids = target_app_ids
|
49
51
|
@disable_evaluation_details = disable_evaluation_details
|
50
52
|
@disable_exposures = disable_exposures
|
53
|
+
@config_version = config_version
|
51
54
|
end
|
52
55
|
|
53
56
|
def self.from_user_persisted_values(config_name, user_persisted_values)
|
data/lib/evaluator.rb
CHANGED
@@ -324,7 +324,11 @@ module Statsig
|
|
324
324
|
def finalize_eval_result(config, end_result, did_pass:, rule:, is_nested: false)
|
325
325
|
end_result.id_type = config[:idType]
|
326
326
|
end_result.target_app_ids = config[:targetAppIDs]
|
327
|
-
end_result.gate_value = did_pass
|
327
|
+
end_result.gate_value = did_pass
|
328
|
+
if config[:entity] == Const::TYPE_FEATURE_GATE
|
329
|
+
end_result.gate_value = did_pass ? rule[:returnValue] == true : config[:defaultValue] == true
|
330
|
+
end
|
331
|
+
end_result.config_version = config[:version]
|
328
332
|
|
329
333
|
if rule.nil?
|
330
334
|
end_result.json_value = config[:defaultValue]
|
@@ -691,7 +695,7 @@ module Statsig
|
|
691
695
|
pass_percentage = rule[:passPercentage]
|
692
696
|
return true if pass_percentage == 100.0
|
693
697
|
return false if pass_percentage == 0.0
|
694
|
-
|
698
|
+
|
695
699
|
unit_id = user.get_unit_id(rule[:idType]) || Const::EMPTY_STR
|
696
700
|
rule_salt = rule[:salt] || rule[:id] || Const::EMPTY_STR
|
697
701
|
hash = compute_user_hash("#{config_salt}.#{rule_salt}.#{unit_id}")
|
data/lib/spec_store.rb
CHANGED
@@ -44,6 +44,8 @@ module Statsig
|
|
44
44
|
@secret_key = secret_key
|
45
45
|
@unsupported_configs = Set.new
|
46
46
|
|
47
|
+
startTime = (Time.now.to_f * 1000).to_i
|
48
|
+
|
47
49
|
@id_list_thread_pool = Concurrent::FixedThreadPool.new(
|
48
50
|
options.idlist_threadpool_size,
|
49
51
|
name: 'statsig-idlist',
|
@@ -57,7 +59,7 @@ module Statsig
|
|
57
59
|
else
|
58
60
|
tracker = @diagnostics.track('initialize','bootstrap', 'process')
|
59
61
|
begin
|
60
|
-
if process_specs(options.bootstrap_values)
|
62
|
+
if process_specs(options.bootstrap_values).nil?
|
61
63
|
@init_reason = EvaluationReason::BOOTSTRAP
|
62
64
|
end
|
63
65
|
rescue StandardError
|
@@ -68,13 +70,15 @@ module Statsig
|
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
73
|
+
failure_details = nil
|
74
|
+
|
71
75
|
unless @options.data_store.nil?
|
72
76
|
@options.data_store.init
|
73
|
-
load_config_specs_from_storage_adapter('initialize')
|
77
|
+
failure_details = load_config_specs_from_storage_adapter('initialize')
|
74
78
|
end
|
75
79
|
|
76
80
|
if @init_reason == EvaluationReason::UNINITIALIZED
|
77
|
-
download_config_specs('initialize')
|
81
|
+
failure_details = download_config_specs('initialize')
|
78
82
|
end
|
79
83
|
|
80
84
|
@initial_config_sync_time = @last_config_sync_time == 0 ? -1 : @last_config_sync_time
|
@@ -86,12 +90,18 @@ module Statsig
|
|
86
90
|
|
87
91
|
@config_sync_thread = spawn_sync_config_specs_thread
|
88
92
|
@id_lists_sync_thread = spawn_sync_id_lists_thread
|
93
|
+
endTime = (Time.now.to_f * 1000).to_i
|
94
|
+
@initialization_details = {duration: endTime - startTime, isSDKReady: true, configSpecReady: @init_reason != EvaluationReason::UNINITIALIZED, failureDetails: failure_details}
|
89
95
|
end
|
90
96
|
|
91
97
|
def is_ready_for_checks
|
92
98
|
@last_config_sync_time != 0
|
93
99
|
end
|
94
100
|
|
101
|
+
def get_initialization_details
|
102
|
+
@initialization_details
|
103
|
+
end
|
104
|
+
|
95
105
|
def shutdown
|
96
106
|
@config_sync_thread&.exit
|
97
107
|
@id_lists_sync_thread&.exit
|
@@ -202,13 +212,19 @@ module Statsig
|
|
202
212
|
return if cached_values.nil?
|
203
213
|
|
204
214
|
tracker = @diagnostics.track(context, 'data_store_config_specs', 'process')
|
205
|
-
process_specs(cached_values, from_adapter: true)
|
206
|
-
|
207
|
-
|
215
|
+
failure_details = process_specs(cached_values, from_adapter: true)
|
216
|
+
if failure_details.nil?
|
217
|
+
@init_reason = EvaluationReason::DATA_ADAPTER
|
218
|
+
tracker.end(success: true)
|
219
|
+
else
|
220
|
+
tracker.end(success: false)
|
221
|
+
return download_config_specs(context)
|
222
|
+
end
|
223
|
+
return failure_details
|
208
224
|
rescue StandardError
|
209
225
|
# Fallback to network
|
210
226
|
tracker.end(success: false)
|
211
|
-
download_config_specs(context)
|
227
|
+
return download_config_specs(context)
|
212
228
|
end
|
213
229
|
|
214
230
|
def save_rulesets_to_storage_adapter(rulesets_string)
|
@@ -253,18 +269,21 @@ module Statsig
|
|
253
269
|
tracker = @diagnostics.track(context, 'download_config_specs', 'network_request')
|
254
270
|
|
255
271
|
error = nil
|
272
|
+
failure_details = nil
|
256
273
|
begin
|
257
274
|
response, e = @network.download_config_specs(@last_config_sync_time)
|
258
275
|
code = response&.status.to_i
|
259
276
|
if e.is_a? NetworkError
|
260
277
|
code = e.http_code
|
278
|
+
failure_details = {statusCode: code, exception: e, reason: "CONFIG_SPECS_NETWORK_ERROR"}
|
261
279
|
end
|
262
280
|
tracker.end(statusCode: code, success: e.nil?, sdkRegion: response&.headers&.[]('X-Statsig-Region'))
|
263
281
|
|
264
282
|
if e.nil?
|
265
283
|
unless response.nil?
|
266
284
|
tracker = @diagnostics.track(context, 'download_config_specs', 'process')
|
267
|
-
|
285
|
+
failure_details = process_specs(response.body.to_s)
|
286
|
+
if failure_details.nil?
|
268
287
|
@init_reason = EvaluationReason::NETWORK
|
269
288
|
end
|
270
289
|
tracker.end(success: @init_reason == EvaluationReason::NETWORK)
|
@@ -274,59 +293,63 @@ module Statsig
|
|
274
293
|
@last_config_sync_time)
|
275
294
|
end
|
276
295
|
end
|
277
|
-
|
278
|
-
nil
|
279
296
|
else
|
280
297
|
error = e
|
281
298
|
end
|
282
299
|
rescue StandardError => e
|
300
|
+
failure_details = {exception: e, reason: "INTERNAL_ERROR"}
|
283
301
|
error = e
|
284
302
|
end
|
285
303
|
|
286
304
|
@error_callback.call(error) unless error.nil? or @error_callback.nil?
|
305
|
+
return failure_details
|
287
306
|
end
|
288
307
|
|
289
308
|
def process_specs(specs_string, from_adapter: false)
|
290
309
|
if specs_string.nil?
|
291
|
-
return
|
310
|
+
return {reason: "EMPTY_SPEC"}
|
292
311
|
end
|
293
312
|
|
294
|
-
|
295
|
-
|
313
|
+
begin
|
314
|
+
specs_json = JSON.parse(specs_string, { symbolize_names: true })
|
315
|
+
return {reason: "PARSE_RESPONSE_ERROR"} unless specs_json.is_a? Hash
|
296
316
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
317
|
+
hashed_sdk_key_used = specs_json[:hashed_sdk_key_used]
|
318
|
+
unless hashed_sdk_key_used.nil? or hashed_sdk_key_used == Statsig::HashUtils.djb2(@secret_key)
|
319
|
+
err_boundary.log_exception(Statsig::InvalidSDKKeyResponse.new)
|
320
|
+
return {reason: "PARSE_RESPONSE_ERROR"}
|
321
|
+
end
|
302
322
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
323
|
+
new_specs_sync_time = specs_json[:time]
|
324
|
+
if new_specs_sync_time.nil? \
|
325
|
+
|| new_specs_sync_time < @last_config_sync_time \
|
326
|
+
|| specs_json[:has_updates] != true \
|
327
|
+
|| specs_json[:feature_gates].nil? \
|
328
|
+
|| specs_json[:dynamic_configs].nil? \
|
329
|
+
|| specs_json[:layer_configs].nil?
|
330
|
+
return {reason: "PARSE_RESPONSE_ERROR"}
|
331
|
+
end
|
312
332
|
|
313
|
-
|
314
|
-
|
333
|
+
@last_config_sync_time = new_specs_sync_time
|
334
|
+
@unsupported_configs.clear
|
315
335
|
|
316
|
-
|
336
|
+
specs_json[:diagnostics]&.each { |key, value| @diagnostics.sample_rates[key.to_s] = value }
|
317
337
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
338
|
+
@gates = specs_json[:feature_gates]
|
339
|
+
@configs = specs_json[:dynamic_configs]
|
340
|
+
@layers = specs_json[:layer_configs]
|
341
|
+
@condition_map = specs_json[:condition_map]
|
342
|
+
@experiment_to_layer = specs_json[:experiment_to_layer]
|
343
|
+
@sdk_keys_to_app_ids = specs_json[:sdk_keys_to_app_ids] || {}
|
344
|
+
@hashed_sdk_keys_to_app_ids = specs_json[:hashed_sdk_keys_to_app_ids] || {}
|
325
345
|
|
326
|
-
|
327
|
-
|
346
|
+
unless from_adapter
|
347
|
+
save_rulesets_to_storage_adapter(specs_string)
|
348
|
+
end
|
349
|
+
rescue StandardError => e
|
350
|
+
return {reason: "PARSE_RESPONSE_ERROR"}
|
328
351
|
end
|
329
|
-
|
352
|
+
nil
|
330
353
|
end
|
331
354
|
|
332
355
|
def get_id_lists_from_adapter(context)
|
data/lib/statsig.rb
CHANGED
@@ -20,6 +20,13 @@ module Statsig
|
|
20
20
|
@shared_instance = StatsigDriver.new(secret_key, options, error_callback)
|
21
21
|
end
|
22
22
|
|
23
|
+
def self.get_initialization_details
|
24
|
+
if not defined? @shared_instance or @shared_instance.nil?
|
25
|
+
return {duration: 0, isSDKReady: false, configSpecReady: false, failure_details: {exception: Statsig::UninitializedError.new, reason: 'INTERNAL_ERROR'}}
|
26
|
+
end
|
27
|
+
@shared_instance.get_initialization_details
|
28
|
+
end
|
29
|
+
|
23
30
|
class GetGateOptions
|
24
31
|
attr_accessor :disable_log_exposure, :skip_evaluation, :disable_evaluation_details
|
25
32
|
|
@@ -65,7 +72,7 @@ module Statsig
|
|
65
72
|
end
|
66
73
|
|
67
74
|
##
|
68
|
-
# @deprecated - use check_gate(user, gate, options)
|
75
|
+
# @deprecated - use check_gate(user, gate, options) with CheckGateOptions.new(disable_log_exposure: true) as options
|
69
76
|
# Gets the boolean result of a gate, evaluated against the given user.
|
70
77
|
#
|
71
78
|
# @param user A StatsigUser object used for the evaluation
|
@@ -108,7 +115,7 @@ module Statsig
|
|
108
115
|
end
|
109
116
|
|
110
117
|
##
|
111
|
-
# @deprecated - use get_config(user, config, options)
|
118
|
+
# @deprecated - use get_config(user, config, options) with GetConfigOptions.new(disable_log_exposure: true) as options
|
112
119
|
# Get the values of a dynamic config, evaluated against the given user.
|
113
120
|
#
|
114
121
|
# @param [StatsigUser] user A StatsigUser object used for the evaluation
|
@@ -152,7 +159,7 @@ module Statsig
|
|
152
159
|
end
|
153
160
|
|
154
161
|
##
|
155
|
-
# @deprecated - use get_experiment(user, experiment, options)
|
162
|
+
# @deprecated - use get_experiment(user, experiment, options) with GetExperimentOptions.new(disable_log_exposure: true) as options
|
156
163
|
# Get the values of an experiment, evaluated against the given user.
|
157
164
|
#
|
158
165
|
# @param [StatsigUser] user A StatsigUser object used for the evaluation
|
@@ -199,7 +206,7 @@ module Statsig
|
|
199
206
|
end
|
200
207
|
|
201
208
|
##
|
202
|
-
# @deprecated - use get_layer(user, gate, options)
|
209
|
+
# @deprecated - use get_layer(user, gate, options) with GetLayerOptions.new(disable_log_exposure: true) as options
|
203
210
|
# Get the values of a layer, evaluated against the given user.
|
204
211
|
#
|
205
212
|
# @param user A StatsigUser object used for the evaluation
|
@@ -363,7 +370,7 @@ module Statsig
|
|
363
370
|
def self.get_statsig_metadata
|
364
371
|
{
|
365
372
|
'sdkType' => 'ruby-server',
|
366
|
-
'sdkVersion' => '2.1
|
373
|
+
'sdkVersion' => '2.2.1',
|
367
374
|
'languageVersion' => RUBY_VERSION
|
368
375
|
}
|
369
376
|
end
|
data/lib/statsig_driver.rb
CHANGED
@@ -43,6 +43,10 @@ class StatsigDriver
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
def get_initialization_details
|
47
|
+
@store.get_initialization_details
|
48
|
+
end
|
49
|
+
|
46
50
|
def get_gate_impl(
|
47
51
|
user,
|
48
52
|
gate_name,
|
@@ -68,9 +72,7 @@ class StatsigDriver
|
|
68
72
|
@evaluator.check_gate(user, gate_name, res, ignore_local_overrides: ignore_local_overrides)
|
69
73
|
|
70
74
|
unless disable_log_exposure
|
71
|
-
@logger.log_gate_exposure(
|
72
|
-
user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details
|
73
|
-
)
|
75
|
+
@logger.log_gate_exposure(user, res)
|
74
76
|
end
|
75
77
|
|
76
78
|
FeatureGate.from_config_result(res)
|
@@ -109,7 +111,7 @@ class StatsigDriver
|
|
109
111
|
res = Statsig::ConfigResult.new(name: gate_name)
|
110
112
|
@evaluator.check_gate(user, gate_name, res)
|
111
113
|
context = { :is_manual_exposure => true }
|
112
|
-
@logger.log_gate_exposure(user,
|
114
|
+
@logger.log_gate_exposure(user, res, context)
|
113
115
|
end
|
114
116
|
end
|
115
117
|
|
@@ -150,7 +152,7 @@ class StatsigDriver
|
|
150
152
|
@evaluator.get_config(user, config_name, res)
|
151
153
|
|
152
154
|
context = { :is_manual_exposure => true }
|
153
|
-
@logger.log_config_exposure(user, res
|
155
|
+
@logger.log_config_exposure(user, res, context)
|
154
156
|
end
|
155
157
|
end
|
156
158
|
|
@@ -381,7 +383,7 @@ class StatsigDriver
|
|
381
383
|
@evaluator.get_config(user, config_name, res, user_persisted_values: user_persisted_values, ignore_local_overrides: ignore_local_overrides)
|
382
384
|
|
383
385
|
unless disable_log_exposure
|
384
|
-
@logger.log_config_exposure(user, res
|
386
|
+
@logger.log_config_exposure(user, res)
|
385
387
|
end
|
386
388
|
|
387
389
|
DynamicConfig.new(res.name, res.json_value, res.rule_id, res.group_name, res.id_type, res.evaluation_details)
|
data/lib/statsig_logger.rb
CHANGED
@@ -38,42 +38,49 @@ module Statsig
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def log_gate_exposure(user,
|
41
|
+
def log_gate_exposure(user, result, context = nil)
|
42
42
|
event = StatsigEvent.new($gate_exposure_event)
|
43
43
|
event.user = user
|
44
44
|
metadata = {
|
45
|
-
gate:
|
46
|
-
gateValue:
|
47
|
-
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
45
|
+
gate: result.name,
|
46
|
+
gateValue: result.gate_value.to_s,
|
47
|
+
ruleID: result.rule_id || Statsig::Const::EMPTY_STR,
|
48
48
|
}
|
49
|
+
if result.config_version != nil
|
50
|
+
metadata[:configVersion] = result.config_version.to_s
|
51
|
+
end
|
49
52
|
if @debug_info != nil
|
50
53
|
metadata[:debugInfo] = @debug_info
|
51
54
|
end
|
52
55
|
return false if not is_unique_exposure(user, $gate_exposure_event, metadata)
|
53
56
|
event.metadata = metadata
|
54
57
|
|
55
|
-
event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
|
58
|
+
event.secondary_exposures = result.secondary_exposures.is_a?(Array) ? result.secondary_exposures : []
|
56
59
|
|
57
|
-
safe_add_eval_details(
|
60
|
+
safe_add_eval_details(result.evaluation_details, event)
|
58
61
|
safe_add_exposure_context(context, event)
|
59
62
|
log_event(event)
|
60
63
|
end
|
61
64
|
|
62
|
-
def log_config_exposure(user,
|
65
|
+
def log_config_exposure(user, result, context = nil)
|
63
66
|
event = StatsigEvent.new($config_exposure_event)
|
64
67
|
event.user = user
|
65
68
|
metadata = {
|
66
|
-
config:
|
67
|
-
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
69
|
+
config: result.name,
|
70
|
+
ruleID: result.rule_id || Statsig::Const::EMPTY_STR,
|
71
|
+
rulePassed: result.gate_value.to_s,
|
68
72
|
}
|
73
|
+
if result.config_version != nil
|
74
|
+
metadata[:configVersion] = result.config_version.to_s
|
75
|
+
end
|
69
76
|
if @debug_info != nil
|
70
77
|
metadata[:debugInfo] = @debug_info
|
71
78
|
end
|
72
79
|
return false if not is_unique_exposure(user, $config_exposure_event, metadata)
|
73
80
|
event.metadata = metadata
|
74
|
-
event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
|
81
|
+
event.secondary_exposures = result.secondary_exposures.is_a?(Array) ? result.secondary_exposures : []
|
75
82
|
|
76
|
-
safe_add_eval_details(
|
83
|
+
safe_add_eval_details(result.evaluation_details, event)
|
77
84
|
safe_add_exposure_context(context, event)
|
78
85
|
log_event(event)
|
79
86
|
end
|
@@ -96,6 +103,9 @@ module Statsig
|
|
96
103
|
parameterName: parameter_name,
|
97
104
|
isExplicitParameter: String(is_explicit)
|
98
105
|
}
|
106
|
+
if config_evaluation.config_version != nil
|
107
|
+
metadata[:configVersion] = config_evaluation.config_version.to_s
|
108
|
+
end
|
99
109
|
if @debug_info != nil
|
100
110
|
metadata[:debugInfo] = @debug_info
|
101
111
|
end
|
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.1
|
4
|
+
version: 2.2.1
|
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-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|