airbrake-ruby 4.15.0 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +22 -33
  3. data/lib/airbrake-ruby/async_sender.rb +1 -1
  4. data/lib/airbrake-ruby/backtrace.rb +6 -5
  5. data/lib/airbrake-ruby/config.rb +30 -30
  6. data/lib/airbrake-ruby/config/processor.rb +69 -0
  7. data/lib/airbrake-ruby/config/validator.rb +6 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +1 -0
  10. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -2
  13. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  14. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  15. data/lib/airbrake-ruby/filters/keys_filter.rb +21 -13
  16. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  17. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  18. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  20. data/lib/airbrake-ruby/ignorable.rb +1 -0
  21. data/lib/airbrake-ruby/notice.rb +1 -8
  22. data/lib/airbrake-ruby/notice_notifier.rb +1 -0
  23. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  24. data/lib/airbrake-ruby/performance_notifier.rb +2 -15
  25. data/lib/airbrake-ruby/promise.rb +1 -0
  26. data/lib/airbrake-ruby/query.rb +1 -6
  27. data/lib/airbrake-ruby/queue.rb +1 -8
  28. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  29. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  30. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  31. data/lib/airbrake-ruby/request.rb +1 -8
  32. data/lib/airbrake-ruby/stat.rb +1 -12
  33. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  34. data/lib/airbrake-ruby/tdigest.rb +2 -0
  35. data/lib/airbrake-ruby/thread_pool.rb +1 -0
  36. data/lib/airbrake-ruby/truncator.rb +8 -2
  37. data/lib/airbrake-ruby/version.rb +11 -1
  38. data/spec/airbrake_spec.rb +45 -22
  39. data/spec/backtrace_spec.rb +26 -26
  40. data/spec/code_hunk_spec.rb +2 -2
  41. data/spec/config/processor_spec.rb +125 -0
  42. data/spec/config/validator_spec.rb +18 -1
  43. data/spec/config_spec.rb +11 -32
  44. data/spec/filters/gem_root_filter_spec.rb +4 -4
  45. data/spec/filters/keys_allowlist_spec.rb +1 -0
  46. data/spec/filters/keys_blocklist_spec.rb +10 -0
  47. data/spec/filters/root_directory_filter_spec.rb +4 -4
  48. data/spec/filters/sql_filter_spec.rb +2 -2
  49. data/spec/notice_notifier/options_spec.rb +2 -2
  50. data/spec/notice_notifier_spec.rb +2 -2
  51. data/spec/notice_spec.rb +1 -1
  52. data/spec/performance_breakdown_spec.rb +0 -12
  53. data/spec/performance_notifier_spec.rb +0 -25
  54. data/spec/query_spec.rb +1 -11
  55. data/spec/queue_spec.rb +1 -13
  56. data/spec/remote_settings/callback_spec.rb +141 -0
  57. data/spec/remote_settings/settings_data_spec.rb +348 -0
  58. data/spec/remote_settings_spec.rb +230 -0
  59. data/spec/request_spec.rb +1 -13
  60. data/spec/spec_helper.rb +4 -4
  61. data/spec/stat_spec.rb +0 -9
  62. data/spec/sync_sender_spec.rb +3 -1
  63. metadata +18 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9737dfd8078f14a0150525e1c8b226bcaa5bb87e7627542d7a1a25a27d3c191d
4
- data.tar.gz: 653ff8929bc7199a77c59ead004167a38d214741157282d93e947c3d5ca8ecd6
3
+ metadata.gz: 43ac92e6db1b50d6cba741b6e3d2270e101bed02fa1779967f142a9a64b7bbb4
4
+ data.tar.gz: 9f0debfc7e2eb97bd2dd0e263d1e40f81e3c8132d009dfc18a9cfcabf66fe31f
5
5
  SHA512:
