statsig 1.26.0 → 1.27.0

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: 42f5328314de8727a2baaf89da54999627a5505ad95cd5ab816877741e02dd55
4
- data.tar.gz: f6af1d20f540ceae4e24c70bdc702fbd9db3acc38776b71ecc50ded6ef40f67d
3
+ metadata.gz: f4d0e32be6be9e6110bf6237eb4ba33728dfde66385272bb9a15412662455873
4
+ data.tar.gz: '03408269143b235e02fc8be9f650ed2d4fc778ddf3cc7ffb7242ed9b9494ab73'
5
5
  SHA512:
6
- metadata.gz: 8c4375a325bf7dd1afaa584951b31eca2fd19422301b72397729fdca89e1ef8ae9b7a979aff562889cf49fc98fcd7db3edfc022ca66eb063b2680645c90df2db
7
- data.tar.gz: ab23702707825b1ed5ada0df1982d765b97b75e8d813aeeaa9f24ae31974bb0cafa58249e82cc041eaa845bea9ee7639189c8778f8e21e0908b7ec3174d41661
6
+ metadata.gz: dac08983696816714e346ff1381fa2e83847375d5cee5bd33eb533a83e92be53f33a8fc5217a63b6856f723ca9063a631d4beee260bff95673329f6bfcdd5097
7
+ data.tar.gz: 8ad5c492697b2b8cb7c06eac670b03230190dbbe9210f4f8ab9bd951bd73ed0c3c91f45353fc4466d2d1295c09366ae95cae109521d98d1041c629d61202c092
data/lib/diagnostics.rb CHANGED
@@ -7,14 +7,18 @@ module Statsig
7
7
  extend T::Sig
8
8
 
9
9
  sig { returns(String) }
10
- attr_reader :context
10
+ attr_accessor :context
11
11
 
12
12
  sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
13
13
  attr_reader :markers
14
14
 
15
+ sig { returns(T::Hash[String, Numeric]) }
16
+ attr_accessor :sample_rates
17
+
15
18
  def initialize(context)
16
19
  @context = context
17
20
  @markers = []
21
+ @sample_rates = {}
18
22
  end
19
23
 
20
24
  sig do
@@ -65,12 +69,27 @@ module Statsig
65
69
  }
66
70
  end
67
71
 
72
+ def serialize_with_sampling
73
+ marker_keys = @markers.map { |e| e[:key] }
74
+ unique_marker_keys = marker_keys.uniq { |e| e }
75
+ sampled_marker_keys = unique_marker_keys.select do |key|
76
+ @sample_rates.key?(key) && !self.class.sample(@sample_rates[key])
77
+ end
78
+ final_markers = @markers.select do |marker|
79
+ !sampled_marker_keys.include?(marker[:key])
80
+ end
81
+ {
82
+ context: @context.clone,
83
+ markers: final_markers
84
+ }
85
+ end
86
+
68
87
  def clear_markers
69
88
  @markers.clear
70
89
  end
71
90
 
72
- def self.sample(rate)
73
- rand(rate).zero?
91
+ def self.sample(rate_over_ten_thousand)
92
+ rand * 10_000 < rate_over_ten_thousand
74
93
  end
75
94
 
76
95
  class Context
@@ -18,7 +18,7 @@ module Statsig
18
18
  def capture(task:, recover: -> {}, caller: nil)
19
19
  begin
20
20
  res = task.call
21
- rescue StandardError => e
21
+ rescue StandardError, SystemStackError => e
22
22
  if e.is_a?(Statsig::UninitializedError) || e.is_a?(Statsig::ValueError)
23
23
  raise e
24
24
  end
data/lib/network.rb CHANGED
@@ -23,7 +23,6 @@ module Statsig
23
23
  extend T::Sig
24
24
 
25
25
  sig { params(server_secret: String, options: StatsigOptions, backoff_mult: Integer).void }
26
-
27
26
  def initialize(server_secret, options, backoff_mult = 10)
