statsig 1.10.0 → 1.20.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/client_initialize_helpers.rb +132 -0
- data/lib/config_result.rb +16 -3
- data/lib/diagnostics.rb +44 -0
- data/lib/dynamic_config.rb +37 -0
- data/lib/error_boundary.rb +57 -0
- data/lib/evaluation_details.rb +42 -0
- data/lib/evaluation_helpers.rb +1 -0
- data/lib/evaluator.rb +127 -14
- data/lib/id_list.rb +1 -0
- data/lib/interfaces/data_store.rb +19 -0
- data/lib/layer.rb +39 -0
- data/lib/network.rb +39 -11
- data/lib/spec_store.rb +183 -43
- data/lib/statsig.rb +213 -4
- data/lib/statsig_driver.rb +192 -62
- data/lib/statsig_errors.rb +11 -0
- data/lib/statsig_event.rb +6 -1
- data/lib/statsig_logger.rb +81 -13
- data/lib/statsig_options.rb +114 -7
- data/lib/statsig_user.rb +79 -16
- metadata +70 -8
data/lib/statsig.rb
CHANGED
@@ -1,59 +1,268 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'statsig_driver'
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
require 'statsig_errors'
|
2
6
|
|
3
7
|
module Statsig
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(secret_key: String, options: T.any(StatsigOptions, NilClass), error_callback: T.any(Method, Proc, NilClass)).void }
|
11
|
+
##
|
12
|
+
# Initializes the Statsig SDK.
|
13
|
+
#
|
14
|
+
# @param secret_key The server SDK key copied from console.statsig.com
|
15
|
+
# @param options The StatsigOptions object used to configure the SDK
|
16
|
+
# @param error_callback A callback function, called if the initialize network call fails
|
4
17
|
def self.initialize(secret_key, options = nil, error_callback = nil)
|
5
18
|
unless @shared_instance.nil?
|
6
19
|
puts 'Statsig already initialized.'
|
20
|
+
@shared_instance.maybe_restart_background_threads
|
7
21
|
return @shared_instance
|
8
22
|
end
|
9
23
|
|
24
|
+
self.bind_sorbet_loggers(options)
|
25
|
+
|
10
26
|
@shared_instance = StatsigDriver.new(secret_key, options, error_callback)
|
11
27
|
end
|
12
28
|
|
29
|
+
sig { params(user: StatsigUser, gate_name: String).returns(T::Boolean) }
|
30
|
+
##
|
31
|
+
# Gets the boolean result of a gate, evaluated against the given user. An exposure event will automatically be logged for the gate.
|
32
|
+
#
|
33
|
+
# @param user A StatsigUser object used for the evaluation
|
34
|
+
# @param gate_name The name of the gate being checked
|
13
35
|
def self.check_gate(user, gate_name)
|
14
36
|
ensure_initialized
|
15
37
|
@shared_instance&.check_gate(user, gate_name)
|
16
38
|
end
|
17
39
|
|
40
|
+
sig { params(user: StatsigUser, gate_name: String).returns(T::Boolean) }
|
41
|
+
##
|
42
|
+
# Gets the boolean result of a gate, evaluated against the given user.
|
43
|
+
#
|
44
|
+
# @param user A StatsigUser object used for the evaluation
|
45
|
+
# @param gate_name The name of the gate being checked
|
46
|
+
def self.check_gate_with_exposure_logging_disabled(user, gate_name)
|
47
|
+
ensure_initialized
|
48
|
+
@shared_instance&.check_gate(user, gate_name, StatsigDriver::CheckGateOptions.new(log_exposure: false))
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { params(user: StatsigUser, gate_name: String).void }
|
52
|
+
##
|
53
|
+
# Logs an exposure event for the gate
|
54
|
+
#
|
55
|
+
# @param user A StatsigUser object used for the evaluation
|
56
|
+
# @param gate_name The name of the gate being checked
|
57
|
+
def self.manually_log_gate_exposure(user, gate_name)
|
58
|
+
ensure_initialized
|
59
|
+
@shared_instance&.manually_log_gate_exposure(user, gate_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(user: StatsigUser, dynamic_config_name: String).returns(DynamicConfig) }
|
63
|
+
##
|
64
|
+
# Get the values of a dynamic config, evaluated against the given user. An exposure event will automatically be logged for the dynamic config.
|
65
|
+
#
|
66
|
+
# @param user A StatsigUser object used for the evaluation
|
67
|
+
# @param dynamic_config_name The name of the dynamic config
|
18
68
|
def self.get_config(user, dynamic_config_name)
|
19
69
|
ensure_initialized
|
20
70
|
@shared_instance&.get_config(user, dynamic_config_name)
|
21
71
|
end
|
22
72
|
|
73
|
+
sig { params(user: StatsigUser, dynamic_config_name: String).returns(DynamicConfig) }
|
74
|
+
##
|
75
|
+
# Get the values of a dynamic config, evaluated against the given user.
|
76
|
+
#
|
77
|
+
# @param user A StatsigUser object used for the evaluation
|
78
|
+
# @param dynamic_config_name The name of the dynamic config
|
79
|
+
def self.get_config_with_exposure_logging_disabled(user, dynamic_config_name)
|
80
|
+
ensure_initialized
|
81
|
+
@shared_instance&.get_config(user, dynamic_config_name, StatsigDriver::GetConfigOptions.new(log_exposure: false))
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(user: StatsigUser, dynamic_config: String).void }
|
85
|
+
##
|
86
|
+
# Logs an exposure event for the dynamic config
|
87
|
+
#
|
88
|
+
# @param user A StatsigUser object used for the evaluation
|
89
|
+
# @param dynamic_config_name The name of the dynamic config
|
90
|
+
def self.manually_log_config_exposure(user, dynamic_config)
|
91
|
+
ensure_initialized
|
92
|
+
@shared_instance&.manually_log_config_exposure(user, dynamic_config)
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { params(user: StatsigUser, experiment_name: String).returns(DynamicConfig) }
|
96
|
+
##
|
97
|
+
# Get the values of an experiment, evaluated against the given user. An exposure event will automatically be logged for the experiment.
|
98
|
+
#
|
99
|
+
# @param user A StatsigUser object used for the evaluation
|
100
|
+
# @param experiment_name The name of the experiment
|
23
101
|
def self.get_experiment(user, experiment_name)
|
24
102
|
ensure_initialized
|
25
103
|
@shared_instance&.get_experiment(user, experiment_name)
|
26
104
|
end
|
27
105
|
|
106
|
+
sig { params(user: StatsigUser, experiment_name: String).returns(DynamicConfig) }
|
107
|
+
##
|
108
|
+
# Get the values of an experiment, evaluated against the given user.
|
109
|
+
#
|
110
|
+
# @param user A StatsigUser object used for the evaluation
|
111
|
+
# @param experiment_name The name of the experiment
|
112
|
+
def self.get_experiment_with_exposure_logging_disabled(user, experiment_name)
|
113
|
+
ensure_initialized
|
114
|
+
@shared_instance&.get_experiment(user, experiment_name, StatsigDriver::GetExperimentOptions.new(log_exposure: false))
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { params(user: StatsigUser, experiment_name: String).void }
|
118
|
+
##
|
119
|
+
# Logs an exposure event for the experiment
|
120
|
+
#
|
121
|
+
# @param user A StatsigUser object used for the evaluation
|
122
|
+
# @param experiment_name The name of the experiment
|
123
|
+
def self.manually_log_experiment_exposure(user, experiment_name)
|
124
|
+
ensure_initialized
|
125
|
+
@shared_instance&.manually_log_config_exposure(user, experiment_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(user: StatsigUser, layer_name: String).returns(Layer) }
|
129
|
+
##
|
130
|
+
# Get the values of a layer, evaluated against the given user.
|
131
|
+
# Exposure events will be fired when get or get_typed is called on the resulting Layer class.
|
132
|
+
#
|
133
|
+
# @param user A StatsigUser object used for the evaluation
|
134
|
+
# @param layer_name The name of the layer
|
28
135
|
def self.get_layer(user, layer_name)
|
29
136
|
ensure_initialized
|
30
137
|
@shared_instance&.get_layer(user, layer_name)
|
31
138
|
end
|
32
139
|
|
140
|
+
sig { params(user: StatsigUser, layer_name: String).returns(Layer) }
|
141
|
+
##
|
142
|
+
# Get the values of a layer, evaluated against the given user.
|
143
|
+
#
|
144
|
+
# @param user A StatsigUser object used for the evaluation
|
145
|
+
# @param layer_name The name of the layer
|
146
|
+
def self.get_layer_with_exposure_logging_disabled(user, layer_name)
|
147
|
+
ensure_initialized
|
148
|
+
@shared_instance&.get_layer(user, layer_name, StatsigDriver::GetLayerOptions.new(log_exposure: false))
|
149
|
+
end
|
150
|
+
|
151
|
+
sig { params(user: StatsigUser, layer_name: String, parameter_name: String).returns(Layer) }
|
152
|
+
##
|
153
|
+
# Logs an exposure event for the parameter in the given layer
|
154
|
+
#
|
155
|
+
# @param user A StatsigUser object used for the evaluation
|
156
|
+
# @param layer_name The name of the layer
|
157
|
+
# @param parameter_name The name of the parameter in the layer
|
158
|
+
def self.manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
159
|
+
ensure_initialized
|
160
|
+
@shared_instance&.manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
sig { params(user: StatsigUser,
|
164
|
+
event_name: String,
|
165
|
+
value: T.any(String, Integer, Float, NilClass),
|
166
|
+
metadata: T.any(T::Hash[String, T.untyped], NilClass)).void }
|
167
|
+
##
|
168
|
+
# Logs an event to Statsig with the provided values.
|
169
|
+
#
|
170
|
+
# @param user A StatsigUser object to be included in the log
|
171
|
+
# @param event_name The name given to the event
|
172
|
+
# @param value A top level value for the event
|
173
|
+
# @param metadata Any extra values to be logged
|
33
174
|
def self.log_event(user, event_name, value = nil, metadata = nil)
|
34
175
|
ensure_initialized
|
35
176
|
@shared_instance&.log_event(user, event_name, value, metadata)
|
36
177
|
end
|
37
178
|
|
179
|
+
sig { void }
|
180
|
+
##
|
181
|
+
# Stops all Statsig activity and flushes any pending events.
|
38
182
|
def self.shutdown
|
39
|
-
|
183
|
+
if defined? @shared_instance and !@shared_instance.nil?
|
40
184
|
@shared_instance.shutdown
|
41
185
|
end
|
42
186
|
@shared_instance = nil
|
43
187
|
end
|
44
188
|
|
189
|
+
sig { params(gate_name: String, gate_value: T::Boolean).void }
|
190
|
+
##
|
191
|
+
# Sets a value to be returned for the given gate instead of the actual evaluated value.
|
192
|
+
#
|
193
|
+
# @param gate_name The name of the gate to be overridden
|
194
|
+
# @param gate_value The value that will be returned
|
195
|
+
def self.override_gate(gate_name, gate_value)
|
196
|
+
ensure_initialized
|
197
|
+
@shared_instance&.override_gate(gate_name, gate_value)
|
198
|
+
end
|
199
|
+
|
200
|
+
sig { params(config_name: String, config_value: Hash).void }
|
201
|
+
##
|
202
|
+
# Sets a value to be returned for the given dynamic config/experiment instead of the actual evaluated value.
|
203
|
+
#
|
204
|
+
# @param config_name The name of the dynamic config or experiment to be overridden
|
205
|
+
# @param config_value The value that will be returned
|
206
|
+
def self.override_config(config_name, config_value)
|
207
|
+
ensure_initialized
|
208
|
+
@shared_instance&.override_config(config_name, config_value)
|
209
|
+
end
|
210
|
+
|
211
|
+
sig { params(user: StatsigUser).returns(T.any(T::Hash[String, T.untyped], NilClass)) }
|
212
|
+
##
|
213
|
+
# Gets all evaluated values for the given user.
|
214
|
+
# These values can then be given to a Statsig Client SDK via bootstrapping.
|
215
|
+
#
|
216
|
+
# @param user A StatsigUser object used for the evaluation
|
217
|
+
#
|
218
|
+
# @note See Ruby Documentation: https://docs.statsig.com/server/rubySDK)
|
219
|
+
def self.get_client_initialize_response(user)
|
220
|
+
ensure_initialized
|
221
|
+
@shared_instance&.get_client_initialize_response(user)
|
222
|
+
end
|
223
|
+
|
224
|
+
sig { returns(T::Hash[String, String]) }
|
225
|
+
##
|
226
|
+
# Internal Statsig metadata for this SDK
|
45
227
|
def self.get_statsig_metadata
|
46
228
|
{
|
47
229
|
'sdkType' => 'ruby-server',
|
48
|
-
'sdkVersion' => '1.
|
230
|
+
'sdkVersion' => '1.20.0',
|
49
231
|
}
|
50
232
|
end
|
51
233
|
|
52
234
|
private
|
53
235
|
|
54
236
|
def self.ensure_initialized
|
55
|
-
if @shared_instance.nil?
|
56
|
-
raise
|
237
|
+
if not defined? @shared_instance or @shared_instance.nil?
|
238
|
+
raise Statsig::UninitializedError.new
|
57
239
|
end
|
58
240
|
end
|
241
|
+
|
242
|
+
sig { params(options: T.any(StatsigOptions, NilClass)).void }
|
243
|
+
|
244
|
+
def self.bind_sorbet_loggers(options)
|
245
|
+
if options&.disable_sorbet_logging_handlers == true
|
246
|
+
return
|
247
|
+
end
|
248
|
+
|
249
|
+
T::Configuration.call_validation_error_handler = lambda do |signature, opts|
|
250
|
+
puts "[Type Error] " + opts[:pretty_message]
|
251
|
+
end
|
252
|
+
|
253
|
+
T::Configuration.inline_type_error_handler = lambda do |error, opts|
|
254
|
+
puts "[Type Error] " + error.message
|
255
|
+
end
|
256
|
+
|
257
|
+
T::Configuration.sig_builder_error_handler = lambda do |error, location|
|
258
|
+
puts "[Type Error] " + error.message
|
259
|
+
end
|
260
|
+
|
261
|
+
T::Configuration.sig_validation_error_handler = lambda do |error, opts|
|
262
|
+
puts "[Type Error] " + error.message
|
263
|
+
end
|
264
|
+
|
265
|
+
return
|
266
|
+
end
|
267
|
+
|
59
268
|
end
|
data/lib/statsig_driver.rb
CHANGED
@@ -1,116 +1,236 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'config_result'
|
2
4
|
require 'evaluator'
|
3
5
|
require 'network'
|
6
|
+
require 'statsig_errors'
|
4
7
|
require 'statsig_event'
|
5
8
|
require 'statsig_logger'
|
6
9
|
require 'statsig_options'
|
7
10
|
require 'statsig_user'
|
8
11
|
require 'spec_store'
|
9
12
|
require 'dynamic_config'
|
13
|
+
require 'error_boundary'
|
10
14
|
require 'layer'
|
15
|
+
require 'sorbet-runtime'
|
16
|
+
require 'diagnostics'
|
11
17
|
|
12
18
|
class StatsigDriver
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
sig { params(secret_key: String, options: T.any(StatsigOptions, NilClass), error_callback: T.any(Method, Proc, NilClass)).void }
|
22
|
+
|
13
23
|
def initialize(secret_key, options = nil, error_callback = nil)
|
14
|
-
|
15
|
-
|
16
|
-
raise 'Invalid secret key provided. Provide your project secret key from the Statsig console'
|
24
|
+
unless secret_key.start_with?('secret-')
|
25
|
+
raise Statsig::ValueError.new('Invalid secret key provided. Provide your project secret key from the Statsig console')
|
17
26
|
end
|
27
|
+
|
18
28
|
if !options.nil? && !options.instance_of?(StatsigOptions)
|
19
|
-
raise 'Invalid options provided. Either provide a valid StatsigOptions object or nil'
|
29
|
+
raise Statsig::ValueError.new('Invalid options provided. Either provide a valid StatsigOptions object or nil')
|
20
30
|
end
|
21
31
|
|
22
|
-
@
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
@err_boundary = Statsig::ErrorBoundary.new(secret_key)
|
33
|
+
@err_boundary.capture(-> {
|
34
|
+
@init_diagnostics = Statsig::Diagnostics.new("initialize")
|
35
|
+
@init_diagnostics.mark("overall", "start")
|
36
|
+
@options = options || StatsigOptions.new
|
37
|
+
@shutdown = false
|
38
|
+
@secret_key = secret_key
|
39
|
+
@net = Statsig::Network.new(secret_key, @options.api_url_base, @options.local_mode)
|
40
|
+
@logger = Statsig::StatsigLogger.new(@net, @options)
|
41
|
+
@evaluator = Statsig::Evaluator.new(@net, @options, error_callback, @init_diagnostics)
|
42
|
+
@init_diagnostics.mark("overall", "end")
|
43
|
+
|
44
|
+
log_init_diagnostics
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
class CheckGateOptions < T::Struct
|
49
|
+
prop :log_exposure, T::Boolean, default: true
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(user: StatsigUser, gate_name: String, options: CheckGateOptions).returns(T::Boolean) }
|
53
|
+
|
54
|
+
def check_gate(user, gate_name, options = CheckGateOptions.new)
|
55
|
+
@err_boundary.capture(-> {
|
56
|
+
user = verify_inputs(user, gate_name, "gate_name")
|
57
|
+
|
58
|
+
res = @evaluator.check_gate(user, gate_name)
|
59
|
+
if res.nil?
|
60
|
+
res = Statsig::ConfigResult.new(gate_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
if res == $fetch_from_server
|
64
|
+
res = check_gate_fallback(user, gate_name)
|
65
|
+
# exposure logged by the server
|
66
|
+
else
|
67
|
+
if options.log_exposure
|
68
|
+
@logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
res.gate_value
|
73
|
+
}, -> { false })
|
74
|
+
|
28
75
|
end
|
29
76
|
|
30
|
-
|
31
|
-
user = verify_inputs(user, gate_name, "gate_name")
|
77
|
+
sig { params(user: StatsigUser, gate_name: String).void }
|
32
78
|
|
79
|
+
def manually_log_gate_exposure(user, gate_name)
|
33
80
|
res = @evaluator.check_gate(user, gate_name)
|
34
|
-
|
35
|
-
|
36
|
-
|
81
|
+
context = {'is_manual_exposure' => true}
|
82
|
+
@logger.log_gate_exposure(user, gate_name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
83
|
+
end
|
37
84
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
85
|
+
class GetConfigOptions < T::Struct
|
86
|
+
prop :log_exposure, T::Boolean, default: true
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(user: StatsigUser, dynamic_config_name: String, options: GetConfigOptions).returns(DynamicConfig) }
|
44
90
|
|
45
|
-
|
91
|
+
def get_config(user, dynamic_config_name, options = GetConfigOptions.new)
|
92
|
+
@err_boundary.capture(-> {
|
93
|
+
user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
|
94
|
+
get_config_impl(user, dynamic_config_name, options)
|
95
|
+
}, -> { DynamicConfig.new(dynamic_config_name) })
|
46
96
|
end
|
47
97
|
|
48
|
-
|
49
|
-
|
50
|
-
get_config_impl(user, dynamic_config_name)
|
98
|
+
class GetExperimentOptions < T::Struct
|
99
|
+
prop :log_exposure, T::Boolean, default: true
|
51
100
|
end
|
52
101
|
|
53
|
-
|
54
|
-
|
55
|
-
|
102
|
+
sig { params(user: StatsigUser, experiment_name: String, options: GetExperimentOptions).returns(DynamicConfig) }
|
103
|
+
|
104
|
+
def get_experiment(user, experiment_name, options = GetExperimentOptions.new)
|
105
|
+
@err_boundary.capture(-> {
|
106
|
+
user = verify_inputs(user, experiment_name, "experiment_name")
|
107
|
+
get_config_impl(user, experiment_name, options)
|
108
|
+
}, -> { DynamicConfig.new(experiment_name) })
|
56
109
|
end
|
57
110
|
|
58
|
-
|
59
|
-
user = verify_inputs(user, layer_name, "layer_name")
|
111
|
+
sig { params(user: StatsigUser, config_name: String).void }
|
60
112
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
113
|
+
def manually_log_config_exposure(user, config_name)
|
114
|
+
res = @evaluator.get_config(user, config_name)
|
115
|
+
context = {'is_manual_exposure' => true}
|
116
|
+
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details, context)
|
117
|
+
end
|
65
118
|
|
66
|
-
|
67
|
-
|
68
|
-
|
119
|
+
class GetLayerOptions < T::Struct
|
120
|
+
prop :log_exposure, T::Boolean, default: true
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { params(user: StatsigUser, layer_name: String, options: GetLayerOptions).returns(Layer) }
|
124
|
+
|
125
|
+
def get_layer(user, layer_name, options = GetLayerOptions.new)
|
126
|
+
@err_boundary.capture(-> {
|
127
|
+
user = verify_inputs(user, layer_name, "layer_name")
|
128
|
+
|
129
|
+
res = @evaluator.get_layer(user, layer_name)
|
130
|
+
if res.nil?
|
131
|
+
res = Statsig::ConfigResult.new(layer_name)
|
69
132
|
end
|
70
|
-
res = get_config_fallback(user, res.config_delegate)
|
71
|
-
# exposure logged by the server
|
72
|
-
end
|
73
133
|
|
74
|
-
|
75
|
-
|
134
|
+
if res == $fetch_from_server
|
135
|
+
if res.config_delegate.empty?
|
136
|
+
return Layer.new(layer_name)
|
137
|
+
end
|
138
|
+
res = get_config_fallback(user, res.config_delegate)
|
139
|
+
# exposure logged by the server
|
140
|
+
end
|
141
|
+
|
142
|
+
exposure_log_func = options.log_exposure ? lambda { |layer, parameter_name|
|
143
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res)
|
144
|
+
} : nil
|
145
|
+
Layer.new(res.name, res.json_value, res.rule_id, exposure_log_func)
|
146
|
+
}, -> {
|
147
|
+
Layer.new(layer_name)
|
76
148
|
})
|
77
149
|
end
|
78
150
|
|
151
|
+
sig { params(user: StatsigUser, layer_name: String, parameter_name: String).void }
|
152
|
+
|
153
|
+
def manually_log_layer_parameter_exposure(user, layer_name, parameter_name)
|
154
|
+
res = @evaluator.get_layer(user, layer_name)
|
155
|
+
layer = Layer.new(layer_name, res.json_value, res.rule_id)
|
156
|
+
context = {'is_manual_exposure' => true}
|
157
|
+
@logger.log_layer_exposure(user, layer, parameter_name, res, context)
|
158
|
+
end
|
159
|
+
|
79
160
|
def log_event(user, event_name, value = nil, metadata = nil)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
161
|
+
@err_boundary.capture(-> {
|
162
|
+
if !user.nil? && !user.instance_of?(StatsigUser)
|
163
|
+
raise Statsig::ValueError.new('Must provide a valid StatsigUser or nil')
|
164
|
+
end
|
165
|
+
check_shutdown
|
84
166
|
|
85
|
-
|
167
|
+
user = normalize_user(user)
|
86
168
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
169
|
+
event = StatsigEvent.new(event_name)
|
170
|
+
event.user = user
|
171
|
+
event.value = value
|
172
|
+
event.metadata = metadata
|
173
|
+
event.statsig_metadata = Statsig.get_statsig_metadata
|
174
|
+
@logger.log_event(event)
|
175
|
+
})
|
93
176
|
end
|
94
177
|
|
95
178
|
def shutdown
|
96
|
-
@
|
97
|
-
|
98
|
-
|
179
|
+
@err_boundary.capture(-> {
|
180
|
+
@shutdown = true
|
181
|
+
@logger.shutdown
|
182
|
+
@evaluator.shutdown
|
183
|
+
})
|
184
|
+
end
|
185
|
+
|
186
|
+
def override_gate(gate_name, gate_value)
|
187
|
+
@err_boundary.capture(-> {
|
188
|
+
@evaluator.override_gate(gate_name, gate_value)
|
189
|
+
})
|
190
|
+
end
|
191
|
+
|
192
|
+
def override_config(config_name, config_value)
|
193
|
+
@err_boundary.capture(-> {
|
194
|
+
@evaluator.override_config(config_name, config_value)
|
195
|
+
})
|
196
|
+
end
|
197
|
+
|
198
|
+
# @param [StatsigUser] user
|
199
|
+
# @return [Hash]
|
200
|
+
def get_client_initialize_response(user)
|
201
|
+
@err_boundary.capture(-> {
|
202
|
+
normalize_user(user)
|
203
|
+
@evaluator.get_client_initialize_response(user)
|
204
|
+
}, -> { nil })
|
205
|
+
end
|
206
|
+
|
207
|
+
def maybe_restart_background_threads
|
208
|
+
if @options.local_mode
|
209
|
+
return
|
210
|
+
end
|
211
|
+
|
212
|
+
@err_boundary.capture(-> {
|
213
|
+
@evaluator.maybe_restart_background_threads
|
214
|
+
@logger.maybe_restart_background_threads
|
215
|
+
})
|
99
216
|
end
|
100
217
|
|
101
218
|
private
|
102
219
|
|
220
|
+
sig { params(user: StatsigUser, config_name: String, variable_name: String).returns(StatsigUser) }
|
221
|
+
|
103
222
|
def verify_inputs(user, config_name, variable_name)
|
104
223
|
validate_user(user)
|
105
224
|
if !config_name.is_a?(String) || config_name.empty?
|
106
|
-
raise "Invalid #{variable_name} provided"
|
225
|
+
raise Statsig::ValueError.new("Invalid #{variable_name} provided")
|
107
226
|
end
|
108
227
|
|
109
228
|
check_shutdown
|
229
|
+
maybe_restart_background_threads
|
110
230
|
normalize_user(user)
|
111
231
|
end
|
112
232
|
|
113
|
-
def get_config_impl(user, config_name)
|
233
|
+
def get_config_impl(user, config_name, options)
|
114
234
|
res = @evaluator.get_config(user, config_name)
|
115
235
|
if res.nil?
|
116
236
|
res = Statsig::ConfigResult.new(config_name)
|
@@ -120,7 +240,9 @@ class StatsigDriver
|
|
120
240
|
res = get_config_fallback(user, config_name)
|
121
241
|
# exposure logged by the server
|
122
242
|
else
|
123
|
-
|
243
|
+
if options.log_exposure
|
244
|
+
@logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
|
245
|
+
end
|
124
246
|
end
|
125
247
|
|
126
248
|
DynamicConfig.new(res.name, res.json_value, res.rule_id)
|
@@ -132,9 +254,9 @@ class StatsigDriver
|
|
132
254
|
(
|
133
255
|
# user_id is nil and custom_ids is not a hash with entries
|
134
256
|
!user.user_id.is_a?(String) &&
|
135
|
-
|
257
|
+
(!user.custom_ids.is_a?(Hash) || user.custom_ids.size == 0)
|
136
258
|
)
|
137
|
-
raise 'Must provide a valid StatsigUser with a user_id or at least a custom ID. See https://docs.statsig.com/messages/serverRequiredUserID/ for more details.'
|
259
|
+
raise Statsig::ValueError.new('Must provide a valid StatsigUser with a user_id or at least a custom ID. See https://docs.statsig.com/messages/serverRequiredUserID/ for more details.')
|
138
260
|
end
|
139
261
|
end
|
140
262
|
|
@@ -180,4 +302,12 @@ class StatsigDriver
|
|
180
302
|
network_result['rule_id'],
|
181
303
|
)
|
182
304
|
end
|
183
|
-
|
305
|
+
|
306
|
+
def log_init_diagnostics
|
307
|
+
if @options.disable_diagnostics_logging
|
308
|
+
return
|
309
|
+
end
|
310
|
+
|
311
|
+
@logger.log_diagnostics_event(@init_diagnostics)
|
312
|
+
end
|
313
|
+
end
|
data/lib/statsig_event.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
class StatsigEvent
|
2
3
|
attr_accessor :value
|
3
4
|
attr_accessor :metadata
|
@@ -7,7 +8,11 @@ class StatsigEvent
|
|
7
8
|
|
8
9
|
def initialize(event_name)
|
9
10
|
@event_name = event_name
|
10
|
-
@
|
11
|
+
@value = nil
|
12
|
+
@metadata = nil
|
13
|
+
@secondary_exposures = nil
|
14
|
+
@user = nil
|
15
|
+
@time = (Time.now.to_f * 1000).to_i
|
11
16
|
end
|
12
17
|
|
13
18
|
def user=(value)
|