airbrake-ruby 4.14.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +23 -35
  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 +37 -13
  6. data/lib/airbrake-ruby/config/processor.rb +84 -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_whitelist.rb → keys_allowlist.rb} +3 -3
  16. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +26 -18
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  22. data/lib/airbrake-ruby/ignorable.rb +1 -0
  23. data/lib/airbrake-ruby/notice.rb +1 -8
  24. data/lib/airbrake-ruby/notice_notifier.rb +1 -0
  25. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  26. data/lib/airbrake-ruby/performance_notifier.rb +2 -15
  27. data/lib/airbrake-ruby/promise.rb +1 -0
  28. data/lib/airbrake-ruby/query.rb +1 -6
  29. data/lib/airbrake-ruby/queue.rb +1 -8
  30. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  31. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  32. data/lib/airbrake-ruby/request.rb +1 -8
  33. data/lib/airbrake-ruby/stat.rb +1 -12
  34. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  35. data/lib/airbrake-ruby/tdigest.rb +2 -0
  36. data/lib/airbrake-ruby/thread_pool.rb +2 -0
  37. data/lib/airbrake-ruby/truncator.rb +8 -2
  38. data/lib/airbrake-ruby/version.rb +11 -1
  39. data/spec/airbrake_spec.rb +59 -36
  40. data/spec/backtrace_spec.rb +26 -26
  41. data/spec/code_hunk_spec.rb +2 -2
  42. data/spec/config/processor_spec.rb +209 -0
  43. data/spec/config/validator_spec.rb +18 -1
  44. data/spec/config_spec.rb +13 -6
  45. data/spec/filters/gem_root_filter_spec.rb +4 -4
  46. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +11 -10
  47. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +20 -10
  48. data/spec/filters/root_directory_filter_spec.rb +4 -4
  49. data/spec/filters/sql_filter_spec.rb +5 -5
  50. data/spec/notice_notifier/options_spec.rb +6 -6
  51. data/spec/notice_notifier_spec.rb +2 -2
  52. data/spec/notice_spec.rb +1 -1
  53. data/spec/performance_breakdown_spec.rb +0 -12
  54. data/spec/performance_notifier_spec.rb +0 -25
  55. data/spec/query_spec.rb +1 -11
  56. data/spec/queue_spec.rb +1 -13
  57. data/spec/remote_settings/settings_data_spec.rb +365 -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. data/spec/thread_pool_spec.rb +25 -5
  64. metadata +21 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b246c5d84c2bc6ff22b535260b416d37bbc2fca17fff01ae3705156ce68a74b
4
- data.tar.gz: 743b3190115223bf043cf8ecf7e9b5a8bb32b40206c146967d86489514247d99
3
+ metadata.gz: e98183a856071ce3d105c36adc9aa22a8ee7071b93958fd70fb673d2735b3977
4
+ data.tar.gz: 80464931a1362c4d845ffee737ed7aa1e46693136023b1f08006c49de98e62eb
5
5
  SHA512:
6
- metadata.gz: c798358c9215c8ba703fd81163378bcd828200a71cc2ad026f9569684ca654ac094d01f38548c2fe5919ad744456c7a42bd58304d961b5c1997b00e4a4df0f8f
7
- data.tar.gz: b7ed80303a34c3b0f58b51037063428384b4680273729f88062fd22146fcccfc55989a0f77ddc88a502859d234661a1243853d98fc1abc05f951a703962ec6af
6
+ metadata.gz: 8c32fc631e3af6348a871094dde31452d4a6fdef834596933e179d2acdbbd4935751715007fa7c4dcfce3639ebbd55f61b15f06428015647e4cbcac590ef7aaf
7
+ data.tar.gz: 23968940c6f1dfb500be5ab479c1d524cdc9f80e40808b4ff08d7f4b03c97baad603d97504c58a5300ddfa7ec9b1a4b5bd44a24a6a4a15776a82778ef2a0c324
@@ -12,6 +12,9 @@ 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/settings_data'
17
+ require 'airbrake-ruby/remote_settings'
15
18
  require 'airbrake-ruby/promise'
16
19
  require 'airbrake-ruby/thread_pool'
17
20
  require 'airbrake-ruby/sync_sender'
@@ -24,8 +27,8 @@ require 'airbrake-ruby/notice'
24
27
  require 'airbrake-ruby/backtrace'
25
28
  require 'airbrake-ruby/truncator'
26
29
  require 'airbrake-ruby/filters/keys_filter'
27
- require 'airbrake-ruby/filters/keys_whitelist'
28
- require 'airbrake-ruby/filters/keys_blacklist'
30
+ require 'airbrake-ruby/filters/keys_allowlist'
31
+ require 'airbrake-ruby/filters/keys_blocklist'
29
32
  require 'airbrake-ruby/filters/gem_root_filter'
30
33
  require 'airbrake-ruby/filters/system_exit_filter'
31
34
  require 'airbrake-ruby/filters/root_directory_filter'
@@ -117,7 +120,15 @@ module Airbrake
117
120
  def configure
118
121
  yield config = Airbrake::Config.instance
119
122
  Airbrake::Loggable.instance = config.logger
120
- process_config_options(config)
123
+
124
+ config_processor = Airbrake::Config::Processor.new(config)
125
+
126
+ config_processor.process_blocklist(notice_notifier)
127
+ config_processor.process_allowlist(notice_notifier)
128
+
129
+ @remote_settings ||= config_processor.process_remote_configuration
130
+
131
+ config_processor.add_filters(notice_notifier)
121
132
  end
122
133
 
123
134
  # @since v4.2.3
@@ -260,8 +271,8 @@ module Airbrake
260
271
  # Airbrake.close
261
272
  # Airbrake.notify('App crashed!') #=> raises Airbrake::Error
262
273
  #
263
- # @return [void]
264
- # rubocop:disable Style/GuardClause, Style/IfUnlessModifier
274
+ # @return [nil]
275
+ # rubocop:disable Style/IfUnlessModifier, Metrics/CyclomaticComplexity
265
276
  def close
266
277
  if defined?(@notice_notifier) && @notice_notifier
267
278
  @notice_notifier.close
@@ -270,8 +281,14 @@ module Airbrake
270
281
  if defined?(@performance_notifier) && @performance_notifier
271
282
  @performance_notifier.close
272
283
  end
284
+
285
+ if defined?(@remote_settings) && @remote_settings
286
+ @remote_settings.stop_polling
287
+ end
288
+
289
+ nil
273
290
  end
274
- # rubocop:enable Style/GuardClause, Style/IfUnlessModifier
291
+ # rubocop:enable Style/IfUnlessModifier, Metrics/CyclomaticComplexity
275
292
 
276
293
  # Pings the Airbrake Deploy API endpoint about the occurred deploy.
277
294
  #
@@ -567,35 +584,6 @@ module Airbrake
567
584
  self.notice_notifier = NoticeNotifier.new
568
585
  self.deploy_notifier = DeployNotifier.new
569
586
  end
570
-
571
- private
572
-
573
- # rubocop:disable Metrics/AbcSize
574
- def process_config_options(config)
575
- if config.blacklist_keys.any?
576
- blacklist = Airbrake::Filters::KeysBlacklist.new(config.blacklist_keys)
577
- notice_notifier.add_filter(blacklist)
578
- end
579
-
580
- if config.whitelist_keys.any?
581
- whitelist = Airbrake::Filters::KeysWhitelist.new(config.whitelist_keys)
582
- notice_notifier.add_filter(whitelist)
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
587
  end
600
588
  end
601
589
  # 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
@@ -68,14 +79,14 @@ module Airbrake
68
79
  # @return [Array<String, Symbol, Regexp>] the keys, which should be
69
80
  # filtered
70
81
  # @api public
71
- # @since v1.2.0
72
- attr_accessor :blacklist_keys
82
+ # @since v4.15.0
83
+ attr_accessor :allowlist_keys
73
84
 
74
- # @return [Array<String, Symbol, Regexp>] the keys, which shouldn't be
85
+ # @return [Array<String, Symbol, Regexp>] the keys, which should be
75
86
  # filtered
76
87
  # @api public
77
- # @since v1.2.0
78
- attr_accessor :whitelist_keys
88
+ # @since v4.15.0
89
+ attr_accessor :blocklist_keys
79
90
 
