airbrake-ruby 5.0.0.rc.2 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +1 -0
  3. data/lib/airbrake-ruby/backtrace.rb +6 -5
  4. data/lib/airbrake-ruby/config.rb +9 -35
  5. data/lib/airbrake-ruby/config/processor.rb +7 -17
  6. data/lib/airbrake-ruby/config/validator.rb +2 -0
  7. data/lib/airbrake-ruby/file_cache.rb +1 -1
  8. data/lib/airbrake-ruby/filter_chain.rb +1 -0
  9. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  10. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -2
  12. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  13. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  14. data/lib/airbrake-ruby/filters/keys_filter.rb +21 -13
  15. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  17. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  18. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  19. data/lib/airbrake-ruby/ignorable.rb +1 -0
  20. data/lib/airbrake-ruby/notice_notifier.rb +1 -0
  21. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  22. data/lib/airbrake-ruby/performance_notifier.rb +1 -14
  23. data/lib/airbrake-ruby/promise.rb +1 -0
  24. data/lib/airbrake-ruby/query.rb +1 -6
  25. data/lib/airbrake-ruby/queue.rb +1 -8
  26. data/lib/airbrake-ruby/remote_settings.rb +16 -54
  27. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  28. data/lib/airbrake-ruby/remote_settings/settings_data.rb +14 -14
  29. data/lib/airbrake-ruby/request.rb +1 -8
  30. data/lib/airbrake-ruby/stat.rb +1 -12
  31. data/lib/airbrake-ruby/sync_sender.rb +1 -0
  32. data/lib/airbrake-ruby/tdigest.rb +2 -0
  33. data/lib/airbrake-ruby/thread_pool.rb +1 -0
  34. data/lib/airbrake-ruby/truncator.rb +8 -2
  35. data/lib/airbrake-ruby/version.rb +2 -2
  36. data/spec/backtrace_spec.rb +26 -26
  37. data/spec/code_hunk_spec.rb +2 -2
  38. data/spec/config/processor_spec.rb +13 -102
  39. data/spec/config_spec.rb +4 -29
  40. data/spec/filters/gem_root_filter_spec.rb +4 -4
  41. data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
  42. data/spec/filters/keys_allowlist_spec.rb +1 -0
  43. data/spec/filters/keys_blocklist_spec.rb +10 -0
  44. data/spec/filters/root_directory_filter_spec.rb +4 -4
  45. data/spec/filters/sql_filter_spec.rb +2 -2
  46. data/spec/notice_notifier/options_spec.rb +2 -2
  47. data/spec/notice_notifier_spec.rb +2 -2
  48. data/spec/notice_spec.rb +1 -1
  49. data/spec/performance_breakdown_spec.rb +0 -12
  50. data/spec/performance_notifier_spec.rb +0 -25
  51. data/spec/query_spec.rb +1 -11
  52. data/spec/queue_spec.rb +1 -13
  53. data/spec/remote_settings/callback_spec.rb +143 -0
  54. data/spec/remote_settings/settings_data_spec.rb +34 -13
  55. data/spec/remote_settings_spec.rb +40 -83
  56. data/spec/request_spec.rb +1 -13
  57. data/spec/spec_helper.rb +4 -4
  58. data/spec/stat_spec.rb +0 -9
  59. metadata +8 -5
@@ -16,6 +16,7 @@ module Airbrake
16
16
  # @macro call_filter
17
17
  def call(notice)
18
18
  return if notice[:errors].none? { |error| error[:type] == SYSTEM_EXIT_TYPE }
19
+
19
20
  notice.ignore!
20
21
  end
21
22
  end
@@ -56,6 +56,7 @@ module Airbrake
56
56
  def thread_variables(th)
57
57
  th.thread_variables.map.with_object({}) do |var, h|
58
58
  next if var.to_s.start_with?(IGNORE_PREFIX)