6
- metadata.gz: 21d60bd1415bec1e0ed3b89891f5b35e56f019b09dcb50c05f1e55b2176a2976a566c95e0a3fc4cdce2601a1b4b0e786b1ddbcc67188da6df51bd5c94188e54d
7
- data.tar.gz: 3c3b2312b7945685c6199ecd21f23601b752e1d4234d35461db2f4c7cf0318c24ee263df2536e9cffb7e107002a5a5be10af1cf7eecc16ac65e07cd948f2c68e
6
+ metadata.gz: 8287f643e7ba4af54d5f6a4ee74b03c412c0a8a8fb2b8b7f8b9e2599cc1a1d38081c85877b554cb75a5e040d06c2d415af293d41f7692d4c1bfc29aef2df4285
7
+ data.tar.gz: dc8eacdeda076da7adffa1a469ed2ffe65e3d78a810cbf38b2e5a55ba584144f343a9cfa9a3c2b1cc036eabe79198045c4dc5e8239dd948c7292f3fba1a3e3fc
@@ -12,6 +12,10 @@ require 'airbrake-ruby/mergeable'
12
12
  require 'airbrake-ruby/grouppable'
13
13
  require 'airbrake-ruby/config'
14
14
  require 'airbrake-ruby/config/validator'
15
+ require 'airbrake-ruby/config/processor'
16
+ require 'airbrake-ruby/remote_settings/callback'
17
+ require 'airbrake-ruby/remote_settings/settings_data'
18
+ require 'airbrake-ruby/remote_settings'
15
19
  require 'airbrake-ruby/promise'
16
20
  require 'airbrake-ruby/thread_pool'
17
21
  require 'airbrake-ruby/sync_sender'
@@ -117,7 +121,15 @@ module Airbrake
117
121
  def configure
118
122
  yield config = Airbrake::Config.instance
119
123
  Airbrake::Loggable.instance = config.logger
120
- process_config_options(config)
124
+
125
+ config_processor = Airbrake::Config::Processor.new(config)
126
+
127
+ config_processor.process_blocklist(notice_notifier)
128
+ config_processor.process_allowlist(notice_notifier)
129
+
130
+ @remote_settings ||= config_processor.process_remote_configuration
131
+
132
+ config_processor.add_filters(notice_notifier)
121
133
  end
122
134
 
123
135
  # @since v4.2.3
@@ -260,8 +272,8 @@ module Airbrake
260
272
  # Airbrake.close
261
273
  # Airbrake.notify('App crashed!') #=> raises Airbrake::Error
262
274
  #
263
- # @return [void]
264
- # rubocop:disable Style/GuardClause, Style/IfUnlessModifier
275
+ # @return [nil]
276
+ # rubocop:disable Style/IfUnlessModifier, Metrics/CyclomaticComplexity
265
277
  def close
266
278
  if defined?(@notice_notifier) && @notice_notifier
267
279
  @notice_notifier.close
@@ -270,8 +282,14 @@ module Airbrake
270
282
  if defined?(@performance_notifier) && @performance_notifier
271
283
  @performance_notifier.close
272
284
  end
285
+
286
+ if defined?(@remote_settings) && @remote_settings
287
+ @remote_settings.stop_polling
288
+ end
289
+
290
+ nil
273
291
  end
274
- # rubocop:enable Style/GuardClause, Style/IfUnlessModifier
292
+ # rubocop:enable Style/IfUnlessModifier, Metrics/CyclomaticComplexity
275
293
 
276
294
  # Pings the Airbrake Deploy API endpoint about the occurred deploy.
277
295
  #
@@ -567,35 +585,6 @@ module Airbrake
567
585
  self.notice_notifier = NoticeNotifier.new
568
586
  self.deploy_notifier = DeployNotifier.new
569
587
  end
570
-
571
- private
572
-
573
- # rubocop:disable Metrics/AbcSize
574
- def process_config_options(config)
575
- if config.blocklist_keys.any?
576
- blocklist = Airbrake::Filters::KeysBlocklist.new(config.blocklist_keys)
577
- notice_notifier.add_filter(blocklist)
578
- end
579
-
580
- if config.allowlist_keys.any?
581
- allowlist = Airbrake::Filters::KeysAllowlist.new(config.allowlist_keys)
582
- notice_notifier.add_filter(allowlist)
583
- end
584
-
585
- return unless config.root_directory
586
-
587
- [
588
- Airbrake::Filters::RootDirectoryFilter,
589
- Airbrake::Filters::GitRevisionFilter,
590
- Airbrake::Filters::GitRepositoryFilter,
591
- Airbrake::Filters::GitLastCheckoutFilter,
592
- ].each do |filter|
593
- next if notice_notifier.has_filter?(filter)
594
-
595
- notice_notifier.add_filter(filter.new(config.root_directory))
596
- end
597
- end
598
- # rubocop:enable Metrics/AbcSize
599
588
  end
600
589
  end
601
590
  # rubocop:enable Metrics/ModuleLength
@@ -16,7 +16,7 @@ module Airbrake
16
16
  #
17
17
  # @param [Hash] payload Whatever needs to be sent
18
18
  # @return [Airbrake::Promise]
19
- def send(payload, promise, endpoint = @config.endpoint)
19
+ def send(payload, promise, endpoint = @config.error_endpoint)
20
20
  unless thread_pool << [payload, promise, endpoint]
21
21
  return promise.reject(
22
22
  "AsyncSender has reached its capacity of #{@config.queue_size}",
@@ -22,7 +22,7 @@ module Airbrake
22
22
  (?<line>\d+) # Matches '43'
23
23
  :in\s
24
24
  `(?<function>.*)' # Matches "`block (3 levels) in <top (required)>'"
25
- \z}x
25
+ \z}x.freeze
26
26
 
27
27
  # @return [Regexp] the pattern that matches JRuby Java stack frames, such
28
28
  # as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105)
@@ -39,7 +39,7 @@ module Airbrake
39
39
  :?
40
40
  (?<line>\d+)? # Matches '105'
41
41
  \)
42
- \z}x
42
+ \z}x.freeze
43
43
 
44
44
  # @return [Regexp] the pattern that tries to assume what a generic stack
45
45
  # frame might look like, when exception's backtrace is set manually.
@@ -53,7 +53,7 @@ module Airbrake
53
53
  |
54
54
  :in\s(?<function>.+) # Matches ":in func"
55
55
  )? # ... or nothing
56
- \z}x
56
+ \z}x.freeze
57
57
 
58
58
  # @return [Regexp] the pattern that matches exceptions from PL/SQL such as
59
59
  # ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945
@@ -67,7 +67,7 @@ module Airbrake
67
67
  |
68
68
  #{GENERIC}
69
69
  )
70
- \z/x
70
+ \z/x.freeze
71
71
 
72
72
  # @return [Regexp] the pattern that matches CoffeeScript backtraces
73
73
  # usually coming from Rails & ExecJS
@@ -82,7 +82,7 @@ module Airbrake
82
82
  # Matches the Ruby part of the backtrace
83
83
  #{RUBY}
84
84
  )
85
- \z/x
85
+ \z/x.freeze
86
86
  end
87
87
 
88
88
  # @return [Integer] how many first frames should include code hunks
@@ -95,6 +95,7 @@ module Airbrake
95
95
  # @return [Array<Hash{Symbol=>String,Integer}>] the parsed backtrace
96
96
  def self.parse(exception)
97
97
  return [] if exception.backtrace.nil? || exception.backtrace.none?
98
+
98
99
  parse_backtrace(exception)
99
100
  end
100
101
 
@@ -46,6 +46,17 @@ module Airbrake
46
46
  # @api public
47
47
  attr_accessor :host
48
48
 
49
+ # @since v5.0.0
50
+ alias error_host host
51
+ # @since v5.0.0
52
+ alias error_host= host=
53
+
54
+ # @return [String] the host, which provides the API endpoint to which
55
+ # APM data should be sent
56
+ # @api public
57
+ # @since v5.0.0
58
+ attr_accessor :apm_host
59
+
49
60
  # @return [String, Pathname] the working directory of your project
50
61
  # @api public
51
62
  attr_accessor :root_directory
@@ -71,18 +82,12 @@ module Airbrake
71
82
  # @since v4.15.0
72
83
  attr_accessor :allowlist_keys
73
84
 
74
- # @deprecated Use allowlist_keys instead
75
- alias whitelist_keys allowlist_keys
76
-
77
85
  # @return [Array<String, Symbol, Regexp>] the keys, which should be
78
86
  # filtered
79
87
  # @api public
80
88
  # @since v4.15.0
81
89
  attr_accessor :blocklist_keys
82
90
 
83
- # @deprecated Use blocklist_keys instead
84
- alias blacklist_keys blocklist_keys
85
-
86
91
  # @return [Boolean] true if the library should attach code hunks to each
87
92
  # frame in a backtrace, false otherwise
88
93
  # @api public
@@ -113,6 +118,16 @@ module Airbrake
113
118
  # @since v4.12.0
