statsig 1.30.0 → 1.31.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72119a11268473774b98457f89017155813cdaab4c19b8f07e1aad370ae6dd17
4
- data.tar.gz: 69cb96c04c6b9c322bb239332b8c406a6b543bb42b0cab6da9e641d44fa15371
3
+ metadata.gz: 1442642777f1e73e38aa9944a88f9eab27a88df0c856ed0bc708555fe51a7b91
4
+ data.tar.gz: '07159c53071c5650f01016e485aca359568025f96dbeea39e3b99d39d1286662'
5
5
  SHA512:
6
- metadata.gz: d75c0a0d9c6529843c554d05b3ae49e6d863cd01da11f932e679bddddffab4287ef036af37c3cd8fff9310c10cd6758bc6e5435f78626245c5d1f315b91e09eb
7
- data.tar.gz: b9886d0b78d1491393cfacdea4c1f4cdeae1d2a993a0f5a0875011be1083bb63e4a7721fb3a007869eaa7eaf69ad300438d8038c099e88277958d4daf6d226f7
6
+ metadata.gz: 1454423d5e182969c8abce5ab37a7aef9ed9ff854398725b13d3743dc85f49dffb2a7e9176ffdda6e18bef2cdbc4c191aac0716ac204b5a054e1e11e359bde6c
7
+ data.tar.gz: 1a07714268ca3bec58974e3059c34dcc6a7e7f0a8617fd22874b0c49342546cc475ea9b6ec24569e4d07109aca7c0afdd5ead59f809512d5a9ba83ca0894e4e7
@@ -3,13 +3,6 @@
3
3
  require_relative 'hash_utils'
4
4
  require 'sorbet-runtime'
5
5
 
6
- $empty_eval_result = {
7
- :gate_value => false,
8
- :json_value => {},
9
- :rule_id => "",
10
- :is_experiment_group => false,
11
- :secondary_exposures => []
12
- }
13
6
 
14
7
  module ClientInitializeHelpers
15
8
  class ResponseFormatter
@@ -51,18 +44,6 @@ module ClientInitializeHelpers
51
44
  return nil
52
45
  end
53
46
 
54
- safe_eval_result = eval_result == $fetch_from_server ? $empty_eval_result : {
55
- :gate_value => eval_result.gate_value,
56
- :json_value => eval_result.json_value,
57
- :rule_id => eval_result.rule_id,
58
- :group_name => eval_result.group_name,
59
- :id_type => eval_result.id_type,
60
- :config_delegate => eval_result.config_delegate,
61
- :is_experiment_group => eval_result.is_experiment_group,
62
- :secondary_exposures => filter_segments_from_secondary_exposures(eval_result.secondary_exposures),
63
- :undelegated_sec_exps => eval_result.undelegated_sec_exps
64
- }
65
-
66
47
  category = config_spec['type']
67
48
  entity_type = config_spec['entity']
68
49
 
@@ -75,26 +56,26 @@ module ClientInitializeHelpers
75
56
  return nil
76
57
  end
77
58
 
78
- result['value'] = safe_eval_result[:gate_value]
79
- result["group_name"] = safe_eval_result[:group_name]
80
- result["id_type"] = safe_eval_result[:id_type]
59
+ result['value'] = eval_result.gate_value
60
+ result["group_name"] = eval_result.group_name
61
+ result["id_type"] = eval_result.id_type
81
62
  when 'dynamic_config'
82
63
  id_type = config_spec['idType']
83
- result['value'] = safe_eval_result[:json_value]
84
- result["group"] = safe_eval_result[:rule_id]
85
- result["group_name"] = safe_eval_result[:group_name]
86
- result["id_type"] = safe_eval_result[:id_type]
64
+ result['value'] = eval_result.json_value
65
+ result["group"] = eval_result.rule_id
66
+ result["group_name"] = eval_result.group_name
67
+ result["id_type"] = eval_result.id_type
87
68
  result["is_device_based"] = id_type.is_a?(String) && id_type.downcase == 'stableid'
88
69
  else
89
70
  return nil
90
71
  end
91
72
 