59
+
59
60
  h[var] = sanitize_value(th.thread_variable_get(var))
60
61
  end
61
62
  end
@@ -63,6 +64,7 @@ module Airbrake
63
64
  def fiber_variables(th)
64
65
  th.keys.map.with_object({}) do |key, h|
65
66
  next if key.to_s.start_with?(IGNORE_PREFIX)
67
+
66
68
  h[key] = sanitize_value(th[key])
67
69
  end
68
70
  end
@@ -38,6 +38,7 @@ module Airbrake
38
38
  # @raise [Airbrake::Error] when instance is ignored
39
39
  def raise_if_ignored
40
40
  return unless ignored?
41
+
41
42
  raise Airbrake::Error, "cannot access ignored #{self.class}"
42
43
  end
43
44
  end
@@ -135,6 +135,7 @@ module Airbrake
135
135
  # If true, then it's likely an internal library error. In this case return
136
136
  # at least some backtrace to simplify debugging.
137
137
  return caller_copy if clean_bt.empty?
138
+
138
139
  clean_bt
139
140
  end
140
141
  end
@@ -12,16 +12,13 @@ module Airbrake
12
12
  include Stashable
13
13
  include Mergeable
14
14
 
15
- attr_accessor :method, :route, :response_type, :groups, :start_time,
16
- :end_time, :timing, :time
15
+ attr_accessor :method, :route, :response_type, :groups, :timing, :time
17
16
 
18
17
  def initialize(
19
18
  method:,
20
19
  route:,
21
20
  response_type:,
22
21
  groups:,
23
- start_time: Time.now,
24
- end_time: start_time + 1,
25
22
  timing: nil,
26
23
  time: Time.now
27
24
  )
@@ -30,8 +27,6 @@ module Airbrake
30
27
  @route = route
31
28
  @response_type = response_type
32
29
  @groups = groups
33
- @start_time = start_time
34
- @end_time = end_time
35
30
  @timing = timing
36
31
  @time = time
37
32
  end
@@ -104,7 +104,7 @@ module Airbrake
104
104
  @payload[resource] = { total: Airbrake::Stat.new }
105
105
  end
106
106
 
107
- update_total(resource, @payload[resource][:total])
107
+ @payload[resource][:total].increment_ms(resource.timing)
108
108
 
109
109
  resource.groups.each do |name, ms|
110
110
  @payload[resource][name] ||= Airbrake::Stat.new
@@ -112,19 +112,6 @@ module Airbrake
112
112
  end
113
113
  end
114
114
 
115
- def update_total(resource, total)
116
- if resource.timing
117
- total.increment_ms(resource.timing)
118
- else
119
- loc = caller_locations(6..6).first
120
- Kernel.warn(
121
- "#{loc.path}:#{loc.lineno}: warning: :start_time and :end_time are " \
122
- "deprecated. Use :timing & :time instead",
123
- )
124
- total.increment(resource.start_time, resource.end_time)
125
- end
126
- end
127
-
128
115
  def check_configuration(resource)
129
116
  promise = @config.check_configuration
130
117
  return promise if promise.rejected?
@@ -103,6 +103,7 @@ module Airbrake
103
103
  # needed for compatibility but it shouldn't exist in the future
104
104
  def value
105
105
  return @value['ok'] if resolved?
106
+
106
107
  @value
107
108
  end
108
109
  end
@@ -12,8 +12,7 @@ module Airbrake
12
12
  include Mergeable
13
13
  include Grouppable
14
14
 
15
- attr_accessor :method, :route, :query, :func, :file, :line, :start_time,
16
- :end_time, :timing, :time
15
+ attr_accessor :method, :route, :query, :func, :file, :line, :timing, :time
17
16
 
18
17
  def initialize(
19
18
  method:,
@@ -22,8 +21,6 @@ module Airbrake
22
21
  func: nil,
23
22
  file: nil,
24
23
  line: nil,
25
- start_time: Time.now,
26
- end_time: start_time + 1,
27
24
  timing: nil,
28
25
  time: Time.now
29
26
  )
