statsig 2.8.3 → 2.9.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/network.rb +94 -41
- data/lib/spec_store.rb +17 -13
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +6 -2
- data/lib/statsig_options.rb +7 -1
- metadata +12 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 800d73e16d593bd9c6b505f9d6207c47e51d8b700344b3c653a07ca8b257cd69
|
|
4
|
+
data.tar.gz: a3cad13252aff42d5eba3e4f9c9b478adb9ec0758633102f2ada9b3d3739052e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9a1df9789f61b60bb4d015da636b20bf081804b5c1c8d1f542afc3f494538fea68b3bc84ca65d7faf513c63d8d0d72bee94f2583a932c7d54ae0c3cdff560e7
|
|
7
|
+
data.tar.gz: a7d46bfa64e8a52c60eec3bc14c596ff9ffe41493ed68bda741d4171c75da407e7e7842ad63c76addff0c98314d95967b515801efba8f1f5486d8aabc793fc8e
|
data/lib/network.rb
CHANGED
|
@@ -28,34 +28,19 @@ module Statsig
|
|
|
28
28
|
@post_logs_retry_backoff = options.post_logs_retry_backoff
|
|
29
29
|
@post_logs_retry_limit = options.post_logs_retry_limit
|
|
30
30
|
@session_id = SecureRandom.uuid
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
client = HTTP.use(:auto_inflate).headers(
|
|
34
|
-
{
|
|
35
|
-
'STATSIG-API-KEY' => @server_secret,
|
|
36
|
-
'STATSIG-SERVER-SESSION-ID' => @session_id,
|
|
37
|
-
'Content-Type' => 'application/json; charset=UTF-8',
|
|
38
|
-
'STATSIG-SDK-TYPE' => meta['sdkType'],
|
|
39
|
-
'STATSIG-SDK-VERSION' => meta['sdkVersion'],
|
|
40
|
-
'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
|
|
41
|
-
'Accept-Encoding' => 'gzip'
|
|
42
|
-
}
|
|
43
|
-
).accept(:json)
|
|
44
|
-
|
|
45
|
-
if @timeout
|
|
46
|
-
client = client.timeout(@timeout)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
client
|
|
50
|
-
end
|
|
31
|
+
@connection_pools = {}
|
|
32
|
+
@connection_pools_mutex = Mutex.new
|
|
51
33
|
end
|
|
52
34
|
|
|
53
|
-
def download_config_specs(since_time)
|
|
35
|
+
def download_config_specs(since_time, context)
|
|
54
36
|
url = @options.download_config_specs_url
|
|
55
37
|
dcs_url = "#{url}#{@server_secret}.json"
|
|
56
38
|
if since_time.positive?
|
|
57
39
|
dcs_url += "?sinceTime=#{since_time}"
|
|
58
40
|
end
|
|
41
|
+
if context == 'initialize'
|
|
42
|
+
return get(dcs_url, @options.initialize_retry_limit)
|
|
43
|
+
end
|
|
59
44
|
get(dcs_url)
|
|
60
45
|
end
|
|
61
46
|
|
|
@@ -66,10 +51,7 @@ module Statsig
|
|
|
66
51
|
gzip = Zlib::GzipWriter.new(StringIO.new)
|
|
67
52
|
gzip << json_body
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Consume response body to ensure connection can be closed.
|
|
72
|
-
response&.flush
|
|
54
|
+
_response, e = post(url, gzip.close.string, @post_logs_retry_limit, 1, true, event_count)
|
|
73
55
|
|
|
74
56
|
unless e == nil
|
|
75
57
|
message = "Failed to log #{event_count} events after #{@post_logs_retry_limit} retries"
|
|
@@ -81,11 +63,18 @@ module Statsig
|
|
|
81
63
|
|
|
82
64
|
end
|
|
83
65
|
|
|
84
|
-
def get_id_lists
|
|
66
|
+
def get_id_lists(context)
|
|
85
67
|
url = @options.get_id_lists_url
|
|
68
|
+
if context == 'initialize'
|
|
69
|
+
return post(url, JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }), @options.initialize_retry_limit)
|
|
70
|
+
end
|
|
86
71
|
post(url, JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }))
|
|
87
72
|
end
|
|
88
73
|
|
|
74
|
+
def download_id_list(url, start_byte = 0)
|
|
75
|
+
request(:GET, url, nil, 0, 1, false, 0, { 'Range' => "bytes=#{start_byte}-" }, false)
|
|
76
|
+
end
|
|
77
|
+
|
|
89
78
|
def get(url, retries = 0, backoff = 1)
|
|
90
79
|
request(:GET, url, nil, retries, backoff)
|
|
91
80
|
end
|
|
@@ -94,7 +83,21 @@ module Statsig
|
|
|
94
83
|
request(:POST, url, body, retries, backoff, zipped, event_count)
|
|
95
84
|
end
|
|
96
85
|
|
|
97
|
-
def
|
|
86
|
+
def shutdown
|
|
87
|
+
pools = @connection_pools_mutex.synchronize do
|
|
88
|
+
pools = @connection_pools.values
|
|
89
|
+
@connection_pools = {}
|
|
90
|
+
pools
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
pools.each do |pool|
|
|
94
|
+
pool.shutdown do |conn|
|
|
95
|
+
conn.close if conn.respond_to?(:close)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true)
|
|
98
101
|
if @local_mode
|
|
99
102
|
return nil, nil
|
|
100
103
|
end
|
|
@@ -108,18 +111,25 @@ module Statsig
|
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
begin
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
pool = connection_pool_for(url)
|
|
115
|
+
res = pool.with do |conn|
|
|
116
|
+
begin
|
|
117
|
+
request_headers = build_request_headers(zipped, event_count, extra_headers, use_statsig_headers)
|
|
118
|
+
request = conn.headers(request_headers)
|
|
119
|
+
|
|
120
|
+
response = case method
|
|
121
|
+
when :GET
|
|
122
|
+
request.get(url)
|
|
123
|
+
when :POST
|
|
124
|
+
request.post(url, body: body)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Fully drain the response before the client returns to the pool.
|
|
128
|
+
response.body.to_s
|
|
129
|
+
response
|
|
130
|
+
rescue StandardError
|
|
131
|
+
pool.discard_current_connection(&:close)
|
|
132
|
+
raise
|
|
123
133
|
end
|
|
124
134
|
end
|
|
125
135
|
rescue StandardError => e
|
|
@@ -127,7 +137,7 @@ module Statsig
|
|
|
127
137
|
return nil, e unless retries.positive?
|
|
128
138
|
|
|
129
139
|
sleep backoff_adjusted
|
|
130
|
-
return request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
|
|
140
|
+
return request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
|
|
131
141
|
end
|
|
132
142
|
return res, nil if res.status.success?
|
|
133
143
|
|
|
@@ -138,7 +148,50 @@ module Statsig
|
|
|
138
148
|
|
|
139
149
|
## status code retry
|
|
140
150
|
sleep backoff_adjusted
|
|
141
|
-
request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
|
|
151
|
+
request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def connection_pool_for(url)
|
|
157
|
+
origin = HTTP::URI.parse(url).origin
|
|
158
|
+
@connection_pools_mutex.synchronize do
|
|
159
|
+
@connection_pools[origin] ||= ConnectionPool.new(size: 3) do
|
|
160
|
+
build_persistent_client(origin)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def build_persistent_client(origin)
|
|
166
|
+
client = HTTP.use(:auto_inflate)
|
|
167
|
+
client = client.timeout(@timeout) if @timeout
|
|
168
|
+
client.persistent(origin)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def build_request_headers(zipped, event_count, extra_headers, use_statsig_headers)
|
|
172
|
+
headers = {}
|
|
173
|
+
|
|
174
|
+
if use_statsig_headers
|
|
175
|
+
meta = Statsig.get_statsig_metadata
|
|
176
|
+
headers.merge!(
|
|
177
|
+
'STATSIG-API-KEY' => @server_secret,
|
|
178
|
+
'STATSIG-SERVER-SESSION-ID' => @session_id,
|
|
179
|
+
'Content-Type' => 'application/json; charset=UTF-8',
|
|
180
|
+
'STATSIG-SDK-TYPE' => meta['sdkType'],
|
|
181
|
+
'STATSIG-SDK-VERSION' => meta['sdkVersion'],
|
|
182
|
+
'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
|
|
183
|
+
'Accept' => 'application/json',
|
|
184
|
+
'Accept-Encoding' => 'gzip',
|
|
185
|
+
'STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
headers['CONTENT-ENCODING'] = 'gzip' if zipped
|
|
190
|
+
headers['STATSIG-EVENT-COUNT'] = event_count.to_s unless event_count == 0
|
|
191
|
+
|
|
192
|
+
headers.merge!(extra_headers)
|
|
193
|
+
headers.delete_if { |_key, value| value.nil? }
|
|
194
|
+
headers
|
|
142
195
|
end
|
|
143
196
|
end
|
|
144
197
|
end
|
data/lib/spec_store.rb
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require 'concurrent-ruby'
|
|
2
|
-
require 'net/http'
|
|
3
|
-
require 'uri'
|
|
4
2
|
require_relative 'api_config'
|
|
5
3
|
require_relative 'evaluation_details'
|
|
6
4
|
require_relative 'hash_utils'
|
|
@@ -291,7 +289,7 @@ module Statsig
|
|
|
291
289
|
error = nil
|
|
292
290
|
failure_details = nil
|
|
293
291
|
begin
|
|
294
|
-
response, e = @network.download_config_specs(@last_config_sync_time)
|
|
292
|
+
response, e = @network.download_config_specs(@last_config_sync_time, context)
|
|
295
293
|
code = response&.status.to_i
|
|
296
294
|
if e.is_a? NetworkError
|
|
297
295
|
code = e.http_code
|
|
@@ -302,14 +300,15 @@ module Statsig
|
|
|
302
300
|
if e.nil?
|
|
303
301
|
unless response.nil?
|
|
304
302
|
tracker = @diagnostics.track(context, 'download_config_specs', 'process')
|
|
305
|
-
|
|
303
|
+
body = response.body.to_s
|
|
304
|
+
failure_details = process_specs(body)
|
|
306
305
|
if failure_details.nil?
|
|
307
306
|
@init_reason = EvaluationReason::NETWORK
|
|
308
307
|
end
|
|
309
308
|
tracker.end(success: @init_reason == EvaluationReason::NETWORK)
|
|
310
309
|
|
|
311
310
|
unless response.body.nil? or @rules_updated_callback.nil?
|
|
312
|
-
@rules_updated_callback.call(
|
|
311
|
+
@rules_updated_callback.call(body,
|
|
313
312
|
@last_config_sync_time)
|
|
314
313
|
end
|
|
315
314
|
end
|
|
@@ -401,7 +400,7 @@ module Statsig
|
|
|
401
400
|
|
|
402
401
|
def get_id_lists_from_network(context)
|
|
403
402
|
tracker = @diagnostics.track(context, 'get_id_list_sources', 'network_request')
|
|
404
|
-
response, e = @network.get_id_lists
|
|
403
|
+
response, e = @network.get_id_lists(context)
|
|
405
404
|
code = response&.status.to_i
|
|
406
405
|
if e.is_a? NetworkError
|
|
407
406
|
code = e.http_code
|
|
@@ -413,9 +412,10 @@ module Statsig
|
|
|
413
412
|
end
|
|
414
413
|
|
|
415
414
|
begin
|
|
416
|
-
|
|
415
|
+
body = response.body.to_s
|
|
416
|
+
server_id_lists = JSON.parse(body)
|
|
417
417
|
process_id_lists(server_id_lists, context)
|
|
418
|
-
save_id_lists_to_adapter(
|
|
418
|
+
save_id_lists_to_adapter(body)
|
|
419
419
|
rescue StandardError
|
|
420
420
|
# Ignored, will try again
|
|
421
421
|
end
|
|
@@ -509,14 +509,18 @@ module Statsig
|
|
|
509
509
|
|
|
510
510
|
def download_single_id_list(list, context)
|
|
511
511
|
nil unless list.is_a? IDList
|
|
512
|
-
http = HTTP.headers({ 'Range' => "bytes=#{list&.size || 0}-" }).accept(:json)
|
|
513
512
|
tracker = @diagnostics.track(context, 'get_id_list', 'network_request', { url: list.url })
|
|
514
513
|
begin
|
|
515
|
-
res =
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
res, e = @network.download_id_list(list.url, list&.size || 0)
|
|
515
|
+
code = res&.status&.to_i
|
|
516
|
+
if e.is_a? NetworkError
|
|
517
|
+
code = e.http_code
|
|
518
|
+
end
|
|
519
|
+
success = e.nil? && !res.nil?
|
|
520
|
+
tracker.end(statusCode: code, success: success)
|
|
521
|
+
return nil unless success
|
|
518
522
|
content_length = Integer(res['content-length'])
|
|
519
|
-
nil if content_length.nil? || content_length <= 0
|
|
523
|
+
return nil if content_length.nil? || content_length <= 0
|
|
520
524
|
content = res.body.to_s
|
|
521
525
|
success = process_single_id_list(list, context, content, content_length)
|
|
522
526
|
save_single_id_list_to_adapter(list.name, content) unless success.nil? || !success
|
data/lib/statsig.rb
CHANGED
data/lib/statsig_driver.rb
CHANGED
|
@@ -291,8 +291,12 @@ class StatsigDriver
|
|
|
291
291
|
def shutdown
|
|
292
292
|
@err_boundary.capture(caller: __method__) do
|
|
293
293
|
@shutdown = true
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
begin
|
|
295
|
+
@logger.shutdown
|
|
296
|
+
@evaluator.shutdown
|
|
297
|
+
ensure
|
|
298
|
+
@net.shutdown
|
|
299
|
+
end
|
|
296
300
|
end
|
|
297
301
|
end
|
|
298
302
|
|
data/lib/statsig_options.rb
CHANGED
|
@@ -92,6 +92,10 @@ class StatsigOptions
|
|
|
92
92
|
# default: false
|
|
93
93
|
attr_accessor :disable_evaluation_memoization
|
|
94
94
|
|
|
95
|
+
# Number of times to retry fetching rulesets and id lists on initialization.
|
|
96
|
+
# default: 0
|
|
97
|
+
attr_accessor :initialize_retry_limit
|
|
98
|
+
|
|
95
99
|
def initialize(
|
|
96
100
|
environment = nil,
|
|
97
101
|
download_config_specs_url: nil,
|
|
@@ -115,7 +119,8 @@ class StatsigOptions
|
|
|
115
119
|
post_logs_retry_limit: 3,
|
|
116
120
|
post_logs_retry_backoff: nil,
|
|
117
121
|
user_persistent_storage: nil,
|
|
118
|
-
disable_evaluation_memoization: false
|
|
122
|
+
disable_evaluation_memoization: false,
|
|
123
|
+
initialize_retry_limit: 0
|
|
119
124
|
)
|
|
120
125
|
@environment = environment.is_a?(Hash) ? environment : nil
|
|
121
126
|
|
|
@@ -146,5 +151,6 @@ class StatsigOptions
|
|
|
146
151
|
@post_logs_retry_backoff = post_logs_retry_backoff
|
|
147
152
|
@user_persistent_storage = user_persistent_storage
|
|
148
153
|
@disable_evaluation_memoization = disable_evaluation_memoization
|
|
154
|
+
@initialize_retry_limit = initialize_retry_limit
|
|
149
155
|
end
|
|
150
156
|
end
|
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: 2.
|
|
4
|
+
version: 2.9.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: 2026-
|
|
11
|
+
date: 2026-04-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -306,16 +306,22 @@ dependencies:
|
|
|
306
306
|
name: zlib
|
|
307
307
|
requirement: !ruby/object:Gem::Requirement
|
|
308
308
|
requirements:
|
|
309
|
-
- - "
|
|
309
|
+
- - ">="
|
|
310
|
+
- !ruby/object:Gem::Version
|
|
311
|
+
version: '3.1'
|
|
312
|
+
- - "<"
|
|
310
313
|
- !ruby/object:Gem::Version
|
|
311
|
-
version:
|
|
314
|
+
version: '4.0'
|
|
312
315
|
type: :runtime
|
|
313
316
|
prerelease: false
|
|
314
317
|
version_requirements: !ruby/object:Gem::Requirement
|
|
315
318
|
requirements:
|
|
316
|
-
- - "
|
|
319
|
+
- - ">="
|
|
320
|
+
- !ruby/object:Gem::Version
|
|
321
|
+
version: '3.1'
|
|
322
|
+
- - "<"
|
|
317
323
|
- !ruby/object:Gem::Version
|
|
318
|
-
version:
|
|
324
|
+
version: '4.0'
|
|
319
325
|
description: Statsig server SDK for feature gates and experimentation in Ruby
|
|
320
326
|
email: support@statsig.com
|
|
321
327
|
executables: []
|