92
73
  if entity_type == 'experiment'
93
- populate_experiment_fields(config_name, config_spec, safe_eval_result, result)
74
+ populate_experiment_fields(config_name, config_spec, eval_result, result)
94
75
  end
95
76
 
96
77
  if entity_type == 'layer'
97
- populate_layer_fields(config_spec, safe_eval_result, result)
78
+ populate_layer_fields(config_spec, eval_result, result)
98
79
  result.delete('id_type') # not exposed for layer configs in /initialize
99
80
  end
100
81
 
@@ -102,8 +83,8 @@ module ClientInitializeHelpers
102
83
  [hashed_name, result.merge(
103
84
  {
104
85
  "name" => hashed_name,
105
- "rule_id" => safe_eval_result[:rule_id],
106
- "secondary_exposures" => clean_exposures(safe_eval_result[:secondary_exposures])
86
+ "rule_id" => eval_result.rule_id,
87
+ "secondary_exposures" => clean_exposures(eval_result.secondary_exposures)
107
88
  }).compact]
108
89
  end
109
90
 
@@ -118,7 +99,7 @@ module ClientInitializeHelpers
118
99
  end
119
100
 
120
101
  def populate_experiment_fields(config_name, config_spec, eval_result, result)
121
- result["is_user_in_experiment"] = eval_result[:is_experiment_group]
102
+ result["is_user_in_experiment"] = eval_result.is_experiment_group
122
103
  result["is_experiment_active"] = config_spec['isActive'] == true
123
104
 
124
105
  if config_spec['hasSharedParams'] != true
@@ -138,7 +119,7 @@ module ClientInitializeHelpers
138
119
  end
139
120
 
140
121
  def populate_layer_fields(config_spec, eval_result, result)
141
- delegate = eval_result[:config_delegate]
122
+ delegate = eval_result.config_delegate
142
123
  result["explicit_parameters"] = config_spec["explicitParameters"] || []
143
124
 
144
125
  if delegate.nil? == false && delegate.empty? == false
@@ -151,7 +132,7 @@ module ClientInitializeHelpers
151
132
  result["explicit_parameters"] = delegate_spec["explicitParameters"] || []
152
133
  end
153
134
 
154
- result["undelegated_secondary_exposures"] = clean_exposures(eval_result[:undelegated_sec_exps] || [])
135
+ result["undelegated_secondary_exposures"] = clean_exposures(eval_result.undelegated_sec_exps || [])
155
136
  end
156
137
 
157
138
  def hash_name(name)
data/lib/diagnostics.rb CHANGED
@@ -6,18 +6,14 @@ module Statsig
6
6
  class Diagnostics
7
7
  extend T::Sig
8
8
 
9
- sig { returns(String) }
10
- attr_accessor :context
11
-
12
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
9
+ sig { returns(T::Hash[String, T::Array[T::Hash[Symbol, T.untyped]]]) }
13
10
  attr_reader :markers
14
11
 
15
12
  sig { returns(T::Hash[String, Numeric]) }
16
13
  attr_accessor :sample_rates
17
14
 
18
- def initialize(context)
19
- @context = context
20
- @markers = []
15
+ def initialize()
16
+ @markers = {:initialize => [], :api_call => [], :config_sync => []}
21
17
  @sample_rates = {}
22
18
  end
23
19
 
@@ -26,11 +22,12 @@ module Statsig
26
22
  key: String,
27
23
  action: String,
28
24
  step: T.any(String, NilClass),
29
- tags: T::Hash[Symbol, T.untyped]
25
+ tags: T::Hash[Symbol, T.untyped],
26
+ context: String
30
27
  ).void
31
28
  end
32
29
 
