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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd554df4ba15f0d1addaecc5e76502c1218c1b08842152eeeffd0f6e121dbcba
4
- data.tar.gz: b1d19d91608685488db9705e213559c4a36c21731b0822137b55fab0693a871d
3
+ metadata.gz: 7b9e465d382f7e7e2a4640eaf549bc3e882b0aac3c2333c9874fc6855a6af1cc
4
+ data.tar.gz: 844845547c6b1914908e38438dc932486e3ea5740ef32c3b59eb1cc67a4274ed
5
5
  SHA512:
6
- metadata.gz: b360f7fb18b06e87b538eb005ce0f7344f8a633a1cb9915d3e6f74e5b0fab3b7d87d46ab4b4498548e66766d00a4f60a9bb5f502c270e09925e67c24575da7ca
7
- data.tar.gz: 337e0fd5ecfc11fa5565980df91d21ee17cc6586d49f1a18b333eaa4de1da29094c453a00daf1671ce8e6872d03882b85c169df6709aa41e543500cdc5c78057
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 ? rule[:returnValue] == true : config[:defaultValue] == true
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
- @init_reason = EvaluationReason::DATA_ADAPTER
207
- tracker.end(success: true)
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
- if process_specs(response.body.to_s)
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 false
310
+ return {reason: "EMPTY_SPEC"}
292
311
  end
293
312
 
294
- specs_json = JSON.parse(specs_string, { symbolize_names: true })
295
- return false unless specs_json.is_a? Hash
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
- hashed_sdk_key_used = specs_json[:hashed_sdk_key_used]
298
- unless hashed_sdk_key_used.nil? or hashed_sdk_key_used == Statsig::HashUtils.djb2(@secret_key)
299
- err_boundary.log_exception(Statsig::InvalidSDKKeyResponse.new)
300
- return false
301
- end
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
- new_specs_sync_time = specs_json[:time]
304
- if new_specs_sync_time.nil? \
305
- || new_specs_sync_time < @last_config_sync_time \
306
- || specs_json[:has_updates] != true \
307
- || specs_json[:feature_gates].nil? \
308
- || specs_json[:dynamic_configs].nil? \
309
- || specs_json[:layer_configs].nil?
310
- return false
311
- end
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
- @last_config_sync_time = new_specs_sync_time
314
- @unsupported_configs.clear
333
+ @last_config_sync_time = new_specs_sync_time
334
+ @unsupported_configs.clear
315
335
 
316
- specs_json[:diagnostics]&.each { |key, value| @diagnostics.sample_rates[key.to_s] = value }
336
+ specs_json[:diagnostics]&.each { |key, value| @diagnostics.sample_rates[key.to_s] = value }
317
337
 
318
- @gates = specs_json[:feature_gates]
319
- @configs = specs_json[:dynamic_configs]
320
- @layers = specs_json[:layer_configs]
321
- @condition_map = specs_json[:condition_map]
322
- @experiment_to_layer = specs_json[:experiment_to_layer]
323
- @sdk_keys_to_app_ids = specs_json[:sdk_keys_to_app_ids] || {}
324
- @hashed_sdk_keys_to_app_ids = specs_json[:hashed_sdk_keys_to_app_ids] || {}
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
- unless from_adapter
327
- save_rulesets_to_storage_adapter(specs_string)
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
- true
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) and disable_exposure_logging in 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) and disable_exposure_logging in 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) and disable_exposure_logging in 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) and disable_exposure_logging in 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.0',
373
+ 'sdkVersion' => '2.2.1',
367
374
  'languageVersion' => RUBY_VERSION
368
375
  }
369
376
  end
@@ -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, gate_name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
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.name, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
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.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
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)
@@ -38,42 +38,49 @@ module Statsig
38
38
  end
39
39
  end
40
40
 
41
- def log_gate_exposure(user, gate_name, value, rule_id, secondary_exposures, eval_details, context = nil)
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: gate_name,
46
- gateValue: value.to_s,
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(eval_details, event)
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, config_name, rule_id, secondary_exposures, eval_details, context = nil)
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: config_name,
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(eval_details, event)
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.0
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-09-26 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler