statsig 1.33.4 → 1.34.1

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: 6b2947b68bfada4686360dc5aab60a460129a3ca5e4c63d6f2107af68788d2a8
4
- data.tar.gz: 98e56828d1f31ec18afc0a2f46dbcab90c2a976dc95ef1db9ead5060b240952f
3
+ metadata.gz: 3c2aff544b63f731dd32e2d2a498f83b91b66222f42d507f7ad483ce11b8cadf
4
+ data.tar.gz: f7c1ec377a7ff7861696cb250817262bf999c8ba81904e6ed663a6e40fee9da0
5
5
  SHA512:
6
- metadata.gz: e1f11434fb5ec030500429cdfd11146fe845568498cded4917e1f412ca50a86beb03f937febcaeceaf8523dacaa550b9024384dc58b0b3c9b2619d37041e0df0
7
- data.tar.gz: d64fd8980ca648626ecdcd0095d4964aa531d24d7acaa7c07dd70013a32d0f7c5c548602e9578044c0567c0c2ac948f5dd8bd499c2178323b0ae176d0925a82c
6
+ metadata.gz: 1d42a4f31466ad9598bfa6e9fc59d768fe3821345ff2b1964385fef7a32a480819937994443145bb9b900c6a404611c69a80bc0399fd72c62b726a08a800dd05
7
+ data.tar.gz: d6b3a2d232962779a8ab5de1d380bc89b28c64ee2ea5ca22c6d140a563cd096f9c5a965de712592d7c72007cf65c9bb853b86e0eaa86c78e49f4207395149759
@@ -93,22 +93,12 @@ module Statsig
93
93
  result[:rule_id] = eval_result.rule_id
94
94
 
95
95
  if include_exposures
96
- result[:secondary_exposures] = clean_exposures(eval_result.secondary_exposures)
96
+ result[:secondary_exposures] = eval_result.secondary_exposures
97
97
  end
98
98
 
99
99
  [hashed_name, result]
100
100
  end
101
101
 
102
- def self.clean_exposures(exposures)
103
- seen = {}
104
- exposures.reject do |exposure|
105
- key = "#{exposure[:gate]}|#{exposure[:gateValue]}|#{exposure[:ruleID]}}"
106
- should_reject = seen[key]
107
- seen[key] = true
108
- should_reject == true
109
- end
110
- end
111
-
112
102
  def self.populate_experiment_fields(config_name, config_spec, eval_result, result, evaluator)
113
103
  result[:is_user_in_experiment] = eval_result.is_experiment_group
114
104
  result[:is_experiment_active] = config_spec.is_active == true
@@ -143,7 +133,7 @@ module Statsig
143
133
  end
144
134
 
145
135
  if include_exposures
146
- result[:undelegated_secondary_exposures] = clean_exposures(eval_result.undelegated_sec_exps || [])
136
+ result[:undelegated_secondary_exposures] = eval_result.undelegated_sec_exps || []
147
137
  end
148
138
  end
149
139
 
@@ -5,9 +5,10 @@ $endpoint = 'https://statsigapi.net/v1/sdk_exception'
5
5
  module Statsig
6
6
  class ErrorBoundary
7
7
 
8
- def initialize(sdk_key)
8
+ def initialize(sdk_key, local_mode = false)
9
9
  @sdk_key = sdk_key
10
10
  @seen = Set.new
11
+ @local_mode = local_mode
11
12
  end
12
13
 
13
14
  def capture(recover: -> {}, caller: nil)
@@ -26,11 +27,12 @@ module Statsig
26
27
  return res
27
28
  end
28
29
 
29
- private
30
-
31
- def log_exception(exception, tag: nil)
30
+ def log_exception(exception, tag: nil, extra: {}, force: false)
31
+ if @local_mode
32
+ return
33
+ end
32
34
  name = exception.class.name
33
- if @seen.include?(name)
35
+ if @seen.include?(name) && !force
34
36
  return
35
37
  end
36
38
 
@@ -51,7 +53,8 @@ module Statsig
51
53
  'message' => exception.message
52
54
  }.to_s,
53
55
  'statsigMetadata' => meta,
54
- 'tag' => tag
56
+ 'tag' => tag,
57
+ 'extra' => extra
55
58
  }
56
59
  http.post($endpoint, body: JSON.generate(body))
57
60
  rescue StandardError
data/lib/evaluator.rb CHANGED
@@ -71,7 +71,7 @@ module Statsig
71
71
  return nil
72
72
  end
73
73
 
74
- def check_gate(user, gate_name, end_result, ignore_local_overrides: false)
74
+ def check_gate(user, gate_name, end_result, ignore_local_overrides: false, is_nested: false)
75
75
  unless ignore_local_overrides
76
76
  local_override = lookup_gate_override(gate_name)
77
77
  unless local_override.nil?
@@ -96,7 +96,7 @@ module Statsig
96
96
  return
97
97
  end
98
98
 
99
- eval_spec(user, @spec_store.get_gate(gate_name), end_result)
99
+ eval_spec(user, @spec_store.get_gate(gate_name), end_result, is_nested: is_nested)
100
100
  end
101
101
 
102
102
  def get_config(user, config_name, end_result, user_persisted_values: nil, ignore_local_overrides: false)
@@ -201,6 +201,9 @@ module Statsig
201
201
  if @spec_store.is_ready_for_checks == false
202
202
  return nil
203
203
  end
204
+ if @spec_store.last_config_sync_time == 0
205
+ return nil
206
+ end
204
207
 
205
208
  evaluated_keys = {}
206
209
  if user.user_id.nil? == false
@@ -209,8 +212,8 @@ module Statsig
209
212
 
210
213
  if user.custom_ids.nil? == false
211
214
  evaluated_keys[:customIDs] = user.custom_ids
212
- end
213
-
215
+ end
216
+ meta = Statsig.get_statsig_metadata
214
217
  {
215
218
  feature_gates: Statsig::ResponseFormatter
216
219
  .get_responses(@spec_store.gates, self, user, client_sdk_key, hash_algo, include_local_overrides: include_local_overrides),
@@ -222,9 +225,10 @@ module Statsig
222
225
  has_updates: true,
223
226
  generator: Const::STATSIG_RUBY_SDK,
224
227
  evaluated_keys: evaluated_keys,
225
- time: 0,
228
+ time: @spec_store.last_config_sync_time,
226
229
  hash_used: hash_algo,
227
- user_hash: user.to_hash_without_stable_id
230
+ user: user.serialize(false),
231
+ sdkInfo: {sdkType: meta["sdkType"], sdkVersion: meta["sdkVersion"]},
228
232
  }
229
233
  end
230
234
 
@@ -277,9 +281,9 @@ module Statsig
277
281
  @config_overrides.clear
278
282
  end
279
283
 
280
- def eval_spec(user, config, end_result)
284
+ def eval_spec(user, config, end_result, is_nested: false)
281
285
  unless config.enabled
282
- finalize_eval_result(config, end_result, did_pass: false, rule: nil)
286
+ finalize_eval_result(config, end_result, did_pass: false, rule: nil, is_nested: is_nested)
283
287
  return
284
288
  end
285
289
 
@@ -288,21 +292,22 @@ module Statsig
288
292
 
289
293
  if end_result.gate_value
290
294
  if eval_delegate(config.name, user, rule, end_result)
295
+ finalize_secondary_exposures(end_result)
291
296
  return
292
297
  end
293
298
 
294
299
  pass = eval_pass_percent(user, rule, config.salt)
295
- finalize_eval_result(config, end_result, did_pass: pass, rule: rule)
300
+ finalize_eval_result(config, end_result, did_pass: pass, rule: rule, is_nested: is_nested)
296
301
  return
297
302
  end
298
303
  end
299
304
 
300
- finalize_eval_result(config, end_result, did_pass: false, rule: nil)
305
+ finalize_eval_result(config, end_result, did_pass: false, rule: nil, is_nested: is_nested)
301
306
  end
302
307
 
303
308
  private
304
309
 
305
- def finalize_eval_result(config, end_result, did_pass:, rule:)
310
+ def finalize_eval_result(config, end_result, did_pass:, rule:, is_nested: false)
306
311
  end_result.id_type = config.id_type
307
312
  end_result.target_app_ids = config.target_app_ids
308
313
  end_result.gate_value = did_pass
@@ -326,6 +331,29 @@ module Statsig
326
331
  @spec_store.init_reason
327
332
  )
328
333
  end
334
+
335
+ unless is_nested
336
+ finalize_secondary_exposures(end_result)
337
+ end
338
+ end
339
+
340
+ def finalize_secondary_exposures(end_result)
341
+ end_result.secondary_exposures = clean_exposures(end_result.secondary_exposures)
342
+ end_result.undelegated_sec_exps = clean_exposures(end_result.undelegated_sec_exps)
343
+ end
344
+
345
+ def clean_exposures(exposures)
346
+ seen = {}
347
+ exposures.reject do |exposure|
348
+ if exposure[:gate].to_s.start_with?('segment:')
349
+ should_reject = true
350
+ else
351
+ key = "#{exposure[:gate]}|#{exposure[:gateValue]}|#{exposure[:ruleID]}}"
352
+ should_reject = seen[key]
353
+ seen[key] = true
354
+ end
355
+ should_reject == true
356
+ end
329
357
  end
330
358
 
331
359
  def eval_rule(user, rule, end_result)
@@ -358,7 +386,7 @@ module Statsig
358
386
 
359
387
  end_result.undelegated_sec_exps = end_result.secondary_exposures.dup
360
388
 
361
- eval_spec(user, config, end_result)
389
+ eval_spec(user, config, end_result, is_nested: true)
362
390
 
363
391
  end_result.name = name
364
392
  end_result.config_delegate = delegate
@@ -380,7 +408,7 @@ module Statsig
380
408
  when :public
381
409
  return true
382
410
  when :fail_gate, :pass_gate
383
- check_gate(user, target, end_result)
411
+ check_gate(user, target, end_result, is_nested: true)
384
412
  gate_value = end_result.gate_value
385
413
 
386
414
  unless end_result.disable_exposures
data/lib/network.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'http'
2
2
  require 'json'
3
3
  require 'securerandom'
4
+ require 'zlib'
4
5
 
5
6
  require 'uri_helper'
6
7
  require 'connection_pool'
@@ -58,15 +59,14 @@ module Statsig
58
59
  request(:GET, endpoint, nil, retries, backoff)
59
60
  end
60
61
 
61
- def post(endpoint, body, retries = 0, backoff = 1)
62
- request(:POST, endpoint, body, retries, backoff)
62
+ def post(endpoint, body, retries = 0, backoff = 1, zipped = false, event_count = 0)
63
+ request(:POST, endpoint, body, retries, backoff, zipped, event_count)
63
64
  end
64
65
 
65
- def request(method, endpoint, body, retries = 0, backoff = 1)
66
+ def request(method, endpoint, body, retries = 0, backoff = 1, zipped = false, event_count = 0)
66
67
  if @local_mode
67
68
  return nil, nil
68
69
  end
69
-
70
70
  backoff_adjusted = backoff > 10 ? backoff += Random.rand(10) : backoff # to deter overlap
71
71
  if @post_logs_retry_backoff
72
72
  if @post_logs_retry_backoff.is_a? Integer
@@ -78,7 +78,7 @@ module Statsig
78
78
  url = URIHelper.build_url(endpoint)
79
79
  begin
80
80
  res = @connection_pool.with do |conn|
81
- request = conn.headers('STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s)
81
+ request = conn.headers('STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s, 'CONTENT-ENCODING' => zipped ? 'gzip' : nil, 'STATSIG-EVENT-COUNT' => event_count == 0 ? nil : event_count.to_s)
82
82
  case method
83
83
  when :GET
84
84
  request.get(url)
@@ -91,7 +91,7 @@ module Statsig
91
91
  return nil, e unless retries.positive?
92
92
 
93
93
  sleep backoff_adjusted
94
- return request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier)
94
+ return request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
95
95
  end
96
96
  return res, nil if res.status.success?
97
97
 
@@ -102,12 +102,21 @@ module Statsig
102
102
 
103
103
  ## status code retry
104
104
  sleep backoff_adjusted
105
- request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier)
105
+ request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
106
106
  end
107
107
 
108
- def post_logs(events)
108
+ def post_logs(events, error_boundary)
109
+ event_count = events.length
109
110
  json_body = JSON.generate({ events: events, statsigMetadata: Statsig.get_statsig_metadata })
110
- post('log_event', json_body, @post_logs_retry_limit)
111
+ gzip = Zlib::GzipWriter.new(StringIO.new)
112
+ gzip << json_body
113
+ response, e = post('log_event', gzip.close.string, @post_logs_retry_limit, 1, true, event_count)
114
+ unless e == nil
115
+ message = "Failed to log #{event_count} events after #{@post_logs_retry_limit} retries"
116
+ puts "[Statsig]: #{message}"
117
+ error_boundary.log_exception(e, tag: 'statsig::log_event_failed', extra: { eventCount: event_count, error: message }, force: true)
118
+ return
119
+ end
111
120
  rescue StandardError
