statsig 1.25.1 → 1.25.2

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: f1772b0fb47eaaf4b95950425eeba70ea7b792a8e5aad952b67e7aea8000d06b
4
- data.tar.gz: 2e75c8c1c9e68817440dd73d98020f99f20e80255424fb499dff8d3464eb39ba
3
+ metadata.gz: c7f3c8522d7d8062daf248fd667fd0d8152a4194339fde799814a5da44812eda
4
+ data.tar.gz: 1fd5e67d4a75ab3b5b6c92c780841252a4610e18bc13310f9b11dbb583aaf16a
5
5
  SHA512:
6
- metadata.gz: 5f291605da74348e702b8258ca52c6bb609d6359c961a862d76c50e7a53b348bb7705ed6ac2e3da2b68ea89c2db852feb50f5213d5c9543af665142e8cc2bc15
7
- data.tar.gz: 3f1923846883e667b5f5ae14c9efd90fd295a3c0336be262ec879227b3af85b513f9dc407687e8f6273e911644b40bbde7324ff86fc3bc7b26e4a3fd9345992b
6
+ metadata.gz: 8f516ca6d221c9d789f1f358b550edeedf4203c01ead45374b97a3b81f4205d09f1c7c3e0214516cdcad314bacbdbde39a5df2053779679fe4df593c974c8300
7
+ data.tar.gz: a90846ef3c050c7c0b8bddbabd19ed7bff6236a15ed941f55d1a305f4c9cb5ff35f595a3d7ea4ab1560d7f7bae11c669c29d17f8041da9125524669703dc5370
data/lib/diagnostics.rb CHANGED
@@ -12,33 +12,92 @@ module Statsig
12
12
  sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
13
13
  attr_reader :markers
14
14
 
15
- sig { params(context: String).void }
16
-
17
15
  def initialize(context)
18
16
  @context = context
19
17
  @markers = []
20
18
  end
21
19
 
22
- sig { params(key: String, action: String, step: T.any(String, NilClass), value: T.any(String, Integer, T::Boolean, NilClass)).void }
20
+ sig do
21
+ params(
22
+ key: String,
23
+ action: String,
24
+ step: T.any(String, NilClass),
25
+ value: T.any(String, Integer, T::Boolean, NilClass),
26
+ metadata: T.any(T::Hash[Symbol, T.untyped], NilClass)
27
+ ).void
28
+ end
23
29
 
24
- def mark(key, action, step = nil, value = nil)
30
+ def mark(key, action, step = nil, value = nil, metadata = nil)
25
31
  @markers.push({
26
32
  key: key,
27
33
  step: step,
28
34
  action: action,
29
35
  value: value,
36
+ metadata: metadata,
30
37
  timestamp: (Time.now.to_f * 1000).to_i
31
38
  })
32
39
  end
33
40
 
41
+ sig do
42
+ params(
43
+ key: String,
44
+ step: T.any(String, NilClass),
45
+ value: T.any(String, Integer, T::Boolean, NilClass),
46
+ metadata: T.any(T::Hash[Symbol, T.untyped], NilClass)
47
+ ).returns(Tracker)
48
+ end
49
+ def track(key, step = nil, value = nil, metadata = nil)
50
+ tracker = Tracker.new(self, key, step, metadata)
51
+ tracker.start(value)
52
+ tracker
53
+ end
54
+
34
55
  sig { returns(T::Hash[Symbol, T.untyped]) }
35
56
 
36
57
  def serialize
37
58
  {
38
- context: @context,
39
- markers: @markers
59
+ context: @context.clone,
60
+ markers: @markers.clone
40
61
  }
41
62
  end
42
- end
43
63
 
44
- end
64
+ def clear_markers
65
+ @markers.clear
66
+ end
67
+
68
+ class Context
69
+ INITIALIZE = 'initialize'.freeze
70
+ CONFIG_SYNC = 'config_sync'.freeze
71
+ API_CALL = 'api_call'.freeze
72
+ end
73
+
74
+ API_CALL_KEYS = %w[check_gate get_config get_experiment get_layer].freeze
75
+
76
+ class Tracker
77
+ extend T::Sig
78
+
79
+ sig do
80
+ params(
81
+ diagnostics: Diagnostics,
82
+ key: String,
83
+ step: T.any(String, NilClass),
84
+ metadata: T.any(T::Hash[Symbol, T.untyped], NilClass)
85
+ ).void
86
+ end
87
+ def initialize(diagnostics, key, step, metadata)
88
+ @diagnostics = diagnostics
89
+ @key = key
90
+ @step = step
91
+ @metadata = metadata
92
+ end
93
+
94
+ def start(value = nil)
95
+ @diagnostics.mark(@key, 'start', @step, value, @metadata)
96
+ end
97
+
98
+ def end(value = nil)
99
+ @diagnostics.mark(@key, 'end', @step, value, @metadata)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,25 +1,46 @@
1
- require "statsig_errors"
1
+ # typed: true
2
+
3
+ require 'statsig_errors'
4
+ require 'sorbet-runtime'
2
5
 
