Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions lib/network.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
175 changes: 91 additions & 84 deletions lib/statsig_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,146 +5,153 @@
# 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?('/')
dcs_url += '/'
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
23 changes: 23 additions & 0 deletions test/test_network.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions test/test_network_timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down