33
- def mark(key, action, step, tags)
30
+ def mark(key, action, step, tags, context)
34
31
  marker = {
35
32
  key: key,
36
33
  action: action,
@@ -44,60 +41,49 @@ module Statsig
44
41
  marker[key] = val
45
42
  end
46
43
  end
47
- @markers.push(marker)
44
+ if @markers[context].nil?
45
+ @markers[context] = []
46
+ end
47
+ @markers[context].push(marker)
48
48
  end
49
49
 
50
50
  sig do
51
51
  params(
52
+ context: String,
52
53
  key: String,
53
54
  step: T.any(String, NilClass),
54
55
  tags: T::Hash[Symbol, T.untyped]
55
56
  ).returns(Tracker)
56
57
  end
57
- def track(key, step = nil, tags = {})
58
- tracker = Tracker.new(self, key, step, tags)
58
+ def track(context, key, step = nil, tags = {})
59
+ tracker = Tracker.new(self, context, key, step, tags)
59
60
  tracker.start(**tags)
60
61
  tracker
61
62
  end
62
63
 
63
- sig { returns(T::Hash[Symbol, T.untyped]) }
64
-
65
- def serialize
66
- {
67
- context: @context.clone,
68
- markers: @markers.clone
69
- }
70
- end
71
-
72
- def serialize_with_sampling
73
- marker_keys = @markers.map { |e| e[:key] }
64
+ def serialize_with_sampling(context)
65
+ marker_keys = @markers[context].map { |e| e[:key] }
74
66
  unique_marker_keys = marker_keys.uniq { |e| e }
75
67
  sampled_marker_keys = unique_marker_keys.select do |key|
76
68
  @sample_rates.key?(key) && !self.class.sample(@sample_rates[key])
77
69
  end
78
- final_markers = @markers.select do |marker|
70
+ final_markers = @markers[context].select do |marker|
79
71
  !sampled_marker_keys.include?(marker[:key])
80
72
  end
81
73
  {
82
- context: @context.clone,
83
- markers: final_markers
74
+ context: context.clone,
75
+ markers: final_markers.clone
84
76
  }
85
77
  end
86
78
 
87
- def clear_markers
88
- @markers.clear
79
+ def clear_markers(context)
80
+ @markers[context].clear
89
81
  end
90
82
 
91
83
  def self.sample(rate_over_ten_thousand)
92
84
  rand * 10_000 < rate_over_ten_thousand
93
85
  end
94
86
 
95
- class Context
96
- INITIALIZE = 'initialize'.freeze
97
- CONFIG_SYNC = 'config_sync'.freeze
98
- API_CALL = 'api_call'.freeze
99
- end
100
-
101
87
  API_CALL_KEYS = %w[check_gate get_config get_experiment get_layer].freeze
102
88
 
103
89
  class Tracker
@@ -106,24 +92,26 @@ module Statsig
106
92
  sig do
107
93
  params(
108
94
  diagnostics: Diagnostics,
95
+ context: String,
109
96
  key: String,
110
97
  step: T.any(String, NilClass),
111
98
  tags: T::Hash[Symbol, T.untyped]
112
99
  ).void
113
100
  end
114
- def initialize(diagnostics, key, step, tags = {})
101
+ def initialize(diagnostics, context, key, step, tags = {})
115
102
  @diagnostics = diagnostics
103
+ @context = context
116
104
  @key = key
117
105
  @step = step
118
106
  @tags = tags
119
107
  end
120
108
 
121
109
  def start(**tags)
122
- @diagnostics.mark(@key, 'start', @step, tags.nil? ? {} : tags.merge(@tags))
110
+ @diagnostics.mark(@key, 'start', @step, tags.nil? ? {} : tags.merge(@tags), @context)
123
111
  end
124
112
 
125
113
  def end(**tags)
126
- @diagnostics.mark(@key, 'end', @step, tags.nil? ? {} : tags.merge(@tags))
114
+ @diagnostics.mark(@key, 'end', @step, tags.nil? ? {} : tags.merge(@tags), @context)
127
115
  end
128
116
  end
129
117
  end
@@ -9,6 +9,7 @@ module Statsig
9
9
  BOOTSTRAP = 'Bootstrap'.freeze
10
10
  DATA_ADAPTER = 'DataAdapter'.freeze
11
11
  PERSISTED = 'Persisted'.freeze
12
+ UNSUPPORTED = 'Unsupported'.freeze
12
13
  end
13
14
 
14
15
  class EvaluationDetails
data/lib/evaluator.rb CHANGED
@@ -13,13 +13,12 @@ require 'evaluation_details'
13
13
  require 'user_agent_parser/operating_system'
14
14
  require 'user_persistent_storage_utils'
15
15
 
16
- $fetch_from_server = 'fetch_from_server'
17
- $type_dynamic_config = 'dynamic_config'
18
-
19
16
  module Statsig
20
17
  class Evaluator
21
18
  extend T::Sig
22
19
 
20
+ UNSUPPORTED_EVALUATION = :unsupported_eval
21
+
23
22
  sig { returns(SpecStore) }
24
23
  attr_accessor :spec_store
25
24
 
@@ -220,7 +219,21 @@ module Statsig
220
219
  until i >= config['rules'].length do
221
220
  rule = config['rules'][i]
222
221
  result = eval_rule(user, rule)
223
- return $fetch_from_server if result.to_s == $fetch_from_server
222
+ return Statsig::ConfigResult.new(
223
+ config['name'],
224
+ false,
225
+ config['defaultValue'],
226
+ '',
227
+ exposures,
228
+ evaluation_details: EvaluationDetails.new(
229
+ @spec_store.last_config_sync_time,
230
+ @spec_store.initial_config_sync_time,
231
+ EvaluationReason::UNSUPPORTED,
232
+ ),
233
+ group_name: nil,
234
+ id_type: config['idType'],
235
+ target_app_ids: config['targetAppIDs']
236
+ ) if result == UNSUPPORTED_EVALUATION
224
237
  exposures = exposures + result.secondary_exposures
225
238
  if result.gate_value
226
239
 
@@ -278,8 +291,8 @@ module Statsig
278
291
  i = 0
279
292
  until i >= rule['conditions'].length do
280
293
  result = eval_condition(user, rule['conditions'][i])
281
- if result.to_s == $fetch_from_server
282
- return $fetch_from_server
294
+ if result == UNSUPPORTED_EVALUATION
295
+ return UNSUPPORTED_EVALUATION
283
296
  end
284
297
 
285
298
  if result.is_a?(Hash)
@@ -312,7 +325,7 @@ module Statsig
312
325
  return nil unless (config = @spec_store.get_config(delegate))
313
326
 
314
327
  delegated_result = self.eval_spec(user, config)
315
- return $fetch_from_server if delegated_result.to_s == $fetch_from_server
328
+ return UNSUPPORTED_EVALUATION if delegated_result == UNSUPPORTED_EVALUATION
316
329
 
317
330
  delegated_result.name = name
318
331
  delegated_result.config_delegate = delegate
@@ -332,7 +345,7 @@ module Statsig
332
345
  additional_values = Hash.new unless additional_values.is_a? Hash
333
346
  id_type = condition['idType']
334
347
 
335
- return $fetch_from_server unless type.is_a? String
348
+ return UNSUPPORTED_EVALUATION unless type.is_a? String
336
349
  type = type.downcase
337
350
 
338
351
  case type
@@ -340,7 +353,7 @@ module Statsig
340
353
  return true
341
354
  when 'fail_gate', 'pass_gate'
342
355
  other_gate_result = check_gate(user, target)
343
- return $fetch_from_server if other_gate_result.to_s == $fetch_from_server
356
+ return UNSUPPORTED_EVALUATION if other_gate_result == UNSUPPORTED_EVALUATION
344
357
 
345
358
  gate_value = other_gate_result&.gate_value == true
346
359
  new_exposure = {
@@ -355,10 +368,8 @@ module Statsig
355
368
  }
356
369
  when 'ip_based'
357
370
  value = get_value_from_user(user, field) || get_value_from_ip(user, field)
358
- return $fetch_from_server if value.to_s == $fetch_from_server
359
371
  when 'ua_based'
360
372
  value = get_value_from_user(user, field) || get_value_from_ua(user, field)
361
- return $fetch_from_server if value.to_s == $fetch_from_server
362
373
  when 'user_field'
363
374
  value = get_value_from_user(user, field)
364
375
  when 'environment_field'
@@ -377,10 +388,10 @@ module Statsig
377
388
  when 'unit_id'
378
389
  value = user.get_unit_id(id_type)
379
390
  else
380
- return $fetch_from_server
391
+ return UNSUPPORTED_EVALUATION
381
392
  end
382
393
 
383
- return $fetch_from_server if value.to_s == $fetch_from_server || !operator.is_a?(String)
394
+ return UNSUPPORTED_EVALUATION if !operator.is_a?(String)
384
395
  operator = operator.downcase
385
396
 
386
397
  case operator
@@ -462,7 +473,7 @@ module Statsig
462
473
  return false
463
474
  end
464
475
  else
465
- return $fetch_from_server
476
+ return UNSUPPORTED_EVALUATION
466
477
  end
467
478
  end
468
479
 
data/lib/network.rb CHANGED
@@ -132,26 +132,6 @@ module Statsig
132
132
  request(method, endpoint, body, retries - 1, backoff * @backoff_multiplier)
133
133
  end
134
134
 
135
- def check_gate(user, gate_name)
136
- request_body = JSON.generate({ 'user' => user&.serialize(false), 'gateName' => gate_name })
137
- response, = post('check_gate', request_body)
138
- return JSON.parse(response.body) unless response.nil?
139
-
140
- false
141
- rescue StandardError
142
- false
143
- end
144
-
145
- def get_config(user, dynamic_config_name)
146
- request_body = JSON.generate({ 'user' => user&.serialize(false), 'configName' => dynamic_config_name })
147
- response, = post('get_config', request_body)
148
- return JSON.parse(response.body) unless response.nil?
149
-
150
- nil
151
- rescue StandardError
152
- nil
153
- end
154
-
155
135
  def post_logs(events)
156
136
  json_body = JSON.generate({ 'events' => events, 'statsigMetadata' => Statsig.get_statsig_metadata })
157
137
  post('log_event', json_body, @post_logs_retry_limit)
data/lib/spec_store.rb CHANGED
@@ -48,7 +48,7 @@ module Statsig
48
48
  if !@options.data_store.nil?
49
49
  puts 'data_store gets priority over bootstrap_values. bootstrap_values will be ignored'
50
50
  else
51
- tracker = @diagnostics.track('bootstrap', 'process')
51
+ tracker = @diagnostics.track('initialize','bootstrap', 'process')
52
52
  begin
53
53
  if process_specs(options.bootstrap_values)
54
54
  @init_reason = EvaluationReason::BOOTSTRAP
@@ -63,18 +63,18 @@ module Statsig
63
63
 
64
64
  unless @options.data_store.nil?
65
65
  @options.data_store.init
66
- load_config_specs_from_storage_adapter
66
+ load_config_specs_from_storage_adapter('initialize')
67
67
  end
68
68
 
69
69
  if @init_reason == EvaluationReason::UNINITIALIZED
70
- download_config_specs
70
+ download_config_specs('initialize')
71
71
  end
72
72
 
73
73
  @initial_config_sync_time = @last_config_sync_time == 0 ? -1 : @last_config_sync_time
74
74
  if !@options.data_store.nil?
75
- get_id_lists_from_adapter
75
+ get_id_lists_from_adapter('initialize')
76
76
  else
77
- get_id_lists_from_network
77
+ get_id_lists_from_network('initialize')
78
78
  end
79
79
 
80
80
  @config_sync_thread = spawn_sync_config_specs_thread
@@ -172,41 +172,39 @@ module Statsig
172
172
  end
173
173
 
174
174
  def sync_config_specs
175
- @diagnostics.context = 'config_sync'
176
175
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_KEY)
177
- load_config_specs_from_storage_adapter
176
+ load_config_specs_from_storage_adapter('config_sync')
178
177
  else
