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 +4 -4
- data/lib/client_initialize_helpers.rb +2 -12
- data/lib/error_boundary.rb +9 -6
- data/lib/evaluator.rb +41 -13
- data/lib/network.rb +18 -9
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +8 -4
- data/lib/statsig_logger.rb +2 -2
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c2aff544b63f731dd32e2d2a498f83b91b66222f42d507f7ad483ce11b8cadf
|
4
|
+
data.tar.gz: f7c1ec377a7ff7861696cb250817262bf999c8ba81904e6ed663a6e40fee9da0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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] =
|
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] =
|
136
|
+
result[:undelegated_secondary_exposures] = eval_result.undelegated_sec_exps || []
|
147
137
|
end
|
148
138
|
end
|
149
139
|
|
data/lib/error_boundary.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
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:
|
228
|
+
time: @spec_store.last_config_sync_time,
|
226
229
|
hash_used: hash_algo,
|
227
|
-
|
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
|
-
|
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
data/lib/statsig_driver.rb
CHANGED
@@ -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
|
|
data/lib/statsig_logger.rb
CHANGED
@@ -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.
|
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-
|
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: []
|