statsig 2.9.2 → 2.9.4

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: cb1b471ac9aa8827bb59d3dd18801a1d7e663e8e3d25ea44be09753f02953ecf
4
- data.tar.gz: 5d08845dba73c8af7978c38c8519e790361a27dd4ed10f940a8af61d6c1b13e2
3
+ metadata.gz: 96c137e20d5bc087e6b3108ad346f3882a8f981c2fc18e95270af8511230f932
4
+ data.tar.gz: daeaec468e9fa579f7a5ef80085bf19a49175d25985ae7d2128ed1c767e073bb
5
5
  SHA512:
6
- metadata.gz: ca761ef81609cbd94bec7294109c893c683477c55c39f6475161269b65a7275e416253d8299e7f1995111d38d7bd7d09687aa8091bb90ffa0762daf609141f85
7
- data.tar.gz: 29d1482c76a1d78be0fd6e7c80886068a23c1973683050db19a08fa4d6a0b855a2549f24ebbadb6bc391669e09085b0bedc263db76cee9ea736099cf58b5fd80
6
+ metadata.gz: 76055e14d07eab81f94c6ad559e3378da16860a8c60fc3dc6a1118fa7621c61f53d7b4921e0a70856e1195aa954b23fb188812fe9ba16f4c6db3059c3ad59f1c
7
+ data.tar.gz: ad835e17a3ee00318cbf3ee7ccbcf94b7ef09dd05a08163b6ff1308d79040593175fb3d08b44d716c015edeae55d5949f1ad3515c74eed13ae16c5c709b84069
@@ -51,7 +51,8 @@ module Statsig
51
51
  name: config_name,
52
52
  disable_evaluation_details: true,
53
53
  disable_exposures: !include_exposures,
54
- include_local_overrides: include_local_overrides
54
+ include_local_overrides: include_local_overrides,
55
+ disable_nested_experiment_evaluation: true
55
56
  )
56
57
  evaluator.eval_spec(config_name_str, user, config_spec, eval_result)
57
58
  else
data/lib/config_result.rb CHANGED
@@ -22,6 +22,7 @@ module Statsig
22
22
  attr_accessor :sampling_rate
23
23
  attr_accessor :has_seen_analytical_gates
24
24
  attr_accessor :override_config_name
25
+ attr_accessor :disable_nested_experiment_evaluation
25
26
 
26
27
  def initialize(
27
28
  name:,
@@ -43,7 +44,8 @@ module Statsig
43
44
  forward_all_exposures: false,
44
45
  sampling_rate: nil,
45
46
  has_seen_analytical_gates: false,
46
- override_config_name: nil
47
+ override_config_name: nil,
48
+ disable_nested_experiment_evaluation: false
47
49
  )
48
50
  @name = name
49
51
  @gate_value = gate_value
@@ -66,6 +68,7 @@ module Statsig
66
68
  @sampling_rate = sampling_rate
67
69
  @has_seen_analytical_gates = has_seen_analytical_gates
68
70
  @override_config_name = override_config_name
71
+ @disable_nested_experiment_evaluation = disable_nested_experiment_evaluation
69
72
  end
70
73
 
71
74
  def self.from_user_persisted_values(config_name, user_persisted_values)
data/lib/constants.rb CHANGED
@@ -7,6 +7,7 @@ module Statsig
7
7
  SUPPORTED_CONDITION_TYPES = Set.new(%i[
8
8
  public fail_gate pass_gate ip_based ua_based user_field
9
9
  environment_field current_time user_bucket unit_id
10
+ experiment_group
10
11
  ]).freeze
11
12
 