3
6
  $endpoint = 'https://statsigapi.net/v1/sdk_exception'
4
7
 
5
8
  module Statsig
6
9
  class ErrorBoundary
10
+ extend T::Sig
11
+
12
+ sig { returns(T.any(StatsigLogger, NilClass)) }
13
+ attr_accessor :logger
14
+
15
+ sig { params(sdk_key: String).void }
7
16
  def initialize(sdk_key)
8
17
  @sdk_key = sdk_key
9
18
  @seen = Set.new
10
19
  end
11
20
 
12
- def capture(task, recover = -> {})
21
+ def sample_diagnostics
22
+ rand(10_000).zero?
23
+ end
24
+
25
+ def capture(task:, recover: -> {}, caller: nil)
26
+ if !caller.nil? && Diagnostics::API_CALL_KEYS.include?(caller) && sample_diagnostics
27
+ diagnostics = Diagnostics.new('api_call')
28
+ tracker = diagnostics.track(caller)
29
+ end
13
30
  begin
14
- return task.call
31
+ res = task.call
32
+ tracker&.end(true)
15
33
  rescue StandardError => e
34
+ tracker&.end(false)
16
35
  if e.is_a?(Statsig::UninitializedError) or e.is_a?(Statsig::ValueError)
17
36
  raise e
18
37
  end
19
- puts "[Statsig]: An unexpected exception occurred."
38
+ puts '[Statsig]: An unexpected exception occurred.'
20
39
  log_exception(e)
21
- return recover.call
40
+ res = recover.call
22
41
  end
42
+ @logger&.log_diagnostics_event(diagnostics)
43
+ return res
23
44
  end
24
45
 
25
46
  private
@@ -35,18 +56,18 @@ module Statsig
35
56
  meta = Statsig.get_statsig_metadata
36
57
  http = HTTP.headers(
37
58
  {
38
- "STATSIG-API-KEY" => @sdk_key,
39
- "STATSIG-SDK-TYPE" => meta['sdkType'],
40
- "STATSIG-SDK-VERSION" => meta['sdkVersion'],
41
- "Content-Type" => "application/json; charset=UTF-8"
59
+ 'STATSIG-API-KEY' => @sdk_key,
60
+ 'STATSIG-SDK-TYPE' => meta['sdkType'],
61
+ 'STATSIG-SDK-VERSION' => meta['sdkVersion'],
62
+ 'Content-Type' => 'application/json; charset=UTF-8'
42
63
  }).accept(:json)