@@ -34,8 +31,6 @@ module Airbrake
34
31
  @func = func
35
32
  @file = file
36
33
  @line = line
37
- @start_time = start_time
38
- @end_time = end_time
39
34
  @timing = timing
40
35
  @time = time
41
36
  end
@@ -4,21 +4,17 @@ module Airbrake
4
4
  # @see Airbrake.notify_queue
5
5
  # @api public
6
6
  # @since v4.9.0
7
- # rubocop:disable Metrics/ParameterLists
8
7
  class Queue
9
8
  include HashKeyable
10
9
  include Ignorable
11
10
  include Stashable
12
11
 
13
- attr_accessor :queue, :error_count, :groups, :start_time, :end_time,
14
- :timing, :time
12
+ attr_accessor :queue, :error_count, :groups, :timing, :time
15
13
 
16
14
  def initialize(
17
15
  queue:,
18
16
  error_count:,
19
17
  groups: {},
20
- start_time: Time.now,
21
- end_time: start_time + 1,
22
18
  timing: nil,
23
19
  time: Time.now
24
20
  )
@@ -26,8 +22,6 @@ module Airbrake
26
22
  @queue = queue
27
23
  @error_count = error_count
28
24
  @groups = groups
29
- @start_time = start_time
30
- @end_time = end_time
31
25
  @timing = timing
32
26
  @time = time
33
27
  end
@@ -68,5 +62,4 @@ module Airbrake
68
62
  ''
69
63
  end
70
64
  end
71
- # rubocop:enable Metrics/ParameterLists
72
65
  end
@@ -8,22 +8,11 @@ module Airbrake
8
8
  # config.error_notifications = data.error_notifications?
9
9
  # end
10
10
  #
11
- # When {#poll} is called, it will try to load remote settings from disk, so
12
- # that it doesn't wait on the result from the API call.
13
- #
14
- # When {#stop_polling} is called, the current config will be dumped to disk.
15
- #
16
- # @since ?.?.?
11
+ # @since 5.0.0
17
12
  # @api private
18
13
  class RemoteSettings
19
14
  include Airbrake::Loggable
20
15
 
21
- # @return [String] the path to the persistent config
22
- CONFIG_DUMP_PATH = File.join(
23
- File.expand_path(__dir__),
24
- '../../config/config.json',
25
- ).freeze
26
-
27
16
  # @return [Hash{Symbol=>String}] metadata to be attached to every GET
28
17
  # request
29
18
  QUERY_PARAMS = URI.encode_www_form(
@@ -33,37 +22,35 @@ module Airbrake
33
22
  language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
34
23
  ).freeze
35
24
 
25
+ # @return [String]
26
+ HTTP_OK = '200'.freeze
27
+
36
28
  # Polls remote config of the given project.
37
29
  #
38
30
  # @param [Integer] project_id
31
+ # @param [String] host
39
32
  # @yield [data]
40
33
  # @yieldparam data [Airbrake::RemoteSettings::SettingsData]
41
34
  # @return [Airbrake::RemoteSettings]
42
- def self.poll(project_id, &block)
43
- new(project_id, &block).poll
35
+ def self.poll(project_id, host, &block)
36
+ new(project_id, host, &block).poll
44
37
  end
45
38
 
46
39
  # @param [Integer] project_id
47
40
  # @yield [data]
48
41
  # @yieldparam data [Airbrake::RemoteSettings::SettingsData]
49
- def initialize(project_id, &block)
42
+ def initialize(project_id, host, &block)
50
43
  @data = SettingsData.new(project_id, {})
44
+ @host = host
51
45
  @block = block
52
46
  @poll = nil
53
47
  end
54
48
 
55
- # Polls remote config of the given project in background. Loads local config
56
- # first (if exists).
49
+ # Polls remote config of the given project in background.
57
50
  #
58
51
  # @return [self]
59
52
  def poll
60
53
  @poll ||= Thread.new do
61
- begin
62
- load_config
63
- rescue StandardError => ex
64
- logger.error("#{LOG_LABEL} config loading failed: #{ex}")
65
- end
66
-
67
54
  @block.call(@data)
68
55
 
69
56
  loop do
@@ -75,17 +62,11 @@ module Airbrake
75
62
  self
76
63
  end
77
64
 
78
- # Stops the background poller thread. Dumps current config to disk.
65
+ # Stops the background poller thread.
79
66
  #
80
67
  # @return [void]
81
68
  def stop_polling
82
69
  @poll.kill if @poll
83
-
84
- begin
85
- dump_config
86
- rescue StandardError => ex
87
- logger.error("#{LOG_LABEL} config dumping failed: #{ex}")
88
- end
89
70
  end
90
71
 
91
72
  private
@@ -93,22 +74,20 @@ module Airbrake
93
74
  def fetch_config
94
75
  response = nil
95
76
  begin
96
- response = Net::HTTP.get(build_config_uri)
77
+ response = Net::HTTP.get_response(build_config_uri)
97
78
  rescue StandardError => ex
98
79
  logger.error(ex)
99
80
  return {}
100
81
  end
101
82
 
102
- # AWS S3 API returns XML when request is not valid. In this case we just
103
- # print the returned body and exit the method.
104
- if response.start_with?('<?xml ')
105
- logger.error(response)
83
+ unless response.code == HTTP_OK
84
+ logger.error(response.body)
106
85
  return {}
107
86
  end
108
87
 
109
88
  json = nil
110
89
  begin
111
- json = JSON.parse(response)
90
+ json = JSON.parse(response.body)
112
91
  rescue JSON::ParserError => ex
113
92
  logger.error(ex)
114
93
  return {}
@@ -118,26 +97,9 @@ module Airbrake
118
97
  end
119
98
 
120
99
  def build_config_uri
121
- uri = URI(@data.config_route)
100
+ uri = URI(@data.config_route(@host))
122
101
  uri.query = QUERY_PARAMS
123
102
  uri
124
103
  end
125
-
126
- def load_config
127
- config_dir = File.dirname(CONFIG_DUMP_PATH)
128
- Dir.mkdir(config_dir) unless File.directory?(config_dir)
129
-
130
- return unless File.exist?(CONFIG_DUMP_PATH)
131
-
132
- config = File.read(CONFIG_DUMP_PATH)
133
- @data.merge!(JSON.parse(config))
134
- end
135
-
136
- def dump_config
137
- config_dir = File.dirname(CONFIG_DUMP_PATH)
138
- Dir.mkdir(config_dir) unless File.directory?(config_dir)
139
-
140
- File.write(CONFIG_DUMP_PATH, JSON.dump(@data.to_h))
141
- end
142
104
  end
143
105
  end
@@ -0,0 +1,44 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # Callback is a class that provides a callback for the config poller, which
4
+ # updates the local config according to the data.
5
+ #
6
+ # @api private
7
+ # @since 5.0.2
8
+ class Callback
9
+ def initialize(config)
10
+ @config = config
11
+ @orig_error_notifications = config.error_notifications
12
+ @orig_performance_stats = config.performance_stats
13
+ end
14
+
15
+ # @param [Airbrake::RemoteSettings::SettingsData] data
16
+ # @return [void]
17
+ def call(data)
18
+ @config.logger.debug do
19
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}"
20
+ end
21
+
22
+ @config.error_host = data.error_host if data.error_host
23
+ @config.apm_host = data.apm_host if data.apm_host
24
+
25
+ process_error_notifications(data)
26
+ process_performance_stats(data)
27
+ end
28
+
29
+ private
30
+
31
+ def process_error_notifications(data)
32
+ return unless @orig_error_notifications
33
+
34
+ @config.error_notifications = data.error_notifications?
35
+ end
36
+
37
+ def process_performance_stats(data)
38
+ return unless @orig_performance_stats
39
+
40
+ @config.performance_stats = data.performance_stats?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -11,7 +11,7 @@ module Airbrake
11
11
  #
12
12
  # settings_data.interval #=> 600
13
13
  #
14
- # @since ?.?.?
14
+ # @since 5.0.0
15
15
  # @api private
16
16
  class SettingsData
17
17
  # @return [Integer] how frequently we should poll the config API
@@ -20,16 +20,15 @@ module Airbrake
20
20
  # @return [String] API version of the S3 API to poll
21
21
  API_VER = '2020-06-18'.freeze
22
22
 
23
- # @return [String] what URL to poll
23
+ # @return [String] what path to poll
24
24
  CONFIG_ROUTE_PATTERN =
25
- 'https://v1-%<bucket>s.s3.amazonaws.com/' \
26
- "#{API_VER}/config/%<project_id>s/config.json".freeze
25
+ "%<host>s/#{API_VER}/config/%<project_id>s/config.json".freeze
27
26
 
28
27
  # @return [Hash{Symbol=>String}] the hash of all supported settings where
29
28
  # the value is the name of the setting returned by the API
30
29
  SETTINGS = {
31
- errors: 'errors',
32
- apm: 'apm',
30
+ errors: 'errors'.freeze,
31
+ apm: 'apm'.freeze,
33
32
  }.freeze
34
33
 
35
34
  # @param [Integer] project_id
@@ -56,17 +55,18 @@ module Airbrake
56
55
  @data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
57
56
  end
58
57
 
58
+ # @param [String] remote_config_host
59
59
  # @return [String] where the config is stored on S3.
60
- def config_route
61
- if !@data.key?('config_route') || !@data['config_route']
62
- return format(
63
- CONFIG_ROUTE_PATTERN,
64
- bucket: 'staging-notifier-configs',
65
- project_id: @project_id,
66
- )
60
+ def config_route(remote_config_host)
61
+ if @data['config_route'] && !@data['config_route'].empty?
62
+ return remote_config_host.chomp('/') + '/' + @data['config_route']
67
63
  end
68
64
 
69
- @data['config_route']
65
+ format(
66
+ CONFIG_ROUTE_PATTERN,
67
+ host: remote_config_host.chomp('/'),
68
+ project_id: @project_id,
69
+ )
70
70
  end
71
71
 
72
72
  # @return [Boolean] whether error notifications are enabled
@@ -4,7 +4,6 @@ module Airbrake
4
4
  # @see Airbrake.notify_request
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/ParameterLists
8
7
  class Request
9
8
  include HashKeyable
10
9
  include Ignorable
@@ -12,15 +11,12 @@ module Airbrake
12
11
  include Mergeable
13
12
  include Grouppable
14
13
 
15
- attr_accessor :method, :route, :status_code, :start_time, :end_time,
16
- :timing, :time
14
+ attr_accessor :method, :route, :status_code, :timing, :time
17
15
 
18
16
  def initialize(
19
17
  method:,
20
18
  route:,
21
19
  status_code:,
22
- start_time: Time.now,
23
- end_time: start_time + 1,
24
20
  timing: nil,
25
21
  time: Time.now
26
22
  )
@@ -28,8 +24,6 @@ module Airbrake
28
24
  @method = method
29
25
  @route = route
30
26
  @status_code = status_code
31
- @start_time = start_time
32
- @end_time = end_time
33
27
  @timing = timing
34
28
  @time = time
35
29
  end
@@ -51,5 +45,4 @@ module Airbrake
51
45
  }.delete_if { |_key, val| val.nil? }
52
46
  end
53
47
  end
54
- # rubocop:enable Metrics/ParameterLists
55
48
  end