179
- download_config_specs
178
+ download_config_specs('config_sync')
180
179
  end
181
- @logger.log_diagnostics_event(@diagnostics)
180
+ @logger.log_diagnostics_event(@diagnostics, 'config_sync')
182
181
  end
183
182
 
184
183
  def sync_id_lists
185
- @diagnostics.context = 'config_sync'
186
184
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::ID_LISTS_KEY)
187
- get_id_lists_from_adapter
185
+ get_id_lists_from_adapter('config_sync')
188
186
  else
189
- get_id_lists_from_network
187
+ get_id_lists_from_network('config_sync')
190
188
  end
191
- @logger.log_diagnostics_event(@diagnostics)
189
+ @logger.log_diagnostics_event(@diagnostics, 'config_sync')
192
190
  end
193
191
 
194
192
  private
195
193
 
196
- def load_config_specs_from_storage_adapter
197
- tracker = @diagnostics.track('data_store_config_specs', 'fetch')
194
+ def load_config_specs_from_storage_adapter(context)
195
+ tracker = @diagnostics.track(context, 'data_store_config_specs', 'fetch')
198
196
  cached_values = @options.data_store.get(Interfaces::IDataStore::CONFIG_SPECS_KEY)
199
197
  tracker.end(success: true)
200
198
  return if cached_values.nil?