80
91
  # @return [Boolean] true if the library should attach code hunks to each
81
92
  # frame in a backtrace, false otherwise
@@ -107,6 +118,16 @@ module Airbrake
107
118
  # @since v4.12.0
108
119
  attr_accessor :job_stats
109
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
+
110
131
  class << self
111
132
  # @return [Config]
112
133
  attr_writer :instance
@@ -128,14 +149,16 @@ module Airbrake
128
149
  self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
129
150
  self.project_id = user_config[:project_id]
130
151
  self.project_key = user_config[:project_key]
131
- 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'
132
155
 
133
156
  self.ignore_environments = []
134
157
 
135
158
  self.timeout = user_config[:timeout]
136
159
 
137
- self.blacklist_keys = []
138
- self.whitelist_keys = []
160
+ self.blocklist_keys = []
161
+ self.allowlist_keys = []
139
162
 
140
163
  self.root_directory = File.realpath(
141
164
  (defined?(Bundler) && Bundler.root) ||
@@ -147,19 +170,20 @@ module Airbrake
147
170
  self.performance_stats_flush_period = 15
148
171
  self.query_stats = true
149
172
  self.job_stats = true
173
+ self.error_notifications = true
150
174
 
151
175
  merge(user_config)
152
176
  end
153
177
  # rubocop:enable Metrics/AbcSize
154
178
 
155
- # 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.
156
180
  # @return [URI] the endpoint address
157
- def endpoint
158
- @endpoint ||=
181
+ def error_endpoint
182
+ @error_endpoint ||=
159
183
  begin
160
- self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
184
+ self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
161
185
  api = "api/v3/projects/#{project_id}/notices"
162
- URI.join(host, api)
186
+ URI.join(error_host, api)
163
187
  end
164
188
  end
165
189
 
@@ -0,0 +1,84 @@
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
+ end
22
+
23
+ # @param [Airbrake::NoticeNotifier] notifier
24
+ # @return [void]
25
+ def process_blocklist(notifier)
26
+ return if @blocklist_keys.none?
27
+
28
+ blocklist = Airbrake::Filters::KeysBlocklist.new(@blocklist_keys)
29
+ notifier.add_filter(blocklist)
30
+ end
31
+
32
+ # @param [Airbrake::NoticeNotifier] notifier
33
+ # @return [void]
34
+ def process_allowlist(notifier)
35
+ return if @allowlist_keys.none?
36
+
37
+ allowlist = Airbrake::Filters::KeysAllowlist.new(@allowlist_keys)
38
+ notifier.add_filter(allowlist)
39
+ end
40
+
41
+ # @return [Airbrake::RemoteSettings]
42
+ def process_remote_configuration
43
+ return unless @project_id
44
+
45
+ RemoteSettings.poll(
46
+ @project_id,
47
+ @config.remote_config_host,
48
+ &method(:poll_callback)
49
+ )
50
+ end
51
+
52
+ # @param [Airbrake::NoticeNotifier] notifier
53
+ # @return [void]
54
+ def add_filters(notifier)
55
+ return unless @config.root_directory
56
+
57
+ [
58
+ Airbrake::Filters::RootDirectoryFilter,
59
+ Airbrake::Filters::GitRevisionFilter,
60
+ Airbrake::Filters::GitRepositoryFilter,
61
+ Airbrake::Filters::GitLastCheckoutFilter,
62
+ ].each do |filter|
63
+ next if notifier.has_filter?(filter)
64
+
65
+ notifier.add_filter(filter.new(@config.root_directory))
66
+ end
67
+ end
68
+
69
+ # @param [Airbrake::RemoteSettings::SettingsData] data
70
+ # @return [void]
71
+ def poll_callback(data)
72
+ @config.logger.debug(
73
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}",
74
+ )
75
+
76
+ @config.error_host = data.error_host if data.error_host
77
+ @config.apm_host = data.apm_host if data.apm_host
78
+
79
+ @config.error_notifications = data.error_notifications?
80
+ @config.performance_stats = data.performance_stats?
81
+ end
82
+ end
83
+ end
84
+ 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