12
13
  SUPPORTED_OPERATORS = Set.new(%i[
@@ -87,6 +88,7 @@ module Statsig
87
88
  CND_MULTI_PASS_GATE = 'multi_pass_gate'.freeze
88
89
  CND_MULTI_FAIL_GATE = 'multi_fail_gate'.freeze
89
90
  CND_CURRENT_TIME = 'current_time'.freeze
91
+ CND_EXPERIMENT_GROUP = 'experiment_group'.freeze
90
92
  CND_ENVIRONMENT_FIELD = 'environment_field'.freeze
91
93
  CND_USER_BUCKET = 'user_bucket'.freeze
92
94
  CND_UNIT_ID = 'unit_id'.freeze
data/lib/evaluator.rb CHANGED
@@ -14,6 +14,12 @@ require_relative 'user_persistent_storage_utils'
14
14
  module Statsig
15
15
  class Evaluator
16
16
 
17
+ EXPOSURE_LOGGING_CONDITION_TYPES = [
18
+ Const::CND_PASS_GATE,
19
+ Const::CND_FAIL_GATE,
20
+ Const::CND_EXPERIMENT_GROUP
21
+ ].freeze
22
+
17
23
  attr_accessor :spec_store
18
24
 
19
25
  attr_accessor :gate_overrides
@@ -26,7 +32,7 @@ module Statsig
26
32
 
27
33
  attr_accessor :persistent_storage_utils
28
34
 
29
- def initialize(store, options, persistent_storage_utils)
35
+ def initialize(store, options, persistent_storage_utils, logger = nil)
30
36
  UAParser.initialize_async
31
37
  CountryLookup.initialize_async
32
38
 
@@ -36,6 +42,7 @@ module Statsig
36
42
  @experiment_overrides = {}
37
43
  @options = options
38
44
  @persistent_storage_utils = persistent_storage_utils
45
+ @logger = logger
39
46
  end
40
47
 
41
48
  def maybe_restart_background_threads
@@ -689,8 +696,10 @@ module Statsig
689
696
  eval_condition(user, condition, end_result)
690
697
  end
691
698
 
699
+ # Conditions that log an exposure as a side effect are never memoized, or the
700
+ # repeat evaluations would silently drop their exposures.
692
701
  if !@options.disable_evaluation_memoization &&
693
- condition && condition[:type] != Const::CND_PASS_GATE && condition[:type] != Const::CND_FAIL_GATE
702
+ condition && !EXPOSURE_LOGGING_CONDITION_TYPES.include?(condition[:type])
694
703
  eval_rule_memo[condition_hash] = result
695
704
  end
696
705
 
@@ -742,6 +751,8 @@ module Statsig
742
751
  return type == Const::CND_PASS_GATE ? result : !result
743
752
  when Const::CND_MULTI_PASS_GATE, Const::CND_MULTI_FAIL_GATE
744
753
  return eval_nested_gates(target, type, user, end_result)
754
+ when Const::CND_EXPERIMENT_GROUP
755
+ value = eval_experiment_group(field, user, end_result)
745
756
  when Const::CND_IP_BASED
746
757
  value = get_value_from_user(user, field) || get_value_from_ip(user, field)
747
758
  when Const::CND_UA_BASED
@@ -921,6 +932,29 @@ module Statsig
921
932
  gate_value
922
933
  end
923
934
 
935
+ # The condition's field carries the name of a parent experiment. The user's group
936
+ # in that experiment becomes the value the operator compares against. Mirrors
937
+ # statsig-rust's evaluate_experiment_group, which evaluates the parent through the
938
+ # public get_experiment API: a full evaluation that logs its own experiment
939
+ # exposure and contributes nothing to the outer result's secondary exposures.
940
+ def eval_experiment_group(experiment_name, user, end_result)
941
+ return nil if end_result.disable_nested_experiment_evaluation
942
+ return nil unless experiment_name.is_a?(String) && !experiment_name.empty?
943
+
944
+ nested_result = ConfigResult.new(
945
+ name: experiment_name,
946
+ disable_exposures: end_result.disable_exposures,
947
+ disable_evaluation_details: end_result.disable_evaluation_details
948
+ )
949
+ get_config(user, experiment_name, nested_result)
950
+
951
+ unless end_result.disable_exposures
952
+ @logger&.log_config_exposure(user, nested_result)
953
+ end
954
+
955
+ nested_result.group_name
956
+ end
957
+
924
958
  def eval_nested_gates(gate_names, condition_type, user, end_result)
925
959
  has_passing_gate = false
926
960
  is_multi_pass_gate_type = condition_type == Const::CND_MULTI_PASS_GATE
data/lib/network.rb CHANGED
@@ -19,6 +19,14 @@ module Statsig
19
19
 
20
20
  class Network
21
21
 
22
+ # Background config-spec sync retries transient failures with a short, bounded
23
+ # exponential backoff, matching statsig-server-core's background sync strategy
24
+ # (3 retries; 2^n * 100ms => 200ms, 400ms, 800ms). These are fixed rather than
25
+ # user-configurable, mirroring server-core where the values are hardcoded.
26
+ CONFIG_SYNC_RETRY_LIMIT = 3
27
+ CONFIG_SYNC_RETRY_BACKOFF_SECONDS = 0.2
28
+ CONFIG_SYNC_RETRY_BACKOFF_MULTIPLIER = 2
29
+
22
30
  def initialize(server_secret, options, backoff_mult = 10)
23
31
  super()
24
32
  @options = options
@@ -42,7 +50,7 @@ module Statsig
42
50
  if context == 'initialize'
43
51
  return get(dcs_url, @options.initialize_retry_limit)
44
52
  end
45
- get(dcs_url)
53
+ get(dcs_url, CONFIG_SYNC_RETRY_LIMIT, CONFIG_SYNC_RETRY_BACKOFF_SECONDS, CONFIG_SYNC_RETRY_BACKOFF_MULTIPLIER)
46
54
  end
47
55
 
48
56
  def download_config_specs_fallback(since_time, context)
@@ -84,8 +92,8 @@ module Statsig
84
92
  request(:GET, url, nil, 0, 1, false, 0, { 'Range' => "bytes=#{start_byte}-" }, false)
85
93
  end
86
94
 
87
- def get(url, retries = 0, backoff = 1)
88
- request(:GET, url, nil, retries, backoff)
95
+ def get(url, retries = 0, backoff = 1, backoff_multiplier = @backoff_multiplier)
96
+ request(:GET, url, nil, retries, backoff, false, 0, {}, true, backoff_multiplier)
89
97
  end
90
98
 
91
99
  def post(url, body, retries = 0, backoff = 1, zipped = false, event_count = 0)
@@ -106,7 +114,7 @@ module Statsig
106
114
  end
107
115
  end
108
116
 
109
- def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true)
117
+ def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true, backoff_multiplier = @backoff_multiplier)
110
118
  if @local_mode