201
199
 
202
- tracker = @diagnostics.track('data_store_config_specs', 'process')
200
+ tracker = @diagnostics.track(context, 'data_store_config_specs', 'process')
203
201
  process_specs(cached_values, from_adapter: true)
204
202
  @init_reason = EvaluationReason::DATA_ADAPTER
205
203
  tracker.end(success: true)
206
204
  rescue StandardError
207
205
  # Fallback to network
208
206
  tracker.end(success: false)
209
- download_config_specs
207
+ download_config_specs(context)
210
208
  end
211
209
 
212
210
  def save_config_specs_to_storage_adapter(specs_string)
@@ -246,8 +244,8 @@ module Statsig
246
244
  end
247
245
  end
248
246
 
249
- def download_config_specs
250
- tracker = @diagnostics.track('download_config_specs', 'network_request')
247
+ def download_config_specs(context)
248
+ tracker = @diagnostics.track(context, 'download_config_specs', 'network_request')
251
249
 
252
250
  error = nil
253
251
  begin
@@ -260,8 +258,7 @@ module Statsig
260
258
 
261
259
  if e.nil?
262
260
  unless response.nil?
263
- tracker = @diagnostics.track('download_config_specs', 'process')
264
-
261
+ tracker = @diagnostics.track(context, 'download_config_specs', 'process')
265
262
  if process_specs(response.body.to_s)
