diff --git a/README.md b/README.md index aaf3e71..968d4da 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 | +| --- | --- | --- | +| `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/` | +| `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` | +| `idlist_threadpool_size` | Number of threads for syncing IDLists | `3` | +| `idlists_sync_interval` | Interval (in seconds) to poll for id list changes | `60` | +| `local_mode` | Restricts the SDK to not issue any network requests | `false` | +| `log_event_url` | URL for log_event | `https://statsigapi.net/v1/log_event` | +| `logger_threadpool_size` | Number of threads for posting event logs | `3` | +| `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_backoff` | Backoff time/function between retries | `nil` | +| `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 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. 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..d945b48 100644 --- a/lib/statsig_options.rb +++ b/lib/statsig_options.rb @@ -5,119 +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 + # Number of times to retry sending a batch of failed log events + attr_accessor :post_logs_retry_limit + + # 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 - # Disable memoization of evaluation results. When true, each evaluation will be performed fresh. - # default: false - attr_accessor :disable_evaluation_memoization - 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, - network_timeout: nil, - post_logs_retry_limit: 3, + logging_interval_seconds: 60, + logging_max_buffer_size: 1000, + network_timeout: 30, post_logs_retry_backoff: nil, - user_persistent_storage: nil, - disable_evaluation_memoization: false + 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?('/') @@ -125,26 +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 + @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 - @disable_evaluation_memoization = disable_evaluation_memoization 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 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)