43
64
  body = {
44
- "exception" => name,
45
- "info" => {
46
- "trace" => exception.backtrace.to_s,
47
- "message" => exception.message
65
+ 'exception' => name,
66
+ 'info' => {
67
+ 'trace' => exception.backtrace.to_s,
68
+ 'message' => exception.message
48
69
  }.to_s,
49
- "statsigMetadata" => meta
70
+ 'statsigMetadata' => meta
50
71
  }
51
72
  http.post($endpoint, body: JSON.generate(body))
52
73
  rescue
data/lib/evaluator.rb CHANGED
@@ -17,8 +17,8 @@ module Statsig
17
17
  class Evaluator
18
18
  attr_accessor :spec_store
19
19
 
20
- def initialize(network, options, error_callback, init_diagnostics = nil)
21
- @spec_store = Statsig::SpecStore.new(network, options, error_callback, init_diagnostics)
20
+ def initialize(network, options, error_callback, diagnostics)
21
+ @spec_store = Statsig::SpecStore.new(network, options, error_callback, diagnostics)
22
22
  UAParser.initialize_async
23
23
  CountryLookup.initialize_async
24
24
 
data/lib/network.rb CHANGED
@@ -4,6 +4,7 @@ require 'http'
4
4
  require 'json'
5
5
  require 'securerandom'
6
6
  require 'sorbet-runtime'
7
+ require 'uri_helper'
7
8
 
8
9
  $retry_codes = [408, 500, 502, 503, 504, 522, 524, 599]
9
10
 
@@ -24,12 +25,8 @@ module Statsig
24
25
 
25
26
  def initialize(server_secret, options, backoff_mult = 10)
26
27
  super()
27
- api = options.api_url_base
28
- unless api.end_with?('/')
29
- api += '/'
30
- end
28
+ URIHelper.initialize(options)
31
29
  @server_secret = server_secret
32
- @api = api
33
30
  @local_mode = options.local_mode
34
31
  @timeout = options.network_timeout
35
32
  @backoff_multiplier = backoff_mult
@@ -67,8 +64,9 @@ module Statsig
67
64
  backoff_adjusted = @post_logs_retry_backoff.call(retries)
68
65
  end
69
66
  end
67
+ url = URIHelper.build_url(endpoint)
70
68
  begin
71
- res = http.post(@api + endpoint, body: body)
69
+ res = http.post(url, body: body)
72
70
  rescue StandardError => e
73
71
  ## network error retry
74
72
  return nil, e unless retries > 0
@@ -76,7 +74,7 @@ module Statsig
76
74
  return post_helper(endpoint, body, retries - 1, backoff * @backoff_multiplier)
77
75
  end
78
76
  return res, nil if res.status.success?
79
- return nil, NetworkError.new("Got an exception when making request to #{@api + endpoint}: #{res.to_s}", res.status.to_i) unless retries > 0 && $retry_codes.include?(res.code)
77
+ return nil, NetworkError.new("Got an exception when making request to #{url}: #{res.to_s}", res.status.to_i) unless retries > 0 && $retry_codes.include?(res.code)
80
78
  ## status code retry
81
79
  sleep backoff_adjusted
82
80
  post_helper(endpoint, body, retries - 1, backoff * @backoff_multiplier)
data/lib/spec_store.rb CHANGED
@@ -12,7 +12,7 @@ module Statsig
12
12
  attr_accessor :initial_config_sync_time
13
13
  attr_accessor :init_reason
14
14
 
15
- def initialize(network, options, error_callback, init_diagnostics = nil)
15
+ def initialize(network, options, error_callback, diagnostics)
16
16
  @init_reason = EvaluationReason::UNINITIALIZED
17
17
  @network = network
18
18
  @options = options
@@ -29,6 +29,7 @@ module Statsig
29
29
  :id_lists => {},
30
30
  :experiment_to_layer => {}
31
31
  }
32
+ @diagnostics = diagnostics
32
33
 