28
27
  super()
29
28
  URIHelper.initialize(options)
@@ -36,14 +35,14 @@ module Statsig
36
35
  @session_id = SecureRandom.uuid
37
36
  @connection_pool = ConnectionPool.new(size: 3) do
38
37
  meta = Statsig.get_statsig_metadata
39
- client = HTTP.headers(
38
+ client = HTTP.use(:auto_inflate).headers(
40
39
  {
41
40
  'STATSIG-API-KEY' => @server_secret,
42
- 'STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s,
43
41
  'STATSIG-SERVER-SESSION-ID' => @session_id,
44
42
  'Content-Type' => 'application/json; charset=UTF-8',
45
43
  'STATSIG-SDK-TYPE' => meta['sdkType'],
46
- 'STATSIG-SDK-VERSION' => meta['sdkVersion']
44
+ 'STATSIG-SDK-VERSION' => meta['sdkVersion'],
45
+ 'Accept-Encoding' => 'gzip'
47
46
  }
48
47
  ).accept(:json)
49
48
  if @timeout
@@ -58,7 +57,6 @@ module Statsig
58
57
  params(endpoint: String, body: String, retries: Integer, backoff: Integer)
59
58
  .returns([T.any(HTTP::Response, NilClass), T.any(StandardError, NilClass)])
60
59
  end
61
-
62
60
  def post_helper(endpoint, body, retries = 0, backoff = 1)
63
61
  if @local_mode
64
62
  return nil, nil
data/lib/spec_store.rb CHANGED
@@ -74,8 +74,8 @@ module Statsig
74
74
  get_id_lists_from_network
75
75
  end
76
76
 
77
- @config_sync_thread = sync_config_specs
78
- @id_lists_sync_thread = sync_id_lists
77
+ @config_sync_thread = spawn_sync_config_specs_thread
78
+ @id_lists_sync_thread = spawn_sync_id_lists_thread
79
79
  end
80
80
 
81
81
  def is_ready_for_checks
@@ -148,6 +148,26 @@ module Statsig
148
148
  end
149
149
  end
150
150
 
151
+ def sync_config_specs
152
+ @diagnostics.context = 'config_sync'
153
+ if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_KEY)
154
+ load_config_specs_from_storage_adapter
155
+ else
156
+ download_config_specs
157
+ end
158
+ @logger.log_diagnostics_event(@diagnostics)
159
+ end
160
+
161
+ def sync_id_lists
162
+ @diagnostics.context = 'config_sync'
163
+ if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::ID_LISTS_KEY)
164
+ get_id_lists_from_adapter
165
+ else
166
+ get_id_lists_from_network
167
+ end
168
+ @logger.log_diagnostics_event(@diagnostics)
169
+ end
170
+
151
171
  private
152
172
 
153
173
  def load_config_specs_from_storage_adapter
@@ -173,35 +193,31 @@ module Statsig
173
193
  @options.data_store.set(Interfaces::IDataStore::CONFIG_SPECS_KEY, specs_string)
174
194
  end
175
195
 
176
- def sync_config_specs
196
+ def spawn_sync_config_specs_thread
197
+ if @options.disable_rulesets_sync
198
+ return nil
199
+ end
200
+
177
201
  Thread.new do
178
202
  @error_boundary.capture(task: lambda {
179
203
  loop do
180
- @diagnostics = Diagnostics.new('config_sync')
181
204
  sleep @options.rulesets_sync_interval
182
- if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_KEY)
183
- load_config_specs_from_storage_adapter
184
- else
185
- download_config_specs
186
- end
187
- @logger.log_diagnostics_event(@diagnostics)
205
+ sync_config_specs
188
206
  end
189
207
  })
190
208
  end
191
209
  end
192
210
 
193
- def sync_id_lists
211
+ def spawn_sync_id_lists_thread
212
+ if @options.disable_idlists_sync
213
+ return nil
214
+ end
215
+
194
216
  Thread.new do
195
217
  @error_boundary.capture(task: lambda {
196
218
  loop do
197
- @diagnostics = Diagnostics.new('config_sync')
198
219
  sleep @id_lists_sync_interval
199
- if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::ID_LISTS_KEY)
200
- get_id_lists_from_adapter
201
- else
202
- get_id_lists_from_network
203
- end
204
- @logger.log_diagnostics_event(@diagnostics)
220
+ sync_id_lists
205
221
  end
206
222
  })
207
223
  end
@@ -222,6 +238,7 @@ module Statsig
222
238
  if e.nil?
223
239
  unless response.nil?
224
240
  tracker = @diagnostics.track('download_config_specs', 'process')
241
+
225
242
  if process_specs(response.body.to_s)
226
243
  @init_reason = EvaluationReason::NETWORK
227
244
  end
@@ -263,6 +280,7 @@ module Statsig
263
280
  specs_json['feature_gates'].each { |gate| new_gates[gate['name']] = gate }
264
281
  specs_json['dynamic_configs'].each { |config| new_configs[config['name']] = config }
265
282
  specs_json['layer_configs'].each { |layer| new_layers[layer['name']] = layer }
283
+ specs_json['diagnostics']&.each { |key, value| @diagnostics.sample_rates[key] = value }
266
284
 
267
285
  if specs_json['layers'].is_a?(Hash)
268
286
  specs_json['layers'].each { |layer_name, experiments|
@@ -276,8 +294,6 @@ module Statsig
276
294
  @specs[:experiment_to_layer] = new_exp_to_layer
277
295
  @specs[:sdk_keys_to_app_ids] = specs_json['sdk_keys_to_app_ids'] || {}
278
296
 
279
- specs_json['diagnostics']
280
-
281
297
  unless from_adapter
282
298
  save_config_specs_to_storage_adapter(specs_string)
283
299
  end
@@ -466,5 +482,6 @@ module Statsig
466
482
  return false
467
483
  end
468
484
  end
485
+
469
486
  end
470
487
  end
data/lib/statsig.rb CHANGED
@@ -176,6 +176,16 @@ module Statsig
176
176
  @shared_instance&.log_event(user, event_name, value, metadata)
177
177
  end
178
178
 
179
+ def self.sync_rulesets
180
+ ensure_initialized
181
+ @shared_instance&.manually_sync_rulesets
182
+ end
183
+
184
+ def self.sync_idlists
185
+ ensure_initialized
186
+ @shared_instance&.manually_sync_idlists
187
+ end
188
+
179
189
  sig { void }
180
190
  ##
181
191
  # Stops all Statsig activity and flushes any pending events.
@@ -229,7 +239,7 @@ module Statsig
229
239
  def self.get_statsig_metadata
230
240
  {
231
241
  'sdkType' => 'ruby-server',
232
- 'sdkVersion' => '1.26.0',
242
+ 'sdkVersion' => '1.27.0',
233
243
  }
234
244
  end
235
245
 
@@ -185,6 +185,18 @@ class StatsigDriver
185
185
  }, caller: __method__.to_s)
186
186
  end
187
187
 
188
+ def manually_sync_rulesets
189
+ @err_boundary.capture(task: lambda {
190
+ @evaluator.spec_store.sync_config_specs
191
+ }, caller: __method__.to_s)
192
+ end
193
+
194
+ def manually_sync_idlists
195
+ @err_boundary.capture(task: lambda {
196
+ @evaluator.spec_store.sync_id_lists
197
+ }, caller: __method__.to_s)
198
+ end
199
+
188
200
  def shutdown