266
263
  @init_reason = EvaluationReason::NETWORK
267
264
  end
@@ -330,18 +327,18 @@ module Statsig
330
327
  true
331
328
  end
332
329
 
333
- def get_id_lists_from_adapter
334
- tracker = @diagnostics.track('data_store_id_lists', 'fetch')
330
+ def get_id_lists_from_adapter(context)
331
+ tracker = @diagnostics.track(context, 'data_store_id_lists', 'fetch')
335
332
  cached_values = @options.data_store.get(Interfaces::IDataStore::ID_LISTS_KEY)
336
333
  return if cached_values.nil?
337
334
 
338
335
  tracker.end(success: true)
339
336
  id_lists = JSON.parse(cached_values)
340
- process_id_lists(id_lists, from_adapter: true)
337
+ process_id_lists(id_lists, context, from_adapter: true)
341
338
  rescue StandardError
342
339
  # Fallback to network
343
340
  tracker.end(success: false)
344
- get_id_lists_from_network
341
+ get_id_lists_from_network(context)
345
342
  end
346
343
 
347
344
  def save_id_lists_to_adapter(id_lists_raw_json)
@@ -351,8 +348,8 @@ module Statsig
351
348
  @options.data_store.set(Interfaces::IDataStore::ID_LISTS_KEY, id_lists_raw_json)
352
349
  end
353
350
 
354
- def get_id_lists_from_network
355
- tracker = @diagnostics.track('get_id_list_sources', 'network_request')
351
+ def get_id_lists_from_network(context)
352
+ tracker = @diagnostics.track(context, 'get_id_list_sources', 'network_request')
356
353
  response, e = @network.post('get_id_lists', JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }))
357
354
  code = response&.status.to_i
