statsig 1.30.0 → 1.31.0

Sign up to get free protection for your applications and to get access to all the features.
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