111
119
  return nil, nil
112
120
  end
@@ -146,7 +154,7 @@ module Statsig
146
154
  return nil, e unless retries.positive?
147
155
 
148
156
  sleep backoff_adjusted
149
- return request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
157
+ return request(method, url, body, retries - 1, backoff * backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers, backoff_multiplier)
150
158
  end
151
159
  return res, nil if res.status.success?
152
160
 
@@ -157,7 +165,7 @@ module Statsig
157
165
 
158
166
  ## status code retry
159
167
  sleep backoff_adjusted
160
- request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
168
+ request(method, url, body, retries - 1, backoff * backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers, backoff_multiplier)
161
169
  end
162
170
 
163
171
  private
data/lib/statsig.rb CHANGED
@@ -410,7 +410,7 @@ module Statsig
410
410
  def self.get_statsig_metadata
411
411
  {
412
412
  'sdkType' => 'ruby-server',
413
- 'sdkVersion' => '2.9.2',
413
+ 'sdkVersion' => '2.9.4',
414
414
  'languageVersion' => RUBY_VERSION
415
415
  }
416
416
  end
@@ -34,12 +34,13 @@ class StatsigDriver
34
34
  tracker = @diagnostics.track('initialize', 'overall')
35
35
  @options = options || StatsigOptions.new
36
36
  @shutdown = false
37
+ @shutdown_message_logged = false
37
38
  @secret_key = secret_key
38
39
  @net = Statsig::Network.new(secret_key, @options)
39
40
  @logger = Statsig::StatsigLogger.new(@net, @options, @err_boundary, @sdk_configs)
40
41
  @persistent_storage_utils = Statsig::UserPersistentStorageUtils.new(@options)
41
42
  @store = Statsig::SpecStore.new(@net, @options, error_callback, @diagnostics, @err_boundary, @logger, secret_key, @sdk_configs)
42
- @evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils)
43
+ @evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils, @logger)
43
44
  tracker.end(success: true)
44
45
 
45
46
  @logger.log_diagnostics_event(@diagnostics, 'initialize')
@@ -460,8 +461,10 @@ class StatsigDriver
460
461
  end
461
462
 
462
463
  def check_shutdown
463
- if @shutdown
464
- puts 'SDK has been shutdown. Updates in the Statsig Console will no longer reflect.'
465
- end
464
+ return unless @shutdown
465
+ return if @shutdown_message_logged
466
+
467
+ @shutdown_message_logged = true
468
+ puts '[Statsig]: SDK has been shutdown. Updates in the Statsig Console will no longer reflect.'
466
469
  end
467
470
  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.9.2
4
+ version: 2.9.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: 2026-05-11 00:00:00.000000000 Z
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler