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.
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)