112
121
 
113
122
  end
data/lib/statsig.rb CHANGED
@@ -363,7 +363,7 @@ module Statsig
363
363
  def self.get_statsig_metadata
364
364
  {
365
365
  'sdkType' => 'ruby-server',
366
- 'sdkVersion' => '1.33.4',
366
+ 'sdkVersion' => '1.34.1',
367
367
  'languageVersion' => RUBY_VERSION
368
368
  }
369
369
  end
@@ -25,7 +25,7 @@ class StatsigDriver
25
25
  raise Statsig::ValueError.new('Invalid options provided. Either provide a valid StatsigOptions object or nil')
26
26
  end
27
27
 
28
- @err_boundary = Statsig::ErrorBoundary.new(secret_key)
28
+ @err_boundary = Statsig::ErrorBoundary.new(secret_key, !options.nil? && options.local_mode)
29
29
  @err_boundary.capture(caller: __method__) do
30
30
  @diagnostics = Statsig::Diagnostics.new()
31
31
  tracker = @diagnostics.track('initialize', 'overall')
@@ -56,7 +56,7 @@ class StatsigDriver
56
56
  return FeatureGate.new(gate_name) if gate.nil?
57
57
  return FeatureGate.new(gate.name, target_app_ids: gate.target_app_ids)
58
58
  end
59
-
59
+
60
60
  user = verify_inputs(user, gate_name, 'gate_name')
61
61
  return Statsig::Memo.for(user.get_memo(), :get_gate_impl, gate_name) do
62
62
 
@@ -87,7 +87,7 @@ class StatsigDriver
87
87
 
88
88
  def check_gate(user, gate_name, options = nil)
89
89
  @err_boundary.capture(caller: __method__, recover: -> {false}) do
90
- run_with_diagnostics(caller: :check_gate) do
90
+ run_with_diagnostics(caller: :check_gate) do
91
91
  get_gate_impl(
92
92
  user,
93
93
  gate_name,
@@ -309,7 +309,11 @@ class StatsigDriver
309
309
  @err_boundary.capture(caller: __method__, recover: -> { nil }) do
310
310
  validate_user(user)
311
311
  normalize_user(user)
312
- @evaluator.get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
312
+ response = @evaluator.get_client_initialize_response(user, hash, client_sdk_key, include_local_overrides)
313
+ if response.nil?
314
+ @err_boundary.log_exception(Statsig::ValueError.new('Failed to get client initialize response'), tag: 'getClientInitializeResponse', extra: {hash: hash, clientKey: client_sdk_key})
315
+ end
316
+ response
313
317
  end
314
318
  end
315
319
 
@@ -160,7 +160,7 @@ module Statsig
160
160
  events_clone = @events
161
161
  @events = []
162
162
  flush_events = events_clone.map { |e| e.serialize }
163
- @network.post_logs(flush_events)
163
+ @network.post_logs(flush_events, @error_boundary)
164
164
  end
165
165
  end
166
166
 
@@ -207,7 +207,7 @@ module Statsig
207
207
  if metadata.is_a?(Hash)
208
208
  metadata_key = metadata.reject { |key, _| $ignored_metadata_keys.include?(key) }.values.join(',')
209
209
  end
210
-
210
+
211
211
  key = [user_key, event_name, metadata_key].join(',')
212
212
 
213
213
  return false if @deduper.include?(key)
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.33.4
4
+ version: 1.34.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-27 00:00:00.000000000 Z
11
+ date: 2024-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -288,6 +288,20 @@ dependencies:
288
288
  - - "~>"
289
289
  - !ruby/object:Gem::Version
290
290
  version: '1.1'
291
+ - !ruby/object:Gem::Dependency
292
+ name: zlib
293
+ requirement: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - "~>"
296
+ - !ruby/object:Gem::Version
297
+ version: 3.1.0
298
+ type: :runtime
299
+ prerelease: false
300
+ version_requirements: !ruby/object:Gem::Requirement
301
+ requirements:
302
+ - - "~>"
303
+ - !ruby/object:Gem::Version
304
+ version: 3.1.0
291
305
  description: Statsig server SDK for feature gates and experimentation in Ruby
292
306
  email: support@statsig.com
293
307
  executables: []