statsig 1.26.0 → 1.27.0

Sign up to get free protection for your applications and to get access to all the features.
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: []