33
34
  @id_list_thread_pool = Concurrent::FixedThreadPool.new(
34
35
  options.idlist_threadpool_size,
@@ -42,11 +43,11 @@ module Statsig
42
43
  if !@options.data_store.nil?
43
44
  puts 'data_store gets priority over bootstrap_values. bootstrap_values will be ignored'
44
45
  else
45
- init_diagnostics&.mark("bootstrap", "start", "load")
46
+ tracker = @diagnostics.track('bootstrap', 'process')
46
47
  if process_specs(options.bootstrap_values)
47
48
  @init_reason = EvaluationReason::BOOTSTRAP
48
49
  end
49
- init_diagnostics&.mark("bootstrap", "end", "load", @init_reason == EvaluationReason::BOOTSTRAP)
50
+ tracker.end(@init_reason == EvaluationReason::BOOTSTRAP)
50
51
  end
51
52
  rescue
52
53
  puts 'the provided bootstrapValues is not a valid JSON string'
@@ -54,21 +55,19 @@ module Statsig
54
55
  end
55
56
 
56
57
  unless @options.data_store.nil?
57
- init_diagnostics&.mark("data_store", "start", "load")
58
58
  @options.data_store.init
59
- load_config_specs_from_storage_adapter(init_diagnostics: init_diagnostics)
60
- init_diagnostics&.mark("data_store", "end", "load", @init_reason == EvaluationReason::DATA_ADAPTER)
59
+ load_config_specs_from_storage_adapter
61
60
  end
62
61
 
63
62
  if @init_reason == EvaluationReason::UNINITIALIZED
64
- download_config_specs(init_diagnostics)
63
+ download_config_specs
65
64
  end
66
65
 
67
66
  @initial_config_sync_time = @last_config_sync_time == 0 ? -1 : @last_config_sync_time
68
67
  if !@options.data_store.nil?
69
- get_id_lists_from_adapter(init_diagnostics)
68
+ get_id_lists_from_adapter
70
69
  else
71
- get_id_lists_from_network(init_diagnostics)
70
+ get_id_lists_from_network
72
71
  end
73
72
 
74
73
  @config_sync_thread = sync_config_specs
@@ -135,20 +134,20 @@ module Statsig
135
134
 
136
135
  private
137
136
 
138
- def load_config_specs_from_storage_adapter(init_diagnostics: nil)
139
- init_diagnostics&.mark("download_config_specs", "start", "fetch_from_adapter")
137
+ def load_config_specs_from_storage_adapter
138
+ tracker = @diagnostics.track('data_store_config_specs', 'fetch')
140
139
  cached_values = @options.data_store.get(Interfaces::IDataStore::CONFIG_SPECS_KEY)
141
- init_diagnostics&.mark("download_config_specs", "end", "fetch_from_adapter", true)
140
+ tracker.end(true)
142
141
  return if cached_values.nil?
143
142
 
144
- init_diagnostics&.mark("download_config_specs", "start", "process")
143
+ tracker = @diagnostics.track('data_store_config_specs', 'process')
145
144
  process_specs(cached_values, from_adapter: true)
146
145
  @init_reason = EvaluationReason::DATA_ADAPTER
147
- init_diagnostics&.mark("download_config_specs", "end", "process", @init_reason)
146
+ tracker.end(true)
148
147
  rescue StandardError
149
148
  # Fallback to network
150
- init_diagnostics&.mark("download_config_specs", "end", "fetch_from_adapter", false)
151
- download_config_specs(init_diagnostics)
149
+ tracker.end(false)
150
+ download_config_specs
152
151
  end
153
152
 
154
153
  def save_config_specs_to_storage_adapter(specs_string)
@@ -160,6 +159,7 @@ module Statsig
160
159
 
161
160
  def sync_config_specs
162
161
  Thread.new do
162
+ @diagnostics = Diagnostics.new('config_sync')
163
163
  loop do
164
164
  sleep @options.rulesets_sync_interval
165
165
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_KEY)
@@ -173,6 +173,7 @@ module Statsig
173
173
 
174
174
  def sync_id_lists
175
175
  Thread.new do
176
+ @diagnostics = Diagnostics.new('config_sync')
176
177
  loop do
177
178
  sleep @id_lists_sync_interval
178
179
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::ID_LISTS_KEY)
@@ -184,8 +185,8 @@ module Statsig
184
185
  end
185
186
  end
186
187
 
187
- def download_config_specs(init_diagnostics = nil)
188
- init_diagnostics&.mark("download_config_specs", "start", "network_request")
188
+ def download_config_specs
189
+ tracker = @diagnostics.track('download_config_specs', 'network_request')
189
190
 
190
191
  error = nil
191
192
  begin
@@ -194,18 +195,17 @@ module Statsig
194
195
  if e.is_a? NetworkError
195
196
  code = e.http_code
196
197
  end
197
- init_diagnostics&.mark("download_config_specs", "end", "network_request", code)
198
+ tracker.end(code)
198
199
 
199
200
  if e.nil?
200
201
  unless response.nil?
201
- init_diagnostics&.mark("download_config_specs", "start", "process")
202
-
202
+ tracker = @diagnostics.track('download_config_specs', 'process')
203
203
  if process_specs(response.body.to_s)
204
204
  @init_reason = EvaluationReason::NETWORK
205
- @rules_updated_callback.call(response.body.to_s, @last_config_sync_time) unless response.body.nil? or @rules_updated_callback.nil?
206
205
  end
206
+ tracker.end(@init_reason == EvaluationReason::NETWORK)
207
207
 
208
- init_diagnostics&.mark("download_config_specs", "end", "process", @init_reason == EvaluationReason::NETWORK)
208
+ @rules_updated_callback.call(response.body.to_s, @last_config_sync_time) unless response.body.nil? or @rules_updated_callback.nil?
209
209
  end
210
210
 
211
211
  nil
@@ -259,18 +259,18 @@ module Statsig
259
259
  true
260
260
  end
261
261
 
262
- def get_id_lists_from_adapter(init_diagnostics = nil)
263
- init_diagnostics&.mark("get_id_lists", "start", "fetch_from_adapter")
262
+ def get_id_lists_from_adapter
263
+ tracker = @diagnostics.track('data_store_id_lists', 'fetch')
264
264
  cached_values = @options.data_store.get(Interfaces::IDataStore::ID_LISTS_KEY)
