statsig 1.30.0 → 1.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/api_config.rb +128 -0
- data/lib/client_initialize_helpers.rb +79 -108
- data/lib/config_result.rb +17 -32
- data/lib/constants.rb +60 -0
- data/lib/diagnostics.rb +21 -70
- data/lib/dynamic_config.rb +1 -24
- data/lib/error_boundary.rb +0 -5
- data/lib/evaluation_details.rb +5 -1
- data/lib/evaluation_helpers.rb +35 -3
- data/lib/evaluator.rb +332 -316
- data/lib/feature_gate.rb +0 -24
- data/lib/id_list.rb +1 -1
- data/lib/interfaces/data_store.rb +1 -1
- data/lib/interfaces/user_persistent_storage.rb +1 -1
- data/lib/layer.rb +1 -20
- data/lib/network.rb +6 -53
- data/lib/spec_store.rb +124 -115
- data/lib/statsig.rb +72 -97
- data/lib/statsig_driver.rb +73 -128
- data/lib/statsig_errors.rb +1 -1
- data/lib/statsig_event.rb +8 -8
- data/lib/statsig_logger.rb +29 -26
- data/lib/statsig_options.rb +0 -50
- data/lib/statsig_user.rb +27 -68
- data/lib/ua_parser.rb +1 -1
- data/lib/uri_helper.rb +1 -9
- data/lib/user_persistent_storage_utils.rb +0 -17
- metadata +4 -2
data/lib/statsig_driver.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# typed: true
|
2
|
-
|
3
1
|
require 'config_result'
|
4
2
|
require 'evaluator'
|
5
3
|
require 'network'
|
@@ -13,13 +11,11 @@ require 'dynamic_config'
|
|
13
11
|
require 'feature_gate'
|
14
12
|
require 'error_boundary'
|
15
13
|
require 'layer'
|
16
|
-
|
14
|
+
|
17
15
|
require 'diagnostics'
|
18
16
|
|
19
17
|
class StatsigDriver
|
20
|
-
extend T::Sig
|
21
18
|
|
22
|
-
sig { params(secret_key: String, options: T.any(StatsigOptions, NilClass), error_callback: T.any(Method, Proc, NilClass)).void }
|
23
19
|
def initialize(secret_key, options = nil, error_callback = nil)
|
24
20
|
unless secret_key.start_with?('secret-')
|
25
21
|
raise Statsig::ValueError.new('Invalid secret key provided. Provide your project secret key from the Statsig console')
|
@@ -31,8 +27,8 @@ class StatsigDriver
|
|
31
27
|
|
32
28
|
@err_boundary = Statsig::ErrorBoundary.new(secret_key)
|
33
29
|
@err_boundary.capture(task: lambda {
|
34
|
-
@diagnostics = Statsig::Diagnostics.new(
|
35
|
-
tracker = @diagnostics.track('overall')
|
30
|
+
@diagnostics = Statsig::Diagnostics.new()
|
31
|
+
tracker = @diagnostics.track('initialize', 'overall')
|
36
32
|
@options = options || StatsigOptions.new
|
37
33
|
@shutdown = false
|
38
34
|
@secret_key = secret_key
|
@@ -43,101 +39,102 @@ class StatsigDriver
|
|
43
39
|
@evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils)
|
44
40
|
tracker.end(success: true)
|
45
41
|
|
46
|
-
@logger.log_diagnostics_event(@diagnostics)
|
42
|
+
@logger.log_diagnostics_event(@diagnostics, 'initialize')
|
47
43
|
}, caller: __method__.to_s)
|
48
44
|
end
|
49
45
|
|
50
|
-
|
51
|
-
params(
|
52
|
-
user: StatsigUser,
|
53
|
-
gate_name: String,
|
54
|
-
disable_log_exposure: T::Boolean,
|
55
|
-
skip_evaluation: T::Boolean
|
56
|
-
).returns(FeatureGate)
|
57
|
-
end
|
58
|
-
def get_gate_impl(user, gate_name, disable_log_exposure: false, skip_evaluation: false)
|
46
|
+
def get_gate_impl(user, gate_name, disable_log_exposure: false, skip_evaluation: false, disable_evaluation_details: false)
|
59
47
|
if skip_evaluation
|
60
48
|
gate = @store.get_gate(gate_name)
|
61
49
|
return FeatureGate.new(gate_name) if gate.nil?
|
62
|
-
return FeatureGate.new(gate
|
50
|
+
return FeatureGate.new(gate.name, target_app_ids: gate.target_app_ids)
|
63
51
|
end
|
64
52
|
user = verify_inputs(user, gate_name, 'gate_name')
|
65
53
|
|
66
|
-
res =
|
67
|
-
|
68
|
-
res = Statsig::ConfigResult.new(gate_name)
|
69
|
-
end
|
54
|
+
res = Statsig::ConfigResult.new(name: gate_name, disable_exposures: disable_log_exposure, disable_evaluation_details: disable_evaluation_details)
|
55
|
+
@evaluator.check_gate(user, gate_name, res)
|
70
56
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
unless disable_log_exposure
|
76
|
-
@logger.log_gate_exposure(
|
77
|
-
user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details
|
78
|
-
)
|
79
|
-
end
|
57
|
+
unless disable_log_exposure
|
58
|
+
@logger.log_gate_exposure(
|
59
|
+
user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details
|
60
|
+
)
|
80
61
|
end
|
81
62
|
FeatureGate.from_config_result(res)
|
82
63
|
end
|
83
64
|
|
84
|
-
|
85
|
-
def get_gate(user, gate_name, options = Statsig::GetGateOptions.new)
|
65
|
+
def get_gate(user, gate_name, options = nil)
|
86
66
|
@err_boundary.capture(task: lambda {
|
87
67
|
run_with_diagnostics(task: lambda {
|
88
|
-
get_gate_impl(user, gate_name,
|
68
|
+
get_gate_impl(user, gate_name,
|
69
|
+
disable_log_exposure: options&.disable_log_exposure == true,
|
70
|
+
skip_evaluation: options&.skip_evaluation == true,
|
71
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
72
|
+
)
|
89
73
|
}, caller: __method__.to_s)
|
90
74
|
}, recover: -> { false }, caller: __method__.to_s)
|
91
75
|
end
|
92
76
|
|
93
|
-
|
94
|
-
def check_gate(user, gate_name, options = Statsig::CheckGateOptions.new)
|
77
|
+
def check_gate(user, gate_name, options = nil)
|
95
78
|
@err_boundary.capture(task: lambda {
|
96
79
|
run_with_diagnostics(task: lambda {
|
97
|
-
get_gate_impl(
|
80
|
+
get_gate_impl(
|
81
|
+
user,
|
82
|
+
gate_name,
|
83
|
+
disable_log_exposure: options&.disable_log_exposure == true,
|
84
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
85
|
+
).value
|
98
86
|
}, caller: __method__.to_s)
|
99
87
|
}, recover: -> { false }, caller: __method__.to_s)
|
100
88
|
end
|
101
89
|
|
102
|
-
sig { params(user: StatsigUser, gate_name: String).void }
|
103
90
|
def manually_log_gate_exposure(user, gate_name)
|
104
91
|
@err_boundary.capture(task: lambda {
|
105
|
-
res =
|
106
|
-
|
92
|
+
res = Statsig::ConfigResult.new(name: gate_name)
|
93
|
+
@evaluator.check_gate(user, gate_name, res)
|
94
|
+
context = { :is_manual_exposure => true }
|
107
95
|
@logger.log_gate_exposure(user, gate_name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
108
96
|
})
|
109
97
|
end
|
110
98
|
|
111
|
-
|
112
|
-
def get_config(user, dynamic_config_name, options = Statsig::GetConfigOptions.new)
|
99
|
+
def get_config(user, dynamic_config_name, options = nil)
|
113
100
|
@err_boundary.capture(task: lambda {
|
114
101
|
run_with_diagnostics(task: lambda {
|
115
102
|
user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
|
116
|
-
get_config_impl(
|
103
|
+
get_config_impl(
|
104
|
+
user,
|
105
|
+
dynamic_config_name,
|
106
|
+
options&.disable_log_exposure == true,
|
107
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
108
|
+
)
|
117
109
|
}, caller: __method__.to_s)
|
118
110
|
}, recover: -> { DynamicConfig.new(dynamic_config_name) }, caller: __method__.to_s)
|
119
111
|
end
|
120
112
|
|
121
|
-
|
122
|
-
def get_experiment(user, experiment_name, options = Statsig::GetExperimentOptions.new)
|
113
|
+
def get_experiment(user, experiment_name, options = nil)
|
123
114
|
@err_boundary.capture(task: lambda {
|
124
115
|
run_with_diagnostics(task: lambda {
|
125
116
|
user = verify_inputs(user, experiment_name, "experiment_name")
|
126
|
-
get_config_impl(
|
117
|
+
get_config_impl(
|
118
|
+
user,
|
119
|
+
experiment_name,
|
120
|
+
options&.disable_log_exposure == true,
|
121
|
+
user_persisted_values: options&.user_persisted_values,
|
122
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
123
|
+
)
|
127
124
|
}, caller: __method__.to_s)
|
128
125
|
}, recover: -> { DynamicConfig.new(experiment_name) }, caller: __method__.to_s)
|
129
126
|
end
|
130
127
|
|
131
|
-
sig { params(user: StatsigUser, config_name: String).void }
|
132
128
|
def manually_log_config_exposure(user, config_name)
|
133
129
|
@err_boundary.capture(task: lambda {
|
134
|
-
res =
|
135
|
-
|
130
|
+
res = Statsig::ConfigResult.new(name: config_name)
|
131
|
+
@evaluator.get_config(user, config_name, res)
|
132
|
+
|
133
|
+
context = { :is_manual_exposure => true }
|
136
134
|
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
137
135
|
}, caller: __method__.to_s)
|
138
136
|
end
|
139
137
|
|
140
|
-
sig { params(user: StatsigUser, id_type: String).returns(Statsig::UserPersistedValues) }
|
141
138
|
def get_user_persisted_values(user, id_type)
|
142
139
|
@err_boundary.capture(task: lambda {
|
143
140
|
persisted_values = @persistent_storage_utils.get_user_persisted_values(user, id_type)
|
@@ -147,39 +144,34 @@ class StatsigDriver
|
|
147
144
|
}, caller: __method__.to_s)
|
148
145
|
end
|
149
146
|
|
150
|
-
|
151
|
-
def get_layer(user, layer_name, options = Statsig::GetLayerOptions.new)
|
147
|
+
def get_layer(user, layer_name, options = nil)
|
152
148
|
@err_boundary.capture(task: lambda {
|
153
149
|
run_with_diagnostics(task: lambda {
|
154
150
|
user = verify_inputs(user, layer_name, "layer_name")
|
151
|
+
exposures_disabled = options&.disable_log_exposure == true
|
152
|
+
res = Statsig::ConfigResult.new(
|
153
|
+
name: layer_name,
|
154
|
+
disable_exposures: exposures_disabled,
|
155
|
+
disable_evaluation_details: options&.disable_evaluation_details == true
|
156
|
+
)
|
157
|
+
@evaluator.get_layer(user, layer_name, res)
|
155
158
|
|
156
|
-
|
157
|
-
if res.nil?
|
158
|
-
res = Statsig::ConfigResult.new(layer_name)
|
159
|
-
end
|
160
|
-
|
161
|
-
if res == $fetch_from_server
|
162
|
-
if res.config_delegate.nil?
|
163
|
-
return Layer.new(layer_name)
|
164
|
-
end
|
165
|
-
res = get_config_fallback(user, res.config_delegate)
|
166
|
-
# exposure logged by the server
|
167
|
-
end
|
168
|
-
|
169
|
-
exposure_log_func = !options.disable_log_exposure ? lambda { |layer, parameter_name|
|
159
|
+
exposure_log_func = !exposures_disabled ? lambda { |layer, parameter_name|
|
170
160
|
@logger.log_layer_exposure(user, layer, parameter_name, res)
|
171
161
|
} : nil
|
162
|
+
|
172
163
|
Layer.new(res.name, res.json_value, res.rule_id, res.group_name, res.config_delegate, exposure_log_func)
|
173
164
|
}, caller: __method__.to_s)
|
174
165
|
}, recover: lambda { Layer.new(layer_name) }, caller: __method__.to_s)
|
175
166
|
end
|
176
167
|
|
177
|
-
sig { params(user: StatsigUser, layer_name: String, parameter_name: String).void }
|
178
168
|
def manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
179
169
|
@err_boundary.capture(task: lambda {
|
180
|
-
res =
|
170
|
+
res = Statsig::ConfigResult.new(name: layer_name)
|
171
|
+
@evaluator.get_layer(user, layer_name, res)
|
172
|
+
|
181
173
|
layer = Layer.new(layer_name, res.json_value, res.rule_id, res.group_name, res.config_delegate)
|
182
|
-
context = {
|
174
|
+
context = { :is_manual_exposure => true }
|
183
175
|
@logger.log_layer_exposure(user, layer, parameter_name, res, context)
|
184
176
|
}, caller: __method__.to_s)
|
185
177
|
end
|
@@ -213,35 +205,30 @@ class StatsigDriver
|
|
213
205
|
}, caller: __method__.to_s)
|
214
206
|
end
|
215
207
|
|
216
|
-
sig { returns(T::Array[String]) }
|
217
208
|
def list_gates
|
218
209
|
@err_boundary.capture(task: lambda {
|
219
210
|
@evaluator.list_gates
|
220
211
|
}, caller: __method__.to_s)
|
221
212
|
end
|
222
213
|
|
223
|
-
sig { returns(T::Array[String]) }
|
224
214
|
def list_configs
|
225
215
|
@err_boundary.capture(task: lambda {
|
226
216
|
@evaluator.list_configs
|
227
217
|
}, caller: __method__.to_s)
|
228
218
|
end
|
229
219
|
|
230
|
-
sig { returns(T::Array[String]) }
|
231
220
|
def list_experiments
|
232
221
|
@err_boundary.capture(task: lambda {
|
233
222
|
@evaluator.list_experiments
|
234
223
|
}, caller: __method__.to_s)
|
235
224
|
end
|
236
225
|
|
237
|
-
sig { returns(T::Array[String]) }
|
238
226
|
def list_autotunes
|
239
227
|
@err_boundary.capture(task: lambda {
|
240
228
|
@evaluator.list_autotunes
|
241
229
|
}, caller: __method__.to_s)
|
242
230
|
end
|
243
231
|
|
244
|
-
sig { returns(T::Array[String]) }
|
245
232
|
def list_layers
|
246
233
|
@err_boundary.capture(task: lambda {
|
247
234
|
@evaluator.list_layers
|
@@ -295,8 +282,8 @@ class StatsigDriver
|
|
295
282
|
def run_with_diagnostics(task:, caller:)
|
296
283
|
diagnostics = nil
|
297
284
|
if Statsig::Diagnostics::API_CALL_KEYS.include?(caller) && Statsig::Diagnostics.sample(1)
|
298
|
-
diagnostics = Statsig::Diagnostics.new(
|
299
|
-
tracker = diagnostics.track(caller)
|
285
|
+
diagnostics = Statsig::Diagnostics.new()
|
286
|
+
tracker = diagnostics.track('api_call', caller)
|
300
287
|
end
|
301
288
|
begin
|
302
289
|
res = task.call
|
@@ -305,12 +292,11 @@ class StatsigDriver
|
|
305
292
|
tracker&.end(success: false)
|
306
293
|
raise e
|
307
294
|
ensure
|
308
|
-
@logger.log_diagnostics_event(diagnostics)
|
295
|
+
@logger.log_diagnostics_event(diagnostics, 'api_call')
|
309
296
|
end
|
310
297
|
return res
|
311
298
|
end
|
312
299
|
|
313
|
-
sig { params(user: StatsigUser, config_name: String, variable_name: String).returns(StatsigUser) }
|
314
300
|
def verify_inputs(user, config_name, variable_name)
|
315
301
|
validate_user(user)
|
316
302
|
if !config_name.is_a?(String) || config_name.empty?
|
@@ -322,27 +308,16 @@ class StatsigDriver
|
|
322
308
|
normalize_user(user)
|
323
309
|
end
|
324
310
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
end
|
333
|
-
def get_config_impl(user, config_name, disable_log_exposure, user_persisted_values: nil)
|
334
|
-
res = @evaluator.get_config(user, config_name, user_persisted_values: user_persisted_values)
|
335
|
-
if res.nil?
|
336
|
-
res = Statsig::ConfigResult.new(config_name)
|
337
|
-
end
|
311
|
+
def get_config_impl(user, config_name, disable_log_exposure, user_persisted_values: nil, disable_evaluation_details: false)
|
312
|
+
res = Statsig::ConfigResult.new(
|
313
|
+
name: config_name,
|
314
|
+
disable_exposures: disable_log_exposure,
|
315
|
+
disable_evaluation_details: disable_evaluation_details
|
316
|
+
)
|
317
|
+
@evaluator.get_config(user, config_name, res, user_persisted_values: user_persisted_values)
|
338
318
|
|
339
|
-
|
340
|
-
res
|
341
|
-
# exposure logged by the server
|
342
|
-
else
|
343
|
-
if !disable_log_exposure
|
344
|
-
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
345
|
-
end
|
319
|
+
unless disable_log_exposure
|
320
|
+
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
346
321
|
end
|
347
322
|
|
348
323
|
DynamicConfig.new(res.name, res.json_value, res.rule_id, res.group_name, res.id_type, res.evaluation_details)
|
@@ -372,34 +347,4 @@ class StatsigDriver
|
|
372
347
|
puts 'SDK has been shutdown. Updates in the Statsig Console will no longer reflect.'
|
373
348
|
end
|
374
349
|
end
|
375
|
-
|
376
|
-
def check_gate_fallback(user, gate_name)
|
377
|
-
network_result = @net.check_gate(user, gate_name)
|
378
|
-
if network_result.nil?
|
379
|
-
config_result = Statsig::ConfigResult.new(gate_name)
|
380
|
-
return config_result
|
381
|
-
end
|
382
|
-
|
383
|
-
Statsig::ConfigResult.new(
|
384
|
-
network_result['name'],
|
385
|
-
network_result['value'],
|
386
|
-
{},
|
387
|
-
network_result['rule_id'],
|
388
|
-
)
|
389
|
-
end
|
390
|
-
|
391
|
-
def get_config_fallback(user, dynamic_config_name)
|
392
|
-
network_result = @net.get_config(user, dynamic_config_name)
|
393
|
-
if network_result.nil?
|
394
|
-
config_result = Statsig::ConfigResult.new(dynamic_config_name)
|
395
|
-
return config_result
|
396
|
-
end
|
397
|
-
|
398
|
-
Statsig::ConfigResult.new(
|
399
|
-
network_result['name'],
|
400
|
-
false,
|
401
|
-
network_result['value'],
|
402
|
-
network_result['rule_id'],
|
403
|
-
)
|
404
|
-
end
|
405
350
|
end
|
data/lib/statsig_errors.rb
CHANGED
data/lib/statsig_event.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
class StatsigEvent
|
3
3
|
attr_accessor :value, :metadata, :statsig_metadata, :secondary_exposures
|
4
4
|
attr_reader :user
|
@@ -21,13 +21,13 @@ class StatsigEvent
|
|
21
21
|
|
22
22
|
def serialize
|
23
23
|
{
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
:eventName => @event_name,
|
25
|
+
:metadata => @metadata,
|
26
|
+
:value => @value,
|
27
|
+
:user => @user,
|
28
|
+
:time => @time,
|
29
|
+
:statsigMetadata => @statsig_metadata,
|
30
|
+
:secondaryExposures => @secondary_exposures
|
31
31
|
}
|
32
32
|
end
|
33
33
|
end
|
data/lib/statsig_logger.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'constants'
|
2
2
|
require 'statsig_event'
|
3
3
|
require 'concurrent-ruby'
|
4
4
|
|
@@ -6,7 +6,7 @@ $gate_exposure_event = 'statsig::gate_exposure'
|
|
6
6
|
$config_exposure_event = 'statsig::config_exposure'
|
7
7
|
$layer_exposure_event = 'statsig::layer_exposure'
|
8
8
|
$diagnostics_event = 'statsig::diagnostics'
|
9
|
-
$ignored_metadata_keys = [
|
9
|
+
$ignored_metadata_keys = [:serverTime, :configSyncTime, :initTime, :reason]
|
10
10
|
module Statsig
|
11
11
|
class StatsigLogger
|
12
12
|
def initialize(network, options, error_boundary)
|
@@ -41,9 +41,9 @@ module Statsig
|
|
41
41
|
event = StatsigEvent.new($gate_exposure_event)
|
42
42
|
event.user = user
|
43
43
|
metadata = {
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
gate: gate_name,
|
45
|
+
gateValue: value.to_s,
|
46
|
+
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
47
47
|
}
|
48
48
|
return false if not is_unique_exposure(user, $gate_exposure_event, metadata)
|
49
49
|
event.metadata = metadata
|
@@ -59,8 +59,8 @@ module Statsig
|
|
59
59
|
event = StatsigEvent.new($config_exposure_event)
|
60
60
|
event.user = user
|
61
61
|
metadata = {
|
62
|
-
|
63
|
-
|
62
|
+
config: config_name,
|
63
|
+
ruleID: rule_id || Statsig::Const::EMPTY_STR,
|
64
64
|
}
|
65
65
|
return false if not is_unique_exposure(user, $config_exposure_event, metadata)
|
66
66
|
event.metadata = metadata
|
@@ -72,8 +72,8 @@ module Statsig
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def log_layer_exposure(user, layer, parameter_name, config_evaluation, context = nil)
|
75
|
-
exposures = config_evaluation.undelegated_sec_exps
|
76
|
-
allocated_experiment =
|
75
|
+
exposures = config_evaluation.undelegated_sec_exps || []
|
76
|
+
allocated_experiment = Statsig::Const::EMPTY_STR
|
77
77
|
is_explicit = (config_evaluation.explicit_parameters&.include? parameter_name) || false
|
78
78
|
if is_explicit
|
79
79
|
allocated_experiment = config_evaluation.config_delegate
|
@@ -83,13 +83,13 @@ module Statsig
|
|
83
83
|
event = StatsigEvent.new($layer_exposure_event)
|
84
84
|
event.user = user
|
85
85
|
metadata = {
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
config: layer.name,
|
87
|
+
ruleID: layer.rule_id || Statsig::Const::EMPTY_STR,
|
88
|
+
allocatedExperiment: allocated_experiment,
|
89
|
+
parameterName: parameter_name,
|
90
|
+
isExplicitParameter: String(is_explicit)
|
91
91
|
}
|
92
|
-
return false
|
92
|
+
return false unless is_unique_exposure(user, $layer_exposure_event, metadata)
|
93
93
|
event.metadata = metadata
|
94
94
|
event.secondary_exposures = exposures.is_a?(Array) ? exposures : []
|
95
95
|
|
@@ -98,18 +98,21 @@ module Statsig
|
|
98
98
|
log_event(event)
|
99
99
|
end
|
100
100
|
|
101
|
-
def log_diagnostics_event(diagnostics, user = nil)
|
102
|
-
return if @options.disable_diagnostics_logging
|
101
|
+
def log_diagnostics_event(diagnostics, context, user = nil)
|
103
102
|
return if diagnostics.nil?
|
103
|
+
if @options.disable_diagnostics_logging
|
104
|
+
diagnostics.clear_markers(context)
|
105
|
+
return
|
106
|
+
end
|
104
107
|
|
105
108
|
event = StatsigEvent.new($diagnostics_event)
|
106
109
|
event.user = user
|
107
|
-
serialized = diagnostics.serialize_with_sampling
|
110
|
+
serialized = diagnostics.serialize_with_sampling(context)
|
111
|
+
diagnostics.clear_markers(context)
|
108
112
|
return if serialized[:markers].empty?
|
109
113
|
|
110
114
|
event.metadata = serialized
|
111
115
|
log_event(event)
|
112
|
-
diagnostics.clear_markers
|
113
116
|
end
|
114
117
|
|
115
118
|
def periodic_flush
|
@@ -164,10 +167,10 @@ module Statsig
|
|
164
167
|
return
|
165
168
|
end
|
166
169
|
|
167
|
-
event.metadata[
|
168
|
-
event.metadata[
|
169
|
-
event.metadata[
|
170
|
-
event.metadata[
|
170
|
+
event.metadata[:reason] = eval_details.reason
|
171
|
+
event.metadata[:configSyncTime] = eval_details.config_sync_time
|
172
|
+
event.metadata[:initTime] = eval_details.init_time
|
173
|
+
event.metadata[:serverTime] = eval_details.server_time
|
171
174
|
end
|
172
175
|
|
173
176
|
def safe_add_exposure_context(context, event)
|
@@ -175,8 +178,8 @@ module Statsig
|
|
175
178
|
return
|
176
179
|
end
|
177
180
|
|
178
|
-
if context[
|
179
|
-
event.metadata[
|
181
|
+
if context[:is_manual_exposure]
|
182
|
+
event.metadata[:isManualExposure] = 'true'
|
180
183
|
end
|
181
184
|
end
|
182
185
|
|
@@ -204,4 +207,4 @@ module Statsig
|
|
204
207
|
true
|
205
208
|
end
|
206
209
|
end
|
207
|
-
end
|
210
|
+
end
|
data/lib/statsig_options.rb
CHANGED
@@ -1,142 +1,92 @@
|
|
1
|
-
# typed: true
|
2
|
-
|
3
|
-
require 'sorbet-runtime'
|
4
1
|
require_relative 'interfaces/data_store'
|
5
2
|
require_relative 'interfaces/user_persistent_storage'
|
6
3
|
|
7
4
|
##
|
8
5
|
# Configuration options for the Statsig SDK.
|
9
6
|
class StatsigOptions
|
10
|
-
extend T::Sig
|
11
7
|
|
12
|
-
sig { returns(T.any(T::Hash[String, String], NilClass)) }
|
13
8
|
# Hash you can use to set environment variables that apply to all of your users in
|
14
9
|
# the same session and will be used for targeting purposes.
|
15
10
|
# eg. { "tier" => "development" }
|
16
11
|
attr_accessor :environment
|
17
12
|
|
18
|
-
sig { returns(String) }
|
19
13
|
# The base url used to make network calls to Statsig.
|
20
14
|
# default: https://statsigapi.net/v1
|
21
15
|
attr_accessor :api_url_base
|
22
16
|
|
23
17
|
# The base url used specifically to call download_config_specs.
|
24
18
|
# Takes precedence over api_url_base
|
25
|
-
sig { returns(String) }
|
26
19
|
attr_accessor :api_url_download_config_specs
|
27
20
|
|
28
|
-
sig { returns(T.any(Float, Integer)) }
|
29
21
|
# The interval (in seconds) to poll for changes to your Statsig configuration
|
30
22
|
# default: 10s
|
31
23
|
attr_accessor :rulesets_sync_interval
|
32
24
|
|
33
|
-
sig { returns(T.any(Float, Integer)) }
|
34
25
|
# The interval (in seconds) to poll for changes to your id lists
|
35
26
|
# default: 60s
|
36
27
|
attr_accessor :idlists_sync_interval
|
37
28
|
|
38
29
|
# Disable background syncing for rulesets
|
39
|
-
sig { returns(T::Boolean) }
|
40
30
|
attr_accessor :disable_rulesets_sync
|
41
31
|
|
42
32
|
# Disable background syncing for id lists
|
43
|
-
sig { returns(T::Boolean) }
|
44
33
|
attr_accessor :disable_idlists_sync
|
45
34
|
|
46
|
-
sig { returns(T.any(Float, Integer)) }
|
47
35
|
# How often to flush logs to Statsig
|
48
36
|
# default: 60s
|
49
37
|
attr_accessor :logging_interval_seconds
|
50
38
|
|
51
|
-
sig { returns(Integer) }
|
52
39
|
# The maximum number of events to batch before flushing logs to the server
|
53
40
|
# default: 1000
|
54
41
|
attr_accessor :logging_max_buffer_size
|
55
42
|
|
56
|
-
sig { returns(T::Boolean) }
|
57
43
|
# Restricts the SDK to not issue any network requests and only respond with default values (or local overrides)
|
58
44
|
# default: false
|
59
45
|
attr_accessor :local_mode
|
60
46
|
|
61
|
-
sig { returns(T.any(String, NilClass)) }
|
62
47
|
# A string that represents all rules for all feature gates, dynamic configs and experiments.
|
63
48
|
# It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs
|
64
49
|
# into network issue or Statsig is down temporarily.
|
65
50
|
attr_accessor :bootstrap_values
|
66
51
|
|
67
|
-
sig { returns(T.any(Method, Proc, NilClass)) }
|
68
52
|
# A callback function that will be called anytime the rulesets are updated.
|
69
53
|
attr_accessor :rules_updated_callback
|
70
54
|
|
71
|
-
sig { returns(T.any(Statsig::Interfaces::IDataStore, NilClass)) }
|
72
55
|
# A class that extends IDataStore. Can be used to provide values from a
|
73
56
|
# common data store (like Redis) to initialize the Statsig SDK.
|
74
57
|
attr_accessor :data_store
|
75
58
|
|
76
|
-
sig { returns(Integer) }
|
77
59
|
# The number of threads allocated to syncing IDLists.
|
78
60
|
# default: 3
|
79
61
|
attr_accessor :idlist_threadpool_size
|
80
62
|
|
81
|
-
sig { returns(Integer) }
|
82
63
|
# The number of threads allocated to posting event logs.
|
83
64
|
# default: 3
|
84
65
|
attr_accessor :logger_threadpool_size
|
85
66
|
|
86
|
-
sig { returns(T::Boolean) }
|
87
67
|
# Should diagnostics be logged. These include performance metrics for initialize.
|
88
68
|
# default: false
|
89
69
|
attr_accessor :disable_diagnostics_logging
|
90
70
|
|
91
|
-
sig { returns(T::Boolean) }
|
92
71
|
# Statsig utilizes Sorbet (https://sorbet.org) to ensure type safety of the SDK. This includes logging
|
93
72
|
# to console when errors are detected. You can disable this logging by setting this flag to true.
|
94
73
|
# default: false
|
95
74
|
attr_accessor :disable_sorbet_logging_handlers
|
96
75
|
|
97
|
-
sig { returns(T.any(Integer, NilClass)) }
|
98
76
|
# Number of seconds before a network call is timed out
|
99
77
|
attr_accessor :network_timeout
|
100
78
|
|
101
|
-
sig { returns(Integer) }
|
102
79
|
# Number of times to retry sending a batch of failed log events
|
103
80
|
attr_accessor :post_logs_retry_limit
|
104
81
|
|
105
|
-
sig { returns(T.any(Method, Proc, Integer, NilClass)) }
|
106
82
|
# The number of seconds, or a function that returns the number of seconds based on the number of retries remaining
|
107
83
|
# which overrides the default backoff time between retries
|
108
84
|
attr_accessor :post_logs_retry_backoff
|
109
85
|
|
110
|
-
sig { returns(T.any(Statsig::Interfaces::IUserPersistentStorage, NilClass)) }
|
111
86
|
# A storage adapter for persisted values. Can be used for sticky bucketing users in experiments.
|
112
87
|
# Implements Statsig::Interfaces::IUserPersistentStorage.
|
113
88
|
attr_accessor :user_persistent_storage
|
114
89
|
|
115
|
-
sig do
|
116
|
-
params(
|
117
|
-
environment: T.any(T::Hash[String, String], NilClass),
|
118
|
-
api_url_base: T.nilable(String),
|
119
|
-
api_url_download_config_specs: T.any(String, NilClass),
|
120
|
-
rulesets_sync_interval: T.any(Float, Integer),
|
121
|
-
idlists_sync_interval: T.any(Float, Integer),
|
122
|
-
disable_rulesets_sync: T::Boolean,
|
123
|
-
disable_idlists_sync: T::Boolean,
|
124
|
-
logging_interval_seconds: T.any(Float, Integer),
|
125
|
-
logging_max_buffer_size: Integer,
|
126
|
-
local_mode: T::Boolean,
|
127
|
-
bootstrap_values: T.any(String, NilClass),
|
128
|
-
rules_updated_callback: T.any(Method, Proc, NilClass),
|
129
|
-
data_store: T.any(Statsig::Interfaces::IDataStore, NilClass),
|
130
|
-
idlist_threadpool_size: Integer,
|
131
|
-
logger_threadpool_size: Integer,
|
132
|
-
disable_diagnostics_logging: T::Boolean,
|
133
|
-
disable_sorbet_logging_handlers: T::Boolean,
|
134
|
-
network_timeout: T.any(Integer, NilClass),
|
135
|
-
post_logs_retry_limit: Integer,
|
136
|
-
post_logs_retry_backoff: T.any(Method, Proc, Integer, NilClass),
|
137
|
-
user_persistent_storage: T.any(Statsig::Interfaces::IUserPersistentStorage, NilClass)
|
138
|
-
).void
|
139
|
-
end
|
140
90
|
def initialize(
|
141
91
|
environment = nil,
|
142
92
|
api_url_base = nil,
|