From 7269ae783dd8feb0be72108a95ab0d7636e77a36 Mon Sep 17 00:00:00 2001 From: Stephen Reid Date: Wed, 25 Feb 2026 19:51:32 -0500 Subject: [PATCH 1/4] Add retry option for fetching rulesets --- lib/network.rb | 4 ++-- lib/statsig_options.rb | 8 +++++++- test/test_network.rb | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/network.rb b/lib/network.rb index 781cfb3..835c438 100644 --- a/lib/network.rb +++ b/lib/network.rb @@ -56,7 +56,7 @@ def download_config_specs(since_time) if since_time.positive? dcs_url += "?sinceTime=#{since_time}" end - get(dcs_url) + get(dcs_url, @options.ruleset_id_list_retry_limit) end def post_logs(events, error_boundary) @@ -83,7 +83,7 @@ def post_logs(events, error_boundary) def get_id_lists url = @options.get_id_lists_url - post(url, JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata })) + post(url, JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }), @options.ruleset_id_list_retry_limit) end def get(url, retries = 0, backoff = 1) diff --git a/lib/statsig_options.rb b/lib/statsig_options.rb index 73a9570..1032b4c 100644 --- a/lib/statsig_options.rb +++ b/lib/statsig_options.rb @@ -92,6 +92,10 @@ class StatsigOptions # default: false attr_accessor :disable_evaluation_memoization + # Number of times to retry fetching rulesets and id lists + # default: 3 + attr_accessor :ruleset_id_list_retry_limit + def initialize( environment = nil, download_config_specs_url: nil, @@ -115,7 +119,8 @@ def initialize( post_logs_retry_limit: 3, post_logs_retry_backoff: nil, user_persistent_storage: nil, - disable_evaluation_memoization: false + disable_evaluation_memoization: false, + ruleset_id_list_retry_limit: 3 ) @environment = environment.is_a?(Hash) ? environment : nil @@ -146,5 +151,6 @@ def initialize( @post_logs_retry_backoff = post_logs_retry_backoff @user_persistent_storage = user_persistent_storage @disable_evaluation_memoization = disable_evaluation_memoization + @ruleset_id_list_retry_limit = ruleset_id_list_retry_limit end end diff --git a/test/test_network.rb b/test/test_network.rb index 92a1234..f2f9795 100644 --- a/test/test_network.rb +++ b/test/test_network.rb @@ -76,4 +76,27 @@ def test_retry_until_out_of_retries assert(spy.calls.size == 6) assert(!e.nil?) end + + def test_ruleset_id_list_retries + @calls = 0 + stub_download_config_specs.to_return(status: lambda { |req| status_lambda(req) }, body: '{}') + stub_request(:post, 'https://statsigapi.net/v1/get_id_lists').to_return(status: lambda { |req| status_lambda(req) }, body: '{}') + + options = StatsigOptions.new(local_mode: false, ruleset_id_list_retry_limit: 2) + net = Statsig::Network.new('secret-abc', options, 0.1) + spy = Spy.on(net, :request).and_call_through + + # Test ruleset retries + @calls = 0 + res, _ = net.download_config_specs(0) + assert(spy.calls.size == 3) # 500, 500, 200 + assert(res.status.success?) + + # Test id list retries + @calls = 0 + spy.clear + res, _ = net.get_id_lists + assert(spy.calls.size == 3) # 500, 500, 200 + assert(res.status.success?) + end end From 85364bb1cde04dd7249ec504c89f799a34a82975 Mon Sep 17 00:00:00 2001 From: Stephen Reid Date: Wed, 25 Feb 2026 20:05:48 -0500 Subject: [PATCH 2/4] Add a default network timeout --- lib/statsig_options.rb | 3 ++- test/test_network_timeout.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/statsig_options.rb b/lib/statsig_options.rb index 1032b4c..fcf174c 100644 --- a/lib/statsig_options.rb +++ b/lib/statsig_options.rb @@ -75,6 +75,7 @@ class StatsigOptions attr_accessor :disable_sorbet_logging_handlers # Number of seconds before a network call is timed out + # default: 30s attr_accessor :network_timeout # Number of times to retry sending a batch of failed log events @@ -115,7 +116,7 @@ def initialize( logger_threadpool_size: 3, disable_diagnostics_logging: false, disable_sorbet_logging_handlers: false, - network_timeout: nil, + network_timeout: 30, post_logs_retry_limit: 3, post_logs_retry_backoff: nil, user_persistent_storage: nil, diff --git a/test/test_network_timeout.rb b/test/test_network_timeout.rb index 8e45649..3d02220 100644 --- a/test/test_network_timeout.rb +++ b/test/test_network_timeout.rb @@ -42,6 +42,7 @@ def test_network_timeout def test_no_network_timeout options = StatsigOptions.new(nil, local_mode: false) + assert_equal(30, options.network_timeout) net = Statsig::Network.new('secret-abc', options, 0) start = Time.now net.get('http://localhost:4567/v2/download_config_specs', 0, 0) From ba70cb8e6de35b944d94e52d72bacc3c0881db14 Mon Sep 17 00:00:00 2001 From: Stephen Reid Date: Wed, 25 Feb 2026 20:07:02 -0500 Subject: [PATCH 3/4] Update readme with options --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index aaf3e71..2ded6b9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,35 @@ Statsig helps you move faster with Feature Gates (Feature Flags) and Dynamic Con Check out our [SDK docs](https://docs.statsig.com/server/rubySDK) to get started. +## StatsigOptions + +| Option | Description | Default | +| --- | --- | --- | +| `environment` | Hash for environment variables (e.g. `{ "tier" => "development" }`) | `nil` | +| `download_config_specs_url` | URL for download_config_specs | `https://api.statsigcdn.com/v2/download_config_specs/` | +| `log_event_url` | URL for log_event | `https://statsigapi.net/v1/log_event` | +| `get_id_lists_url` | URL for get_id_lists | `https://statsigapi.net/v1/get_id_lists` | +| `rulesets_sync_interval` | Interval (in seconds) to poll for configuration changes | `10` | +| `idlists_sync_interval` | Interval (in seconds) to poll for id list changes | `60` | +| `disable_rulesets_sync` | Disable background syncing for rulesets | `false` | +| `disable_idlists_sync` | Disable background syncing for id lists | `false` | +| `logging_interval_seconds` | How often to flush logs to Statsig | `60` | +| `logging_max_buffer_size` | Maximum number of events to batch before flushing | `1000` | +| `local_mode` | Restricts the SDK to not issue any network requests | `false` | +| `bootstrap_values` | String representing all rules for initialization | `nil` | +| `rules_updated_callback` | Callback function called when rulesets are updated | `nil` | +| `data_store` | Class extending IDataStore for common data store (e.g. Redis) | `nil` | +| `idlist_threadpool_size` | Number of threads for syncing IDLists | `3` | +| `logger_threadpool_size` | Number of threads for posting event logs | `3` | +| `disable_diagnostics_logging` | Should diagnostics be logged | `false` | +| `disable_sorbet_logging_handlers` | Disable Sorbet type safety logging | `false` | +| `network_timeout` | Number of seconds before a network call is timed out | `30` | +| `post_logs_retry_limit` | Number of times to retry failed log events | `3` | +| `post_logs_retry_backoff` | Backoff time/function between retries | `nil` | +| `user_persistent_storage` | Storage adapter for persisted values | `nil` | +| `disable_evaluation_memoization` | Disable memoization of evaluation results | `false` | +| `ruleset_id_list_retry_limit` | Number of times to retry fetching rulesets and id lists | `3` | + ## Testing Each server SDK is tested at multiple levels - from unit to integration and e2e tests. Our internal e2e test harness runs daily against each server SDK, while unit and integration tests can be seen in the respective github repos of each SDK. The `server_sdk_consistency_test` runs a validation test on local rule/condition evaluation for this SDK against the results in the statsig backend. From bd78333b55e1dd2e4f16981503bca6342d95e87f Mon Sep 17 00:00:00 2001 From: Stephen Reid Date: Wed, 25 Feb 2026 20:08:35 -0500 Subject: [PATCH 4/4] Alphabetitize options --- README.md | 32 ++++---- lib/statsig_options.rb | 174 ++++++++++++++++++++--------------------- 2 files changed, 103 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 2ded6b9..968d4da 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,30 @@ Check out our [SDK docs](https://docs.statsig.com/server/rubySDK) to get started | Option | Description | Default | | --- | --- | --- | -| `environment` | Hash for environment variables (e.g. `{ "tier" => "development" }`) | `nil` | +| `bootstrap_values` | String representing all rules for initialization | `nil` | +| `data_store` | Class extending IDataStore for common data store (e.g. Redis) | `nil` | +| `disable_diagnostics_logging` | Should diagnostics be logged | `false` | +| `disable_evaluation_memoization` | Disable memoization of evaluation results | `false` | +| `disable_idlists_sync` | Disable background syncing for id lists | `false` | +| `disable_rulesets_sync` | Disable background syncing for rulesets | `false` | +| `disable_sorbet_logging_handlers` | Disable Sorbet type safety logging | `false` | | `download_config_specs_url` | URL for download_config_specs | `https://api.statsigcdn.com/v2/download_config_specs/` | -| `log_event_url` | URL for log_event | `https://statsigapi.net/v1/log_event` | +| `environment` | Hash for environment variables (e.g. `{ "tier" => "development" }`) | `nil` | | `get_id_lists_url` | URL for get_id_lists | `https://statsigapi.net/v1/get_id_lists` | -| `rulesets_sync_interval` | Interval (in seconds) to poll for configuration changes | `10` | +| `idlist_threadpool_size` | Number of threads for syncing IDLists | `3` | | `idlists_sync_interval` | Interval (in seconds) to poll for id list changes | `60` | -| `disable_rulesets_sync` | Disable background syncing for rulesets | `false` | -| `disable_idlists_sync` | Disable background syncing for id lists | `false` | -| `logging_interval_seconds` | How often to flush logs to Statsig | `60` | -| `logging_max_buffer_size` | Maximum number of events to batch before flushing | `1000` | | `local_mode` | Restricts the SDK to not issue any network requests | `false` | -| `bootstrap_values` | String representing all rules for initialization | `nil` | -| `rules_updated_callback` | Callback function called when rulesets are updated | `nil` | -| `data_store` | Class extending IDataStore for common data store (e.g. Redis) | `nil` | -| `idlist_threadpool_size` | Number of threads for syncing IDLists | `3` | +| `log_event_url` | URL for log_event | `https://statsigapi.net/v1/log_event` | | `logger_threadpool_size` | Number of threads for posting event logs | `3` | -| `disable_diagnostics_logging` | Should diagnostics be logged | `false` | -| `disable_sorbet_logging_handlers` | Disable Sorbet type safety logging | `false` | +| `logging_interval_seconds` | How often to flush logs to Statsig | `60` | +| `logging_max_buffer_size` | Maximum number of events to batch before flushing | `1000` | | `network_timeout` | Number of seconds before a network call is timed out | `30` | -| `post_logs_retry_limit` | Number of times to retry failed log events | `3` | | `post_logs_retry_backoff` | Backoff time/function between retries | `nil` | -| `user_persistent_storage` | Storage adapter for persisted values | `nil` | -| `disable_evaluation_memoization` | Disable memoization of evaluation results | `false` | +| `post_logs_retry_limit` | Number of times to retry failed log events | `3` | +| `rules_updated_callback` | Callback function called when rulesets are updated | `nil` | | `ruleset_id_list_retry_limit` | Number of times to retry fetching rulesets and id lists | `3` | +| `rulesets_sync_interval` | Interval (in seconds) to poll for configuration changes | `10` | +| `user_persistent_storage` | Storage adapter for persisted values | `nil` | ## Testing diff --git a/lib/statsig_options.rb b/lib/statsig_options.rb index fcf174c..d945b48 100644 --- a/lib/statsig_options.rb +++ b/lib/statsig_options.rb @@ -5,125 +5,131 @@ # Configuration options for the Statsig SDK. class StatsigOptions - # Hash you can use to set environment variables that apply to all of your users in - # the same session and will be used for targeting purposes. - # eg. { "tier" => "development" } - attr_accessor :environment + # A string that represents all rules for all feature gates, dynamic configs and experiments. + # It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs + # into network issue or Statsig is down temporarily. + attr_accessor :bootstrap_values + + # A class that extends IDataStore. Can be used to provide values from a + # common data store (like Redis) to initialize the Statsig SDK. + attr_accessor :data_store + + # Should diagnostics be logged. These include performance metrics for initialize. + # default: false + attr_accessor :disable_diagnostics_logging + + # Disable memoization of evaluation results. When true, each evaluation will be performed fresh. + # default: false + attr_accessor :disable_evaluation_memoization + + # Disable background syncing for id lists + attr_accessor :disable_idlists_sync + + # Disable background syncing for rulesets + attr_accessor :disable_rulesets_sync + + # Statsig utilizes Sorbet (https://sorbet.org) to ensure type safety of the SDK. This includes logging + # to console when errors are detected. You can disable this logging by setting this flag to true. + # default: false + attr_accessor :disable_sorbet_logging_handlers # The url used specifically to call download_config_specs. attr_accessor :download_config_specs_url - # The url used specifically to call log_event. - attr_accessor :log_event_url + # Hash you can use to set environment variables that apply to all of your users in + # the same session and will be used for targeting purposes. + # eg. { "tier" => "development" } + attr_accessor :environment # The url used specifically to call get_id_lists. attr_accessor :get_id_lists_url - # The interval (in seconds) to poll for changes to your Statsig configuration - # default: 10s - attr_accessor :rulesets_sync_interval + # The number of threads allocated to syncing IDLists. + # default: 3 + attr_accessor :idlist_threadpool_size # The interval (in seconds) to poll for changes to your id lists # default: 60s attr_accessor :idlists_sync_interval - # Disable background syncing for rulesets - attr_accessor :disable_rulesets_sync - - # Disable background syncing for id lists - attr_accessor :disable_idlists_sync - - # How often to flush logs to Statsig - # default: 60s - attr_accessor :logging_interval_seconds - - # The maximum number of events to batch before flushing logs to the server - # default: 1000 - attr_accessor :logging_max_buffer_size - # Restricts the SDK to not issue any network requests and only respond with default values (or local overrides) # default: false attr_accessor :local_mode - # A string that represents all rules for all feature gates, dynamic configs and experiments. - # It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs - # into network issue or Statsig is down temporarily. - attr_accessor :bootstrap_values - - # A callback function that will be called anytime the rulesets are updated. - attr_accessor :rules_updated_callback - - # A class that extends IDataStore. Can be used to provide values from a - # common data store (like Redis) to initialize the Statsig SDK. - attr_accessor :data_store - - # The number of threads allocated to syncing IDLists. - # default: 3 - attr_accessor :idlist_threadpool_size + # The url used specifically to call log_event. + attr_accessor :log_event_url # The number of threads allocated to posting event logs. # default: 3 attr_accessor :logger_threadpool_size - # Should diagnostics be logged. These include performance metrics for initialize. - # default: false - attr_accessor :disable_diagnostics_logging + # How often to flush logs to Statsig + # default: 60s + attr_accessor :logging_interval_seconds - # Statsig utilizes Sorbet (https://sorbet.org) to ensure type safety of the SDK. This includes logging - # to console when errors are detected. You can disable this logging by setting this flag to true. - # default: false - attr_accessor :disable_sorbet_logging_handlers + # The maximum number of events to batch before flushing logs to the server + # default: 1000 + attr_accessor :logging_max_buffer_size # Number of seconds before a network call is timed out # default: 30s attr_accessor :network_timeout - # Number of times to retry sending a batch of failed log events - attr_accessor :post_logs_retry_limit - # The number of seconds, or a function that returns the number of seconds based on the number of retries remaining # which overrides the default backoff time between retries attr_accessor :post_logs_retry_backoff - # A storage adapter for persisted values. Can be used for sticky bucketing users in experiments. - # Implements Statsig::Interfaces::IUserPersistentStorage. - attr_accessor :user_persistent_storage + # Number of times to retry sending a batch of failed log events + attr_accessor :post_logs_retry_limit - # Disable memoization of evaluation results. When true, each evaluation will be performed fresh. - # default: false - attr_accessor :disable_evaluation_memoization + # A callback function that will be called anytime the rulesets are updated. + attr_accessor :rules_updated_callback # Number of times to retry fetching rulesets and id lists # default: 3 attr_accessor :ruleset_id_list_retry_limit + # The interval (in seconds) to poll for changes to your Statsig configuration + # default: 10s + attr_accessor :rulesets_sync_interval + + # A storage adapter for persisted values. Can be used for sticky bucketing users in experiments. + # Implements Statsig::Interfaces::IUserPersistentStorage. + attr_accessor :user_persistent_storage + def initialize( - environment = nil, + bootstrap_values: nil, + data_store: nil, + disable_diagnostics_logging: false, + disable_evaluation_memoization: false, + disable_idlists_sync: false, + disable_rulesets_sync: false, + disable_sorbet_logging_handlers: false, download_config_specs_url: nil, - log_event_url: nil, + environment: nil, get_id_lists_url: nil, - rulesets_sync_interval: 10, + idlist_threadpool_size: 3, idlists_sync_interval: 60, - disable_rulesets_sync: false, - disable_idlists_sync: false, - logging_interval_seconds: 60, - logging_max_buffer_size: 1000, local_mode: false, - bootstrap_values: nil, - rules_updated_callback: nil, - data_store: nil, - idlist_threadpool_size: 3, + log_event_url: nil, logger_threadpool_size: 3, - disable_diagnostics_logging: false, - disable_sorbet_logging_handlers: false, + logging_interval_seconds: 60, + logging_max_buffer_size: 1000, network_timeout: 30, - post_logs_retry_limit: 3, post_logs_retry_backoff: nil, - user_persistent_storage: nil, - disable_evaluation_memoization: false, - ruleset_id_list_retry_limit: 3 + post_logs_retry_limit: 3, + rules_updated_callback: nil, + ruleset_id_list_retry_limit: 3, + rulesets_sync_interval: 10, + user_persistent_storage: nil ) - @environment = environment.is_a?(Hash) ? environment : nil + @bootstrap_values = bootstrap_values + @data_store = data_store + @disable_diagnostics_logging = disable_diagnostics_logging + @disable_evaluation_memoization = disable_evaluation_memoization + @disable_idlists_sync = disable_idlists_sync + @disable_rulesets_sync = disable_rulesets_sync + @disable_sorbet_logging_handlers = disable_sorbet_logging_handlers dcs_url = download_config_specs_url || 'https://api.statsigcdn.com/v2/download_config_specs/' unless dcs_url.end_with?('/') @@ -131,27 +137,21 @@ def initialize( end @download_config_specs_url = dcs_url - @log_event_url = log_event_url || 'https://statsigapi.net/v1/log_event' + @environment = environment.is_a?(Hash) ? environment : nil @get_id_lists_url = get_id_lists_url || 'https://statsigapi.net/v1/get_id_lists' - @rulesets_sync_interval = rulesets_sync_interval + @idlist_threadpool_size = idlist_threadpool_size @idlists_sync_interval = idlists_sync_interval - @disable_rulesets_sync = disable_rulesets_sync - @disable_idlists_sync = disable_idlists_sync - @logging_interval_seconds = logging_interval_seconds - @logging_max_buffer_size = [logging_max_buffer_size, 1000].min @local_mode = local_mode - @bootstrap_values = bootstrap_values - @rules_updated_callback = rules_updated_callback - @data_store = data_store - @idlist_threadpool_size = idlist_threadpool_size + @log_event_url = log_event_url || 'https://statsigapi.net/v1/log_event' @logger_threadpool_size = logger_threadpool_size - @disable_diagnostics_logging = disable_diagnostics_logging - @disable_sorbet_logging_handlers = disable_sorbet_logging_handlers + @logging_interval_seconds = logging_interval_seconds + @logging_max_buffer_size = [logging_max_buffer_size, 1000].min @network_timeout = network_timeout - @post_logs_retry_limit = post_logs_retry_limit @post_logs_retry_backoff = post_logs_retry_backoff - @user_persistent_storage = user_persistent_storage - @disable_evaluation_memoization = disable_evaluation_memoization + @post_logs_retry_limit = post_logs_retry_limit + @rules_updated_callback = rules_updated_callback @ruleset_id_list_retry_limit = ruleset_id_list_retry_limit + @rulesets_sync_interval = rulesets_sync_interval + @user_persistent_storage = user_persistent_storage end end