265
265
  return if cached_values.nil?
266
266
 
267
- init_diagnostics&.mark("get_id_lists", "end", "fetch_from_adapter", true)
267
+ tracker.end(true)
268
268
  id_lists = JSON.parse(cached_values)
269
- process_id_lists(id_lists, init_diagnostics, from_adapter: true)
269
+ process_id_lists(id_lists, from_adapter: true)
270
270
  rescue StandardError
271
271
  # Fallback to network
272
- init_diagnostics&.mark("get_id_lists", "end", "fetch_from_adapter", false)
273
- get_id_lists_from_network(init_diagnostics)
272
+ tracker.end(false)
273
+ get_id_lists_from_network
274
274
  end
275
275
 
276
276
  def save_id_lists_to_adapter(id_lists_raw_json)
@@ -280,36 +280,45 @@ module Statsig
280
280
  @options.data_store.set(Interfaces::IDataStore::ID_LISTS_KEY, id_lists_raw_json)
281
281
  end
282
282
 
283
- def get_id_lists_from_network(init_diagnostics = nil)
284
- init_diagnostics&.mark("get_id_lists", "start", "network_request")
283
+ def get_id_lists_from_network
284
+ tracker = @diagnostics.track('get_id_list_sources', 'network_request')
285
285
  response, e = @network.post_helper('get_id_lists', JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }))
286
+ code = response&.status.to_i
287
+ if e.is_a? NetworkError
288
+ code = e.http_code
289
+ end
290
+ tracker.end(code)
286
291
  if !e.nil? || response.nil?
287
292
  return
288
293
  end
289
- init_diagnostics&.mark("get_id_lists", "end", "network_request", response.status.to_i)
290
294
 
291
295
  begin
292
296
  server_id_lists = JSON.parse(response)
293
- process_id_lists(server_id_lists, init_diagnostics)
297
+ process_id_lists(server_id_lists)
294
298
  save_id_lists_to_adapter(response.body.to_s)
295
299
  rescue
296
300
  # Ignored, will try again
297
301
  end
298
302
  end
299
303
 
300
- def process_id_lists(new_id_lists, init_diagnostics, from_adapter: false)
304
+ def process_id_lists(new_id_lists, from_adapter: false)
301
305
  local_id_lists = @specs[:id_lists]
302
306
  if !new_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
303
307
  return
304
308
  end
305
309
  tasks = []
306
310
 
307
- if new_id_lists.length == 0
311
+ tracker = @diagnostics.track(
312
+ from_adapter ? 'data_store_id_lists' : 'get_id_list_sources',
313
+ 'process',
314
+ new_id_lists.length
315
+ )
316
+
317
+ if new_id_lists.empty?
318
+ tracker.end
308
319
  return
309
320
  end
310
321
 
311
- init_diagnostics&.mark("get_id_lists", "start", "process", new_id_lists.length)
312
-
313
322
  delete_lists = []
314
323
  local_id_lists.each do |list_name, list|
315
324
  unless new_id_lists.key? list_name
@@ -357,14 +366,17 @@ module Statsig
357
366
  end
358
367
 
359
368
  result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
360
- init_diagnostics&.mark("get_id_lists", "end", "process", result.state == :fulfilled)
369
+ tracker.end(result.state == :fulfilled)
361
370
  end
362
371
 
363
372
  def get_single_id_list_from_adapter(list)
373
+ tracker = @diagnostics.track('data_store_id_list', 'fetch', nil, { url: list.url })
364
374
  cached_values = @options.data_store.get("#{Interfaces::IDataStore::ID_LISTS_KEY}::#{list.name}")
375
+ tracker.end(true)
365
376
  content = cached_values.to_s
366
- process_single_id_list(list, content)
377
+ process_single_id_list(list, content, from_adapter: true)
367
378
  rescue StandardError
379
+ tracker.end(false)
368
380
  nil
369
381
  end
370
382
 
@@ -378,7 +390,9 @@ module Statsig
378
390
  nil unless list.is_a? IDList
379
391
  http = HTTP.headers({ 'Range' => "bytes=#{list&.size || 0}-" }).accept(:json)
380
392
  begin
393
+ tracker = @diagnostics.track('get_id_list', 'network_request', nil, { url: list.url })
381
394
  res = http.get(list.url)
395
+ tracker.end(res.status.code)
382
396
  nil unless res.status.success?