114
119
  attr_accessor :job_stats
115
120
 
121
+ # @return [Boolean] true if the library should send error reports to
122
+ # Airbrake, false otherwise
123
+ # @api public
124
+ # @since 5.0.0
125
+ attr_accessor :error_notifications
126
+
127
+ # @return [String] the host such as which should be used for fetching remote
128
+ # configuration options (example: "https://bucket-name.s3.amazonaws.com")
129
+ attr_accessor :remote_config_host
130
+
116
131
  class << self
117
132
  # @return [Config]
118
133
  attr_writer :instance
@@ -134,7 +149,9 @@ module Airbrake
134
149
  self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
135
150
  self.project_id = user_config[:project_id]
136
151
  self.project_key = user_config[:project_key]
137
- self.host = 'https://api.airbrake.io'
152
+ self.error_host = 'https://api.airbrake.io'
153
+ self.apm_host = 'https://api.airbrake.io'
154
+ self.remote_config_host = 'https://v1-production-notifier-configs.s3.amazonaws.com'
138
155
 
139
156
  self.ignore_environments = []
140
157
 
@@ -153,37 +170,20 @@ module Airbrake
153
170
  self.performance_stats_flush_period = 15
154
171
  self.query_stats = true
155
172
  self.job_stats = true
173
+ self.error_notifications = true
156
174
 
157
175
  merge(user_config)
158
176
  end
159
177
  # rubocop:enable Metrics/AbcSize
160
178
 
161
- def blacklist_keys=(keys)
162
- loc = caller_locations(1..1).first
163
- Kernel.warn(
164
- "#{loc.path}:#{loc.lineno}: warning: blacklist_keys= is deprecated " \
165
- "use blocklist_keys= instead",
166
- )
167
- self.blocklist_keys = keys
168
- end
169
-
170
- def whitelist_keys=(keys)
171
- loc = caller_locations(1..1).first
172
- Kernel.warn(
173
- "#{loc.path}:#{loc.lineno}: warning: whitelist_keys= is deprecated " \
174
- "use allowlist_keys= instead",
175
- )
176
- self.allowlist_keys = keys
177
- end
178
-
179
- # The full URL to the Airbrake Notice API. Based on the +:host+ option.
179
+ # The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
180
180
  # @return [URI] the endpoint address
181
- def endpoint
182
- @endpoint ||=
181
+ def error_endpoint
182
+ @error_endpoint ||=
183
183
  begin
184
- self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
184
+ self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
185
185
  api = "api/v3/projects/#{project_id}/notices"
186
- URI.join(host, api)
186
+ URI.join(error_host, api)
187
187
  end
188
188
  end
189
189
 
@@ -0,0 +1,69 @@
1
+ module Airbrake
2
+ class Config
3
+ # Processor is a helper class, which is responsible for setting default
4
+ # config values, default notifier filters and remote configuration changes.
5
+ #
6
+ # @since 5.0.0
7
+ # @api private
8
+ class Processor
9
+ # @param [Airbrake::Config] config
10
+ # @return [Airbrake::Config::Processor]
11
+ def self.process(config)
12
+ new(config).process
13
+ end
14
+
15
+ # @param [Airbrake::Config] config
16
+ def initialize(config)
17
+ @config = config
18
+ @blocklist_keys = @config.blocklist_keys
19
+ @allowlist_keys = @config.allowlist_keys
20
+ @project_id = @config.project_id
21
+ @poll_callback = Airbrake::RemoteSettings::Callback.new(config)
22
+ end
23
+
24
+ # @param [Airbrake::NoticeNotifier] notifier
25
+ # @return [void]
26
+ def process_blocklist(notifier)
27
+ return if @blocklist_keys.none?
28
+
29
+ blocklist = Airbrake::Filters::KeysBlocklist.new(@blocklist_keys)
30
+ notifier.add_filter(blocklist)
31
+ end
32
+
33
+ # @param [Airbrake::NoticeNotifier] notifier
34
+ # @return [void]
35
+ def process_allowlist(notifier)
36
+ return if @allowlist_keys.none?
37
+
38
+ allowlist = Airbrake::Filters::KeysAllowlist.new(@allowlist_keys)
39
+ notifier.add_filter(allowlist)
40
+ end
41
+
42
+ # @return [Airbrake::RemoteSettings]
43
+ def process_remote_configuration
44
+ return unless @project_id
45
+
46
+ RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
47
+ @poll_callback.call(data)
48
+ end
49
+ end
50
+
51
+ # @param [Airbrake::NoticeNotifier] notifier
52
+ # @return [void]
53
+ def add_filters(notifier)
54
+ return unless @config.root_directory
55
+
56
+ [
57
+ Airbrake::Filters::RootDirectoryFilter,
58
+ Airbrake::Filters::GitRevisionFilter,
59
+ Airbrake::Filters::GitRepositoryFilter,
60
+ Airbrake::Filters::GitLastCheckoutFilter,
61
+ ].each do |filter|
62
+ next if notifier.has_filter?(filter)
63
+
64
+ notifier.add_filter(filter.new(@config.root_directory))
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -44,6 +44,10 @@ module Airbrake
44
44
  def check_notify_ability(config)
45
45
  promise = Airbrake::Promise.new
46
46
 
47
+ unless config.error_notifications
48
+ return promise.reject('error notifications are disabled')
49
+ end
50
+
47
51
  if ignored_environment?(config)
48
52
  return promise.reject(
49
53
  "current environment '#{config.environment}' is ignored",
@@ -57,12 +61,14 @@ module Airbrake
57
61
 
58
62
  def valid_project_id?(config)
59
63
  return true if config.project_id.to_i > 0
64
+
60
65
  false
61
66
  end
62
67
 
63
68
  def valid_project_key?(config)
64
69
  return false unless config.project_key.is_a?(String)
65
70
  return false if config.project_key.empty?
71
+
66
72
  true
67
73
  end
68
74
 
@@ -40,7 +40,7 @@ module Airbrake
40
40
  data.empty?
41
41
  end
42
42
 
43
- # @since ?.?.?
43
+ # @since 4.7.0
44
44
  # @return [void]
45
45
  def self.reset
46
46
  @data = {}
@@ -70,6 +70,7 @@ module Airbrake
70
70
  def refine(notice)
71
71
  @filters.each do |filter|
72
72
  break if notice.ignored?
73
+
73
74
  filter.call(notice)
74
75
  end
75
76
  end
@@ -24,6 +24,7 @@ module Airbrake
24
24
 
25
25
  def git_version(spec)
26
26
  return unless spec.respond_to?(:git_version) || spec.git_version
27
+
27
28
  spec.git_version.to_s
28
29
  end
29
30
  end
@@ -23,6 +23,7 @@ module Airbrake
23
23
  # If the frame is unparseable, then 'file' is nil, thus nothing to
24
24
  # filter (all frame's data is in 'function' instead).
25
25
  next unless (file = frame[:file])
26
+
26
27
  frame[:file] = file.sub(/\A#{gem_path}/, GEM_ROOT_LABEL)
27
28
  end
28
29
  end
@@ -41,12 +41,12 @@ module Airbrake
41
41
 
42
42
  return unless File.exist?(@git_path)
43
43
  return unless (checkout = last_checkout)
44
+
44
45
  notice[:context][:lastCheckout] = checkout
45
46
  end
46
47
 
47
48
  private
48
49
 
49
- # rubocop:disable Metrics/AbcSize
50
50
  def last_checkout
51
51
  return unless (line = last_checkout_line)
52
52
 
@@ -66,7 +66,6 @@ module Airbrake
66
66
  time: timestamp(parts[-2].to_i),
67
67
  }
68
68
  end
69
- # rubocop:enable Metrics/AbcSize
70
69
 
71
70
  def last_checkout_line
72
71
  head_path = File.join(@git_path, 'logs', 'HEAD')
@@ -18,6 +18,7 @@ module Airbrake
18
18
  # @macro call_filter
19
19
  def call(notice)
20
20
  return if notice[:context].key?(:repository)
21
+
21
22
  attach_repository(notice)
22
23
  end
23
24
 
@@ -39,6 +40,7 @@ module Airbrake
39
40
  end
40
41
 
41
42
  return unless @repository
43
+
42
44
  notice[:context][:repository] = @repository
43
45
  end
44
46
 
@@ -46,6 +48,7 @@ module Airbrake
46
48
 
47
49
  def detect_git_version
48
50
  return unless which('git')
51
+
49
52
  Gem::Version.new(`git --version`.split[2])
50
53
  end
51
54