statsig 1.10.0 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
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
- unless @shared_instance.nil?
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.10.0',
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 'Must call initialize first.'
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
@@ -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
- super()
15
- if !secret_key.is_a?(String) || !secret_key.start_with?('secret-')
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
- @options = options || StatsigOptions.new
23
- @shutdown = false
24
- @secret_key = secret_key
25
- @net = Statsig::Network.new(secret_key, @options.api_url_base)
26
- @logger = Statsig::StatsigLogger.new(@net)
27
- @evaluator = Statsig::Evaluator.new(@net, @options, error_callback)
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
- def check_gate(user, gate_name)
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
- if res.nil?
35
- res = Statsig::ConfigResult.new(gate_name)
36
- end
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
- if res == $fetch_from_server
39
- res = check_gate_fallback(user, gate_name)
40
- # exposure logged by the server
41
- else
42
- @logger.log_gate_exposure(user, res.name, res.gate_value, res.rule_id, res.secondary_exposures)
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
- res.gate_value
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
- def get_config(user, dynamic_config_name)
49
- user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
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
- def get_experiment(user, experiment_name)
54
- user = verify_inputs(user, experiment_name, "experiment_name")
55
- get_config_impl(user, experiment_name)
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
- def get_layer(user, layer_name)
59
- user = verify_inputs(user, layer_name, "layer_name")
111
+ sig { params(user: StatsigUser, config_name: String).void }
60
112
 
61
- res = @evaluator.get_layer(user, layer_name)
62
- if res.nil?
63
- res = Statsig::ConfigResult.new(layer_name)
64
- end
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
- if res == $fetch_from_server
67
- if res.config_delegate.empty?
68
- return Layer.new(layer_name)
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
- Layer.new(res.name, res.json_value, res.rule_id, lambda { |layer, parameter_name|
75
- @logger.log_layer_exposure(user, layer, parameter_name, res)
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
- if !user.nil? && !user.instance_of?(StatsigUser)
81
- raise 'Must provide a valid StatsigUser or nil'
82
- end
83
- check_shutdown
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
- user = normalize_user(user)
167
+ user = normalize_user(user)
86
168
 
87
- event = StatsigEvent.new(event_name)
88
- event.user = user
89
- event.value = value
90
- event.metadata = metadata
91
- event.statsig_metadata = Statsig.get_statsig_metadata
92
- @logger.log_event(event)
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
- @shutdown = true
97
- @logger.flush(true)
98
- @evaluator.shutdown
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
- @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures)
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
- (!user.custom_ids.is_a?(Hash) || user.custom_ids.size == 0)
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
- end
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
@@ -0,0 +1,11 @@
1
+ module Statsig
2
+ class UninitializedError < StandardError
3
+ def initialize(msg="Must call initialize first.")
4
+ super
5
+ end
6
+ end
7
+
8
+ class ValueError < StandardError
9
+
10
+ end
11
+ 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
- @time = Time.now.to_f * 1000
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)