383
397
  content_length = Integer(res['content-length'])
384
398
  nil if content_length.nil? || content_length <= 0
@@ -390,11 +404,13 @@ module Statsig
390
404
  end
391
405
  end
392
406
 
393
- def process_single_id_list(list, content, content_length = nil)
407
+ def process_single_id_list(list, content, content_length = nil, from_adapter: false)
394
408
  false unless list.is_a? IDList
395
409
  begin
410
+ tracker = @diagnostics.track(from_adapter ? 'data_store_id_list' : 'get_id_list', 'process', nil, { url: list.url })
396
411
  unless content.is_a?(String) && (content[0] == '-' || content[0] == '+')
397
412
  @specs[:id_lists].delete(list.name)
413
+ tracker.end(false)
398
414
  return false
399
415
  end
400
416
  ids_clone = list.ids # clone the list, operate on the new list, and swap out the old list, so the operation is thread-safe
@@ -416,8 +432,10 @@ module Statsig
416
432
  else
417
433
  list.size + content_length
418
434
  end
435
+ tracker.end(true)
419
436
  return true
420
437
  rescue
438
+ tracker.end(false)
421
439
  return false
422
440
  end
423
441
  end
data/lib/statsig.rb CHANGED
@@ -227,7 +227,7 @@ module Statsig
227
227
  def self.get_statsig_metadata
228
228
  {
229
229
  'sdkType' => 'ruby-server',
230
- 'sdkVersion' => '1.25.1',
230
+ 'sdkVersion' => '1.25.2',
231
231
  }
232
232
  end
233
233
 
@@ -30,19 +30,20 @@ class StatsigDriver
30
30
  end
31
31
 
32
32
  @err_boundary = Statsig::ErrorBoundary.new(secret_key)
