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 +4 -4
- data/lib/client_initialize_helpers.rb +14 -33
- data/lib/diagnostics.rb +25 -37
- data/lib/evaluation_details.rb +1 -0
- data/lib/evaluator.rb +25 -14
- data/lib/network.rb +0 -20
- data/lib/spec_store.rb +38 -41
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +12 -60
- data/lib/statsig_logger.rb +8 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1442642777f1e73e38aa9944a88f9eab27a88df0c856ed0bc708555fe51a7b91
|
|
4
|
+
data.tar.gz: '07159c53071c5650f01016e485aca359568025f96dbeea39e3b99d39d1286662'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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'] =
|
|
79
|
-
result["group_name"] =
|
|
80
|
-
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'] =
|
|
84
|
-
result["group"] =
|
|
85
|
-
result["group_name"] =
|
|
86
|
-
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,
|
|
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,
|
|
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" =>
|
|
106
|
-
"secondary_exposures" => clean_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
|
|
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
|
|
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
|
|
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(
|
|
19
|
-
@
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
data/lib/evaluation_details.rb
CHANGED
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
|
|
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
|
|
282
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
391
|
+
return UNSUPPORTED_EVALUATION
|
|
381
392
|
end
|
|
382
393
|
|
|
383
|
-
return
|
|
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
|
|
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
data/lib/statsig_driver.rb
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
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
|
-
|
|
340
|
-
res
|
|
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
|
data/lib/statsig_logger.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
11
|
+
date: 2024-01-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|