statsig 1.25.2 → 1.33.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,3 @@
1
- # typed: false
2
-
3
- require 'sorbet-runtime'
4
-
5
1
  ##
6
2
  # Contains the current experiment/dynamic config values from Statsig
7
3
  #
@@ -9,33 +5,28 @@ require 'sorbet-runtime'
9
5
  #
10
6
  # Experiments Documentation: https://docs.statsig.com/experiments-plus
11
7
  class DynamicConfig
12
- extend T::Sig
13
8
 
14
- sig { returns(String) }
15
9
  attr_accessor :name
16
10
 
17
- sig { returns(T::Hash[String, T.untyped]) }
18
11
  attr_accessor :value
19
12
 
20
- sig { returns(String) }
21
13
  attr_accessor :rule_id
22
14
 
23
- sig { returns(T.nilable(String)) }
24
15
  attr_accessor :group_name
25
16
 
26
- sig { returns(String) }
27
17
  attr_accessor :id_type
28
18
 
29
- sig { params(name: String, value: T::Hash[String, T.untyped], rule_id: String, group_name: T.nilable(String), id_type: String).void }
30
- def initialize(name, value = {}, rule_id = '', group_name = nil, id_type = '')
19
+ attr_accessor :evaluation_details
20
+
21
+ def initialize(name, value = {}, rule_id = '', group_name = nil, id_type = '', evaluation_details = nil)
31
22
  @name = name
32
- @value = value
23
+ @value = value || {}
33
24
  @rule_id = rule_id
34
25
  @group_name = group_name
35
26
  @id_type = id_type
27
+ @evaluation_details = evaluation_details
36
28
  end
37
29
 
38
- sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
39
30
  ##
40
31
  # Get the value for the given key (index), falling back to the default_value if it cannot be found.
41
32
  #
@@ -46,7 +37,6 @@ class DynamicConfig
46
37
  @value[index]
47
38
  end
48
39
 
49
- sig { params(index: String, default_value: T.untyped).returns(T.untyped) }
50
40
  ##
51
41
  # Get the value for the given key (index), falling back to the default_value if it cannot be found
52
42
  # or is found to have a different type from the default_value.
@@ -1,78 +1,61 @@
1
- # typed: true
2
-
3
1
  require 'statsig_errors'
4
- require 'sorbet-runtime'
5
2
 
6
3
  $endpoint = 'https://statsigapi.net/v1/sdk_exception'
7
4
 
8
5
  module Statsig
9
6
  class ErrorBoundary
10
- extend T::Sig
11
-
12
- sig { returns(T.any(StatsigLogger, NilClass)) }
13
- attr_accessor :logger
14
7
 
15
- sig { params(sdk_key: String).void }
16
8
  def initialize(sdk_key)
17
9
  @sdk_key = sdk_key
18
10
  @seen = Set.new
19
11
  end
20
12
 
21
- def sample_diagnostics
22
- rand(10_000).zero?
23
- end
24
-
25
13
  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
30
14
  begin
31
15
  res = task.call
32
- tracker&.end(true)
33
- rescue StandardError => e
34
- tracker&.end(false)
35
- if e.is_a?(Statsig::UninitializedError) or e.is_a?(Statsig::ValueError)
16
+ rescue StandardError, SystemStackError => e
17
+ if e.is_a?(Statsig::UninitializedError) || e.is_a?(Statsig::ValueError)
36
18
  raise e
37
19
  end
20
+
38
21
  puts '[Statsig]: An unexpected exception occurred.'
39
- log_exception(e)
22
+ puts e.message
23
+ log_exception(e, tag: caller)
40
24
  res = recover.call
41
25
  end
42
- @logger&.log_diagnostics_event(diagnostics)
43
26
  return res
44
27
  end
45
28
 
46
29
  private
47
30
 
48
- def log_exception(exception)
49
- begin
50
- name = exception.class.name
51
- if @seen.include?(name)
52
- return
53
- end
54
-
55
- @seen << name
56
- meta = Statsig.get_statsig_metadata
57
- http = HTTP.headers(
58
- {
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'
63
- }).accept(:json)
64
- body = {
65
- 'exception' => name,
66
- 'info' => {
67
- 'trace' => exception.backtrace.to_s,
68
- 'message' => exception.message
69
- }.to_s,
70
- 'statsigMetadata' => meta
71
- }
72
- http.post($endpoint, body: JSON.generate(body))
73
- rescue
31
+ def log_exception(exception, tag: nil)
32
+ name = exception.class.name
33
+ if @seen.include?(name)
74
34
  return
75
35
  end
36
+
37
+ @seen << name
38
+ meta = Statsig.get_statsig_metadata
39
+ http = HTTP.headers(
40
+ {
41
+ 'STATSIG-API-KEY' => @sdk_key,
42
+ 'STATSIG-SDK-TYPE' => meta['sdkType'],
43
+ 'STATSIG-SDK-VERSION' => meta['sdkVersion'],
44
+ 'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
45
+ 'Content-Type' => 'application/json; charset=UTF-8'
46
+ }).accept(:json)
47
+ body = {
48
+ 'exception' => name,
49
+ 'info' => {
50
+ 'trace' => exception.backtrace.to_s,
51
+ 'message' => exception.message
52
+ }.to_s,
53
+ 'statsigMetadata' => meta,
54
+ 'tag' => tag
55
+ }
56
+ http.post($endpoint, body: JSON.generate(body))
57
+ rescue StandardError
58
+ return
76
59
  end
77
60
  end
78
- end
61
+ end
@@ -1,13 +1,14 @@
1
- # typed: true
2
1
  module Statsig
3
2
 
4
3
  module EvaluationReason
5
- NETWORK = "Network"
6
- LOCAL_OVERRIDE = "LocalOverride"
7
- UNRECOGNIZED = "Unrecognized"
8
- UNINITIALIZED = "Uninitialized"
9
- BOOTSTRAP = "Bootstrap"
10
- DATA_ADAPTER = "DataAdapter"
4
+ NETWORK = 'Network'.freeze
5
+ LOCAL_OVERRIDE = 'LocalOverride'.freeze
6
+ UNRECOGNIZED = 'Unrecognized'.freeze
7
+ UNINITIALIZED = 'Uninitialized'.freeze
8
+ BOOTSTRAP = 'Bootstrap'.freeze
9
+ DATA_ADAPTER = 'DataAdapter'.freeze
10
+ PERSISTED = 'Persisted'.freeze
11
+ UNSUPPORTED = 'Unsupported'.freeze
11
12
  end
12
13
 
13
14
  class EvaluationDetails
@@ -23,6 +24,10 @@ module Statsig
23
24
  @server_time = (Time.now.to_i * 1000).to_s
24
25
  end
25
26
 
27
+ def self.unsupported(config_sync_time, init_time)
28
+ EvaluationDetails.new(config_sync_time, init_time, EvaluationReason::UNSUPPORTED)
29
+ end
30
+
26
31
  def self.unrecognized(config_sync_time, init_time)
27
32
  EvaluationDetails.new(config_sync_time, init_time, EvaluationReason::UNRECOGNIZED)
28
33
  end
@@ -38,5 +43,9 @@ module Statsig
38
43
  def self.local_override(config_sync_time, init_time)
39
44
  EvaluationDetails.new(config_sync_time, init_time, EvaluationReason::LOCAL_OVERRIDE)
40
45
  end
46
+
47
+ def self.persisted(config_sync_time, init_time)
48
+ EvaluationDetails.new(config_sync_time, init_time, EvaluationReason::PERSISTED)
49
+ end
41
50
  end
42
- end
51
+ end
@@ -1,4 +1,3 @@
1
- # typed: true
2
1
  require 'time'
3
2
 
4
3
  module EvaluationHelpers
@@ -9,9 +8,41 @@ module EvaluationHelpers
9
8
 
10
9
  # returns true if array has any element that evaluates to true with value using func lambda, ignoring case
11
10
  def self.match_string_in_array(array, value, ignore_case, func)
12
- return false unless array.is_a?(Array) && !value.nil?
13
11
  str_value = value.to_s
14
- array.any?{ |s| !s.nil? && ((ignore_case && func.call(str_value.downcase, s.to_s.downcase)) || func.call(str_value, s.to_s)) } rescue false
12
+ str_value_downcased = nil
13
+
14
+ return false if array.nil?
15
+
16
+ return array.any? do |item|
17
+ next false if item.nil?
18
+ item_str = item.to_s
19
+
20
+ return true if func.call(str_value, item_str)
21
+ next false unless ignore_case
22
+
23
+ str_value_downcased ||= str_value.downcase
24
+ func.call(str_value_downcased, item_str.downcase)
25
+ end
26
+ end
27
+
28
+ def self.equal_string_in_array(array, value, ignore_case)
29
+ str_value = value.to_s
30
+ str_value_downcased = nil
31
+
32
+ return false if array.nil?
33
+
34
+ return array.any? do |item|
35
+ next false if item.nil?
36
+ item_str = item.to_s
37
+
38
+ next false unless item_str.length == str_value.length
39
+
40
+ return true if item_str == str_value
41
+ next false unless ignore_case
42
+
43
+ str_value_downcased ||= str_value.downcase
44
+ item_str.downcase == str_value_downcased
45
+ end
15
46
  end
16
47
 
17
48
  def self.compare_times(a, b, func)
@@ -27,6 +58,7 @@ module EvaluationHelpers
27
58
  private
28
59
 
29
60
  def self.is_numeric(v)
61
+ return true if v.is_a?(Numeric)
30
62
  !(v.to_s =~ /\A[-+]?\d*\.?\d+\z/).nil?
31
63
  end
32
64