33
- @err_boundary.capture(-> {
34
- @init_diagnostics = Statsig::Diagnostics.new("initialize")
35
- @init_diagnostics.mark("overall", "start")
33
+ @err_boundary.capture(task: lambda {
34
+ @diagnostics = Statsig::Diagnostics.new('initialize')
35
+ tracker = @diagnostics.track('overall')
36
36
  @options = options || StatsigOptions.new
37
37
  @shutdown = false
38
38
  @secret_key = secret_key
39
39
  @net = Statsig::Network.new(secret_key, @options)
40
40
  @logger = Statsig::StatsigLogger.new(@net, @options)
41
- @evaluator = Statsig::Evaluator.new(@net, @options, error_callback, @init_diagnostics)
42
- @init_diagnostics.mark("overall", "end")
41
+ @evaluator = Statsig::Evaluator.new(@net, @options, error_callback, @diagnostics)
42
+ tracker.end('success')
43
43
 
44
- log_init_diagnostics
44
+ @logger.log_diagnostics_event(@diagnostics)
45
45
  })
46
+ @err_boundary.logger = @logger
46
47
  end
47
48
 
48
49
  class CheckGateOptions < T::Struct
@@ -52,7 +53,7 @@ class StatsigDriver
52
53
  sig { params(user: StatsigUser, gate_name: String, options: CheckGateOptions).returns(T::Boolean) }
53
54
 
54
55
  def check_gate(user, gate_name, options = CheckGateOptions.new)
55
- @err_boundary.capture(-> {
56
+ @err_boundary.capture(task: lambda {
56
57
  user = verify_inputs(user, gate_name, "gate_name")
57
58
 
58
59
  res = @evaluator.check_gate(user, gate_name)
@@ -70,8 +71,7 @@ class StatsigDriver
70
71
  end
71
72
 
72
73
  res.gate_value
73
- }, -> { false })
74
-
74
+ }, recover: -> { false }, caller: __method__.to_s)
75
75
  end
76
76
 
77
77
  sig { params(user: StatsigUser, gate_name: String).void }
@@ -89,10 +89,10 @@ class StatsigDriver
89
89
  sig { params(user: StatsigUser, dynamic_config_name: String, options: GetConfigOptions).returns(DynamicConfig) }
90
90
 
91
91
  def get_config(user, dynamic_config_name, options = GetConfigOptions.new)
92
- @err_boundary.capture(-> {
92
+ @err_boundary.capture(task: lambda {
93
93
  user = verify_inputs(user, dynamic_config_name, "dynamic_config_name")
94
94
  get_config_impl(user, dynamic_config_name, options)
95
- }, -> { DynamicConfig.new(dynamic_config_name) })
95
+ }, recover: -> { DynamicConfig.new(dynamic_config_name) }, caller: __method__.to_s)
96
96
  end
97
97
 
98
98
  class GetExperimentOptions < T::Struct
@@ -102,10 +102,10 @@ class StatsigDriver
102
102
  sig { params(user: StatsigUser, experiment_name: String, options: GetExperimentOptions).returns(DynamicConfig) }
103
103
 
104
104
  def get_experiment(user, experiment_name, options = GetExperimentOptions.new)
105
- @err_boundary.capture(-> {
105
+ @err_boundary.capture(task: lambda {
106
106
  user = verify_inputs(user, experiment_name, "experiment_name")
107
107
  get_config_impl(user, experiment_name, options)
108
- }, -> { DynamicConfig.new(experiment_name) })
108
+ }, recover: -> { DynamicConfig.new(experiment_name) }, caller: __method__.to_s)
109
109
  end
110
110
 
111
111
  sig { params(user: StatsigUser, config_name: String).void }
@@ -123,7 +123,7 @@ class StatsigDriver
123
123
  sig { params(user: StatsigUser, layer_name: String, options: GetLayerOptions).returns(Layer) }
124
124
 
125
125
  def get_layer(user, layer_name, options = GetLayerOptions.new)
126
- @err_boundary.capture(-> {
126
+ @err_boundary.capture(task: lambda {
127
127
  user = verify_inputs(user, layer_name, "layer_name")
128
128
 
129
129
  res = @evaluator.get_layer(user, layer_name)
@@ -143,9 +143,9 @@ class StatsigDriver
143
143
  @logger.log_layer_exposure(user, layer, parameter_name, res)
144
144
  } : nil
145
145
  Layer.new(res.name, res.json_value, res.rule_id, exposure_log_func)
146
- }, -> {
146
+ }, recover: lambda {
147
147
  Layer.new(layer_name)
148
- })
148
+ }, caller: __method__.to_s)
149
149
  end
150
150
 
151
151
  sig { params(user: StatsigUser, layer_name: String, parameter_name: String).void }
@@ -158,7 +158,7 @@ class StatsigDriver
158
158
  end
159
159
 
160
160
  def log_event(user, event_name, value = nil, metadata = nil)
161
- @err_boundary.capture(-> {
161
+ @err_boundary.capture(task: lambda {
162
162
  if !user.nil? && !user.instance_of?(StatsigUser)
163
163
  raise Statsig::ValueError.new('Must provide a valid StatsigUser or nil')
164
164
  end
@@ -175,7 +175,7 @@ class StatsigDriver
175
175
  end
176
176
 
177
177
  def shutdown
178
- @err_boundary.capture(-> {
178
+ @err_boundary.capture(task: lambda {
179
179
  @shutdown = true
180
180
  @logger.shutdown
181
181
  @evaluator.shutdown
@@ -183,13 +183,13 @@ class StatsigDriver
183
183
  end
184
184
 
185
185
  def override_gate(gate_name, gate_value)
186
- @err_boundary.capture(-> {
186
+ @err_boundary.capture(task: lambda {
187
187
  @evaluator.override_gate(gate_name, gate_value)
188
188
  })
189
189
  end
190
190
 
191
191
  def override_config(config_name, config_value)
192
- @err_boundary.capture(-> {
192
+ @err_boundary.capture(task: lambda {
193
193
  @evaluator.override_config(config_name, config_value)
194
194
  })
195
195
  end
@@ -197,11 +197,11 @@ class StatsigDriver
197
197
  # @param [StatsigUser] user
198
198
  # @return [Hash]
199
199
  def get_client_initialize_response(user)
200
- @err_boundary.capture(-> {
200
+ @err_boundary.capture(task: lambda {
201
201
  validate_user(user)
202
202
  normalize_user(user)
203
203
  @evaluator.get_client_initialize_response(user)
204
- }, -> { nil })
204
+ }, recover: -> { nil })
205
205
  end
206
206
 
207
207
  def maybe_restart_background_threads
@@ -209,7 +209,7 @@ class StatsigDriver
209
209
  return
210
210
  end
211
211
 
212
- @err_boundary.capture(-> {
212
+ @err_boundary.capture(task: lambda {
213
213
  @evaluator.maybe_restart_background_threads
214
214
  @logger.maybe_restart_background_threads
215
215
  })
@@ -302,12 +302,4 @@ class StatsigDriver
302
302
  network_result['rule_id'],
303
303
  )
304
304
  end
305
-
306
- def log_init_diagnostics
307
- if @options.disable_diagnostics_logging
308
- return
309
- end
310
-
311
- @logger.log_diagnostics_event(@init_diagnostics)
312
- end
313
305
  end
@@ -97,10 +97,14 @@ module Statsig
97
97
  end
98
98
 
99
99
  def log_diagnostics_event(diagnostics, user = nil)
100
+ return if @options.disable_diagnostics_logging
101
+ return if diagnostics.nil? || diagnostics.markers.empty?
102
+
100
103
  event = StatsigEvent.new($diagnostics_event)
101
104
  event.user = user
102
105
  event.metadata = diagnostics.serialize
103
106
  log_event(event)
107
+ diagnostics.clear_markers
104
108
  end
105
109
 
106
110
  def periodic_flush
@@ -19,6 +19,11 @@ class StatsigOptions
19
19
  # default: https://statsigapi.net/v1
20
20
  attr_accessor :api_url_base
21
21
 
22
+ # The base url used specifically to call download_config_specs.
23
+ # Takes precedence over api_url_base
24
+ sig { returns(T.any(String, NilClass)) }
25
+ attr_accessor :api_url_download_config_specs
26
+
22
27
  sig { returns(T.any(Float, Integer)) }
23
28
  # The interval (in seconds) to poll for changes to your Statsig configuration
24
29
  # default: 10s
@@ -97,6 +102,7 @@ class StatsigOptions
97
102
  params(
98
103
  environment: T.any(T::Hash[String, String], NilClass),
99
104
  api_url_base: String,
105
+ api_url_download_config_specs: T.any(String, NilClass),
100
106
  rulesets_sync_interval: T.any(Float, Integer),
101
107
  idlists_sync_interval: T.any(Float, Integer),
102
108
  logging_interval_seconds: T.any(Float, Integer),
@@ -118,6 +124,7 @@ class StatsigOptions
118
124
  def initialize(
119
125
  environment = nil,
120
126
  api_url_base = 'https://statsigapi.net/v1',
127
+ api_url_download_config_specs: nil,
121
128
  rulesets_sync_interval: 10,
122
129
  idlists_sync_interval: 60,
123
130
  logging_interval_seconds: 60,
@@ -135,6 +142,7 @@ class StatsigOptions
135
142
  post_logs_retry_backoff: nil)
136
143
  @environment = environment.is_a?(Hash) ? environment : nil
137
144
  @api_url_base = api_url_base
145
+ @api_url_download_config_specs = api_url_download_config_specs
138
146
  @rulesets_sync_interval = rulesets_sync_interval
139
147
  @idlists_sync_interval = idlists_sync_interval
140
148
  @logging_interval_seconds = logging_interval_seconds
data/lib/uri_helper.rb ADDED
@@ -0,0 +1,37 @@
1
+ # typed: true
2
+
3
+ require 'sorbet-runtime'
4
+
5
+ class URIHelper
6
+ class URIBuilder
7
+ extend T::Sig
8
+
9
+ sig { returns(StatsigOptions) }
10
+ attr_accessor :options
11
+
12
+ sig { params(options: StatsigOptions).void }
13
+ def initialize(options)
14
+ @options = options
15
+ end
16
+
17
+ sig { params(endpoint: String).returns(String) }
18
+ def build_url(endpoint)
19
+ api = @options.api_url_base
20
+ if endpoint == 'download_config_specs' && !@options.api_url_download_config_specs.nil?
21
+ api = T.must(@options.api_url_download_config_specs)
22
+ end
23
+ unless api.end_with?('/')
24
+ api += '/'
25
+ end
26
+ "#{api}#{endpoint}"
27
+ end
28
+ end
29
+
30
+ def self.initialize(options)
31
+ @uri_builder = URIBuilder.new(options)
32
+ end
33
+
34
+ def self.build_url(endpoint)
35
+ @uri_builder.build_url(endpoint)
36
+ end
37
+ 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.25.1
4
+ version: 1.25.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-31 00:00:00.000000000 Z
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -295,6 +295,7 @@ files:
295
295
  - lib/statsig_options.rb
296
296
  - lib/statsig_user.rb
297
297
  - lib/ua_parser.rb
298
+ - lib/uri_helper.rb
298
299
  homepage: https://rubygems.org/gems/statsig
299
300
  licenses:
300
301
  - ISC