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 +4 -4
- data/lib/diagnostics.rb +22 -3
- data/lib/error_boundary.rb +1 -1
- data/lib/network.rb +3 -5
- data/lib/spec_store.rb +37 -20
- data/lib/statsig.rb +11 -1
- data/lib/statsig_driver.rb +13 -1
- data/lib/statsig_logger.rb +5 -2
- data/lib/statsig_options.rb +14 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4d0e32be6be9e6110bf6237eb4ba33728dfde66385272bb9a15412662455873
|
4
|
+
data.tar.gz: '03408269143b235e02fc8be9f650ed2d4fc778ddf3cc7ffb7242ed9b9494ab73'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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(
|
73
|
-
rand
|
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
|
data/lib/error_boundary.rb
CHANGED
@@ -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 =
|
78
|
-
@id_lists_sync_thread =
|
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
|
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
|
-
|
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
|
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
|
-
|
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.
|
242
|
+
'sdkVersion' => '1.27.0',
|
233
243
|
}
|
234
244
|
end
|
235
245
|
|
data/lib/statsig_driver.rb
CHANGED
@@ -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(
|
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
|
data/lib/statsig_logger.rb
CHANGED
@@ -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?
|
103
|
+
return if diagnostics.nil?
|
104
104
|
|
105
105
|
event = StatsigEvent.new($diagnostics_event)
|
106
106
|
event.user = user
|
107
|
-
|
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
|
data/lib/statsig_options.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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: []
|