189
201
  @err_boundary.capture(task: lambda {
190
202
  @shutdown = true
@@ -231,7 +243,7 @@ class StatsigDriver
231
243
 
232
244
  def run_with_diagnostics(task:, caller:)
233
245
  diagnostics = nil
234
- if Statsig::Diagnostics::API_CALL_KEYS.include?(caller) && Statsig::Diagnostics.sample(10_000)
246
+ if Statsig::Diagnostics::API_CALL_KEYS.include?(caller) && Statsig::Diagnostics.sample(1)
235
247
  diagnostics = Statsig::Diagnostics.new('api_call')
236
248
  tracker = diagnostics.track(caller)
237
249
  end
@@ -100,11 +100,14 @@ module Statsig
100
100
 
101
101
  def log_diagnostics_event(diagnostics, user = nil)
102
102
  return if @options.disable_diagnostics_logging
103
- return if diagnostics.nil? || diagnostics.markers.empty?
103
+ return if diagnostics.nil?
104
104
 
105
105
  event = StatsigEvent.new($diagnostics_event)
106
106
  event.user = user
107
- event.metadata = diagnostics.serialize
107
+ serialized = diagnostics.serialize_with_sampling
108
+ return if serialized[:markers].empty?
109
+
110
+ event.metadata = serialized
108
111
  log_event(event)
109
112
  diagnostics.clear_markers
110
113
  end
@@ -34,6 +34,14 @@ class StatsigOptions
34
34
  # default: 60s
35
35
  attr_accessor :idlists_sync_interval
36
36
 
37
+ # Disable background syncing for rulesets
38
+ sig { returns(T::Boolean) }
39
+ attr_accessor :disable_rulesets_sync
40
+
41
+ # Disable background syncing for id lists
42
+ sig { returns(T::Boolean) }
43
+ attr_accessor :disable_idlists_sync
44
+
37
45
  sig { returns(T.any(Float, Integer)) }
38
46
  # How often to flush logs to Statsig
39
47
  # default: 60s
@@ -105,6 +113,8 @@ class StatsigOptions
105
113
  api_url_download_config_specs: T.any(String, NilClass),
106
114
  rulesets_sync_interval: T.any(Float, Integer),
107
115
  idlists_sync_interval: T.any(Float, Integer),
116
+ disable_rulesets_sync: T::Boolean,
117
+ disable_idlists_sync: T::Boolean,
108
118
  logging_interval_seconds: T.any(Float, Integer),
109
119
  logging_max_buffer_size: Integer,
110
120
  local_mode: T::Boolean,
@@ -127,6 +137,8 @@ class StatsigOptions
127
137
  api_url_download_config_specs: nil,
128
138
  rulesets_sync_interval: 10,
129
139
  idlists_sync_interval: 60,
140
+ disable_rulesets_sync: false,
141
+ disable_idlists_sync: false,
130
142
  logging_interval_seconds: 60,
131
143
  logging_max_buffer_size: 1000,
132
144
  local_mode: false,
@@ -145,6 +157,8 @@ class StatsigOptions
145
157
  @api_url_download_config_specs = api_url_download_config_specs
146
158
  @rulesets_sync_interval = rulesets_sync_interval
147
159
  @idlists_sync_interval = idlists_sync_interval
160
+ @disable_rulesets_sync = disable_rulesets_sync
161
+ @disable_idlists_sync = disable_idlists_sync
148
162
  @logging_interval_seconds = logging_interval_seconds
149
163
  @logging_max_buffer_size = [logging_max_buffer_size, 1000].min
150
164
  @local_mode = local_mode
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: 1.26.0
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-27 00:00:00.000000000 Z
11
+ date: 2023-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -349,7 +349,7 @@ homepage: https://rubygems.org/gems/statsig
349
349
  licenses:
350
350
  - ISC
351
351
  metadata: {}
352
- post_install_message:
352
+ post_install_message:
353
353
  rdoc_options: []
354
354
  require_paths:
355
355
  - lib
@@ -364,8 +364,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
364
364
  - !ruby/object:Gem::Version
365
365
  version: '0'
366
366
  requirements: []
367
- rubygems_version: 3.3.7
368
- signing_key:
367
+ rubygems_version: 3.2.33
368
+ signing_key:
369
369
  specification_version: 4
370
370
  summary: Statsig server SDK for Ruby
371
371
  test_files: []