358
355
  if e.is_a? NetworkError
@@ -366,21 +363,21 @@ module Statsig
366
363
 
367
364
  begin
368
365
  server_id_lists = JSON.parse(response)
369
- process_id_lists(server_id_lists)
366
+ process_id_lists(server_id_lists, context)
370
367
  save_id_lists_to_adapter(response.body.to_s)
371
368
  rescue
372
369
  # Ignored, will try again
373
370
  end
374
371
  end
375
372
 
376
- def process_id_lists(new_id_lists, from_adapter: false)
373
+ def process_id_lists(new_id_lists, context, from_adapter: false)
377
374
  local_id_lists = @specs[:id_lists]
378
375
  if !new_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
379
376
  return
380
377
  end
381
378
  tasks = []
382
379
 
383
- tracker = @diagnostics.track(
380
+ tracker = @diagnostics.track(context,
384
381
  from_adapter ? 'data_store_id_lists' : 'get_id_list_sources',
385
382
  'process',
386
383
  { idListCount: new_id_lists.length }
@@ -430,9 +427,9 @@ module Statsig
430
427
 
431
428
  tasks << Concurrent::Promise.execute(:executor => @id_list_thread_pool) do
432
429
  if from_adapter
433
- get_single_id_list_from_adapter(local_list)
430
+ get_single_id_list_from_adapter(local_list, context)
434
431
  else
435
- download_single_id_list(local_list)
432
+ download_single_id_list(local_list, context)
436
433
  end
437
434
  end
438
435
  end
@@ -441,12 +438,12 @@ module Statsig
441
438
  tracker.end(success: result.state == :fulfilled)
442
439
  end
443
440
 
444
- def get_single_id_list_from_adapter(list)
445
- tracker = @diagnostics.track('data_store_id_list', 'fetch', { url: list.url })
441
+ def get_single_id_list_from_adapter(list, context)
442
+ tracker = @diagnostics.track(context, 'data_store_id_list', 'fetch', { url: list.url })
446
443
  cached_values = @options.data_store.get("#{Interfaces::IDataStore::ID_LISTS_KEY}::#{list.name}")
447
444
  tracker.end(success: true)
448
445
  content = cached_values.to_s
449
- process_single_id_list(list, content, from_adapter: true)
446
+ process_single_id_list(list, context, content, from_adapter: true)
450
447
  rescue StandardError
451
448
  tracker.end(success: false)
452
449
  nil
@@ -458,10 +455,10 @@ module Statsig
458
455
  @options.data_store.set("#{Interfaces::IDataStore::ID_LISTS_KEY}::#{name}", content)
459
456
  end
460
457
 
461
- def download_single_id_list(list)
458
+ def download_single_id_list(list, context)
462
459
  nil unless list.is_a? IDList
463
460
  http = HTTP.headers({ 'Range' => "bytes=#{list&.size || 0}-" }).accept(:json)
464
- tracker = @diagnostics.track('get_id_list', 'network_request', { url: list.url })
461
+ tracker = @diagnostics.track(context, 'get_id_list', 'network_request', { url: list.url })
465
462
  begin
466
463
  res = http.get(list.url)
467
464
  tracker.end(statusCode: res.status.code, success: res.status.success?)
@@ -469,7 +466,7 @@ module Statsig
469
466
  content_length = Integer(res['content-length'])
470
467
  nil if content_length.nil? || content_length <= 0
471
468
  content = res.body.to_s
472
- success = process_single_id_list(list, content, content_length)
469
+ success = process_single_id_list(list, context, content, content_length)
473
470
  save_single_id_list_to_adapter(list.name, content) unless success.nil? || !success
474
471
  rescue
475
472
  tracker.end(success: false)
@@ -477,10 +474,10 @@ module Statsig
477
474
  end
478
475
  end
479
476
 
480
- def process_single_id_list(list, content, content_length = nil, from_adapter: false)
477
+ def process_single_id_list(list, context, content, content_length = nil, from_adapter: false)
481
478
  false unless list.is_a? IDList
482
479
  begin
483
- tracker = @diagnostics.track(from_adapter ? 'data_store_id_list' : 'get_id_list', 'process', { url: list.url })
480
+ tracker = @diagnostics.track(context, from_adapter ? 'data_store_id_list' : 'get_id_list', 'process', { url: list.url })
484
481
  unless content.is_a?(String) && (content[0] == '-' || content[0] == '+')
485
482
  @specs[:id_lists].delete(list.name)
486
483
  tracker.end(success: false)
@@ -514,4 +511,4 @@ module Statsig
514
511
  end
515
512
 
516
513
  end
517
- end
514
+ end
data/lib/statsig.rb CHANGED
@@ -327,7 +327,7 @@ module Statsig
327
327
  def self.get_statsig_metadata
328
328
  {
329
329
  'sdkType' => 'ruby-server',
330
- 'sdkVersion' => '1.30.0',
330
+ 'sdkVersion' => '1.31.0',
331
331
  'languageVersion' => RUBY_VERSION
332
332
  }
333
333
  end
@@ -31,8 +31,8 @@ class StatsigDriver
31
31
 
32
32
  @err_boundary = Statsig::ErrorBoundary.new(secret_key)
33
33
  @err_boundary.capture(task: lambda {
34
- @diagnostics = Statsig::Diagnostics.new('initialize')
35
- tracker = @diagnostics.track('overall')
34
+ @diagnostics = Statsig::Diagnostics.new()
35
+ tracker = @diagnostics.track('initialize', 'overall')
36
36
  @options = options || StatsigOptions.new
37
37
  @shutdown = false
38
38
  @secret_key = secret_key
@@ -43,7 +43,7 @@ class StatsigDriver
43
43
  @evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils)
44
44
  tracker.end(success: true)
45
45
 
46
- @logger.log_diagnostics_event(@diagnostics)
46
+ @logger.log_diagnostics_event(@diagnostics, 'initialize')
47
47
  }, caller: __method__.to_s)
48
48
  end
49
49
 
@@ -68,15 +68,10 @@ class StatsigDriver
68
68
  res = Statsig::ConfigResult.new(gate_name)
69
69
  end
70
70
 
71
- if res == $fetch_from_server
72
- res = check_gate_fallback(user, gate_name)
73
- # exposure logged by the server
74
- else
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
71
+ unless disable_log_exposure
72
+ @logger.log_gate_exposure(
73
+ user, res.name, res.gate_value, res.rule_id, res.secondary_exposures, res.evaluation_details
74
+ )
80
75
  end
81
76
  FeatureGate.from_config_result(res)
82
77
  end
@@ -158,14 +153,6 @@ class StatsigDriver
158
153
  res = Statsig::ConfigResult.new(layer_name)
159
154
  end
160
155
 
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
156
  exposure_log_func = !options.disable_log_exposure ? lambda { |layer, parameter_name|
170
157
  @logger.log_layer_exposure(user, layer, parameter_name, res)
171
158
  } : nil
@@ -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('api_call')
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,7 +292,7 @@ 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
@@ -336,13 +323,8 @@ class StatsigDriver
336
323
  res = Statsig::ConfigResult.new(config_name)
337
324
  end
338
325
 
339
- if res == $fetch_from_server
340
- res = get_config_fallback(user, config_name)
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
326
+ unless disable_log_exposure
327
+ @logger.log_config_exposure(user, res.name, res.rule_id, res.secondary_exposures, res.evaluation_details)
346
328
  end
347
329
 
348
330
  DynamicConfig.new(res.name, res.json_value, res.rule_id, res.group_name, res.id_type, res.evaluation_details)
@@ -372,34 +354,4 @@ class StatsigDriver
372
354
  puts 'SDK has been shutdown. Updates in the Statsig Console will no longer reflect.'
373
355
  end
374
356
  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
357
  end
@@ -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
@@ -204,4 +207,4 @@ module Statsig
204
207
  true
205
208
  end
206
209
  end
207
- end
210
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsig
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.30.0
4
+ version: 1.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-05 00:00:00.000000000 Z
11
+ date: 2024-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler