statsig 2.9.1 → 2.9.2

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: 800d73e16d593bd9c6b505f9d6207c47e51d8b700344b3c653a07ca8b257cd69
4
- data.tar.gz: a3cad13252aff42d5eba3e4f9c9b478adb9ec0758633102f2ada9b3d3739052e
3
+ metadata.gz: cb1b471ac9aa8827bb59d3dd18801a1d7e663e8e3d25ea44be09753f02953ecf
4
+ data.tar.gz: 5d08845dba73c8af7978c38c8519e790361a27dd4ed10f940a8af61d6c1b13e2
5
5
  SHA512:
6
- metadata.gz: f9a1df9789f61b60bb4d015da636b20bf081804b5c1c8d1f542afc3f494538fea68b3bc84ca65d7faf513c63d8d0d72bee94f2583a932c7d54ae0c3cdff560e7
7
- data.tar.gz: a7d46bfa64e8a52c60eec3bc14c596ff9ffe41493ed68bda741d4171c75da407e7e7842ad63c76addff0c98314d95967b515801efba8f1f5486d8aabc793fc8e
6
+ metadata.gz: ca761ef81609cbd94bec7294109c893c683477c55c39f6475161269b65a7275e416253d8299e7f1995111d38d7bd7d09687aa8091bb90ffa0762daf609141f85
7
+ data.tar.gz: 29d1482c76a1d78be0fd6e7c80886068a23c1973683050db19a08fa4d6a0b855a2549f24ebbadb6bc391669e09085b0bedc263db76cee9ea736099cf58b5fd80
data/lib/network.rb CHANGED
@@ -7,6 +7,7 @@ require 'zlib'
7
7
  RETRY_CODES = [408, 500, 502, 503, 504, 522, 524, 599].freeze
8
8
 
9
9
  module Statsig
10
+ STATSIG_CDN_DCS_BASE = 'https://api.statsigcdn.com/v2'.freeze
10
11
  class NetworkError < StandardError
11
12
  attr_reader :http_code
12
13
 
@@ -44,6 +45,14 @@ module Statsig
44
45
  get(dcs_url)
45
46
  end
46
47
 
48
+ def download_config_specs_fallback(since_time, context)
49
+ dcs_url = "#{STATSIG_CDN_DCS_BASE}/download_config_specs/#{@server_secret}.json"
50
+ if since_time.positive?
51
+ dcs_url += "?sinceTime=#{since_time}"
52
+ end
53
+ get(dcs_url)
54
+ end
55
+
47
56
  def post_logs(events, error_boundary)
48
57
  url = @options.log_event_url
49
58
  event_count = events.length
data/lib/spec_store.rb CHANGED
@@ -6,6 +6,8 @@ require_relative 'id_list'
6
6
 
7
7
  module Statsig
8
8
  class SpecStore
9
+ STATSIG_NETWORK_FALLBACK_THRESHOLD = 5
10
+
9
11
  attr_accessor :last_config_sync_time
10
12
  attr_accessor :initial_config_sync_time
11
13
  attr_accessor :init_reason
@@ -48,6 +50,7 @@ module Statsig
48
50
  @secret_key = secret_key
49
51
  @unsupported_configs = Set.new
50
52
  @sdk_configs = sdk_config
53
+ @sync_failure_count = 0
51
54
 
52
55
  startTime = (Time.now.to_f * 1000).to_i
53
56
 
@@ -84,6 +87,9 @@ module Statsig
84
87
 
85
88
  if @init_reason == EvaluationReason::UNINITIALIZED
86
89
  failure_details = download_config_specs('initialize')
90
+ if !failure_details.nil? && @options.fallback_to_statsig_api && using_proxy_for_dcs?
91
+ failure_details = download_config_specs_fallback('initialize')
92
+ end
87
93
  end
88
94
 
89
95
  @initial_config_sync_time = @last_config_sync_time == 0 ? -1 : @last_config_sync_time
@@ -205,10 +211,20 @@ module Statsig
205
211
 
206
212
  def sync_config_specs
207
213
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_V2_KEY)
208
- load_config_specs_from_storage_adapter('config_sync')
214
+ failure_details = load_config_specs_from_storage_adapter('config_sync')
215
+ else
216
+ failure_details = download_config_specs('config_sync')
217
+ end
218
+
219
+ if failure_details.nil?
220
+ @sync_failure_count = 0
209
221
  else
210
- download_config_specs('config_sync')
222
+ @sync_failure_count += 1
223
+ if @options.fallback_to_statsig_api && using_proxy_for_dcs? && (@sync_failure_count % STATSIG_NETWORK_FALLBACK_THRESHOLD == 0)
224
+ download_config_specs_fallback('config_sync')
225
+ end
211
226
  end
227
+
212
228
  @logger.log_diagnostics_event(@diagnostics, 'config_sync')
213
229
  end
214
230
 
@@ -223,6 +239,58 @@ module Statsig
223
239
 
224
240
  private
225
241
 
242
+ def using_proxy_for_dcs?
243
+ !@options.download_config_specs_url.start_with?(STATSIG_CDN_DCS_BASE)
244
+ end
245
+
246
+ def download_config_specs_fallback(context)
247
+ response, e = @network.download_config_specs_fallback(@last_config_sync_time, context)
248
+ handle_config_specs_response(context, 'download_config_specs_fallback', 'CONFIG_SPECS_FALLBACK', response, e)
249
+ end
250
+
251
+ def handle_config_specs_response(context, diagnostic_key, error_prefix, response, e)
252
+ tracker = @diagnostics.track(context, diagnostic_key, 'network_request')
253
+
254
+ error = nil
255
+ failure_details = nil
256
+ begin
257
+ code = response&.status.to_i
258
+ if e.is_a? NetworkError
259
+ code = e.http_code
260
+ failure_details = {statusCode: code, exception: e, reason: "#{error_prefix}_NETWORK_ERROR"}
261
+ elsif !e.nil?
262
+ # Transport-level errors (ECONNREFUSED, timeout, DNS, SSL) are caught and returned as values
263
+ # by network.rb. Without this, failure_details stays nil and the caller treats it as success.
264
+ failure_details = {exception: e, reason: "#{error_prefix}_CONNECTION_ERROR"}
265
+ end
266
+ tracker.end(statusCode: code, success: e.nil?, sdkRegion: response&.headers&.[]('X-Statsig-Region'))
267
+
268
+ if e.nil?
269
+ unless response.nil?
270
+ tracker = @diagnostics.track(context, diagnostic_key, 'process')
271
+ body = response.body.to_s
272
+ failure_details = process_specs(body)
273
+ if failure_details.nil?
274
+ @init_reason = EvaluationReason::NETWORK
275
+ end
276
+ tracker.end(success: @init_reason == EvaluationReason::NETWORK)
277
+
278
+ unless response.body.nil? or @rules_updated_callback.nil?
279
+ @rules_updated_callback.call(body, @last_config_sync_time)
280
+ end
281
+ end
282
+ else
283
+ error = e
284
+ end
285
+ rescue StandardError => e
286
+ failure_details = {exception: e, reason: "INTERNAL_ERROR"}
287
+ error = e
288
+ end
289
+
290
+ @error_callback.call(error) unless error.nil? or @error_callback.nil?
291
+ return failure_details
292
+ end
293
+
226
294
  def load_config_specs_from_storage_adapter(context)
227
295
  tracker = @diagnostics.track(context, 'data_store_config_specs', 'fetch')
228
296
  cached_values = @options.data_store.get(Interfaces::IDataStore::CONFIG_SPECS_V2_KEY)
@@ -284,44 +352,8 @@ module Statsig
284
352
  end
285
353
 
286
354
  def download_config_specs(context)
287
- tracker = @diagnostics.track(context, 'download_config_specs', 'network_request')
288
-
289
- error = nil
290
- failure_details = nil
291
- begin
292
- response, e = @network.download_config_specs(@last_config_sync_time, context)
293
- code = response&.status.to_i
294
- if e.is_a? NetworkError
295
- code = e.http_code
296
- failure_details = {statusCode: code, exception: e, reason: "CONFIG_SPECS_NETWORK_ERROR"}
297
- end
298
- tracker.end(statusCode: code, success: e.nil?, sdkRegion: response&.headers&.[]('X-Statsig-Region'))
299
-
300
- if e.nil?
301
- unless response.nil?
302
- tracker = @diagnostics.track(context, 'download_config_specs', 'process')
303
- body = response.body.to_s
304
- failure_details = process_specs(body)
305
- if failure_details.nil?
306
- @init_reason = EvaluationReason::NETWORK
307
- end
308
- tracker.end(success: @init_reason == EvaluationReason::NETWORK)
309
-
310
- unless response.body.nil? or @rules_updated_callback.nil?
311
- @rules_updated_callback.call(body,
312
- @last_config_sync_time)
313
- end
314
- end
315
- else
316
- error = e
317
- end
318
- rescue StandardError => e
319
- failure_details = {exception: e, reason: "INTERNAL_ERROR"}
320
- error = e
321
- end
322
-
323
- @error_callback.call(error) unless error.nil? or @error_callback.nil?
324
- return failure_details
355
+ response, e = @network.download_config_specs(@last_config_sync_time, context)
356
+ handle_config_specs_response(context, 'download_config_specs', 'CONFIG_SPECS', response, e)
325
357
  end
326
358
 
327
359
  def process_specs(specs_string, from_adapter: false)
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.1',
413
+ 'sdkVersion' => '2.9.2',
414
414
  'languageVersion' => RUBY_VERSION
415
415
  }
416
416
  end
@@ -96,6 +96,12 @@ class StatsigOptions
96
96
  # default: 0
97
97
  attr_accessor :initialize_retry_limit
98
98
 
99
+ # When true, if the configured download_config_specs_url fails, the SDK will
100
+ # automatically fall back to the Statsig CDN to fetch config specs.
101
+ # Only effective when a custom download_config_specs_url is configured.
102
+ # default: false
103
+ attr_accessor :fallback_to_statsig_api
104
+
99
105
  def initialize(
100
106
  environment = nil,
101
107
  download_config_specs_url: nil,
@@ -120,7 +126,8 @@ class StatsigOptions
120
126
  post_logs_retry_backoff: nil,
121
127
  user_persistent_storage: nil,
122
128
  disable_evaluation_memoization: false,
123
- initialize_retry_limit: 0
129
+ initialize_retry_limit: 0,
130
+ fallback_to_statsig_api: false
124
131
  )
125
132
  @environment = environment.is_a?(Hash) ? environment : nil
126
133
 
@@ -152,5 +159,6 @@ class StatsigOptions
152
159
  @user_persistent_storage = user_persistent_storage
153
160
  @disable_evaluation_memoization = disable_evaluation_memoization
154
161
  @initialize_retry_limit = initialize_retry_limit
162
+ @fallback_to_statsig_api = fallback_to_statsig_api
155
163
  end
156
164
  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.1
4
+ version: 2.9.2
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-04-29 00:00:00.000000000 Z
11
+ date: 2026-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler