airbrake-ruby 4.6.0 → 6.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +24 -109
  3. data/lib/airbrake-ruby/backlog.rb +123 -0
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config/processor.rb +77 -0
  8. data/lib/airbrake-ruby/config/validator.rb +9 -3
  9. data/lib/airbrake-ruby/config.rb +78 -19
  10. data/lib/airbrake-ruby/context.rb +51 -0
  11. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  12. data/lib/airbrake-ruby/file_cache.rb +6 -0
  13. data/lib/airbrake-ruby/filter_chain.rb +18 -1
  14. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  15. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +3 -3
  17. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  18. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +7 -7
  19. data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
  20. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  21. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  22. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  23. data/lib/airbrake-ruby/filters/keys_filter.rb +41 -22
  24. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/sql_filter.rb +35 -11
  26. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  27. data/lib/airbrake-ruby/filters/thread_filter.rb +6 -5
  28. data/lib/airbrake-ruby/grouppable.rb +12 -0
  29. data/lib/airbrake-ruby/ignorable.rb +1 -2
  30. data/lib/airbrake-ruby/inspectable.rb +2 -2
  31. data/lib/airbrake-ruby/loggable.rb +1 -1
  32. data/lib/airbrake-ruby/mergeable.rb +12 -0
  33. data/lib/airbrake-ruby/monotonic_time.rb +6 -1
  34. data/lib/airbrake-ruby/nested_exception.rb +22 -1
  35. data/lib/airbrake-ruby/notice.rb +12 -17
  36. data/lib/airbrake-ruby/notice_notifier.rb +15 -7
  37. data/lib/airbrake-ruby/performance_breakdown.rb +16 -9
  38. data/lib/airbrake-ruby/performance_notifier.rb +106 -54
  39. data/lib/airbrake-ruby/promise.rb +1 -0
  40. data/lib/airbrake-ruby/query.rb +20 -14
  41. data/lib/airbrake-ruby/queue.rb +65 -0
  42. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  43. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  44. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  45. data/lib/airbrake-ruby/request.rb +14 -11
  46. data/lib/airbrake-ruby/response.rb +56 -5
  47. data/lib/airbrake-ruby/stat.rb +27 -34
  48. data/lib/airbrake-ruby/sync_sender.rb +43 -11
  49. data/lib/airbrake-ruby/tdigest.rb +52 -66
  50. data/lib/airbrake-ruby/thread_pool.rb +139 -0
  51. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  52. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  53. data/lib/airbrake-ruby/truncator.rb +10 -4
  54. data/lib/airbrake-ruby/version.rb +11 -1
  55. data/lib/airbrake-ruby.rb +157 -78
  56. metadata +122 -116
  57. data/spec/airbrake_spec.rb +0 -324
  58. data/spec/async_sender_spec.rb +0 -155
  59. data/spec/backtrace_spec.rb +0 -427
  60. data/spec/benchmark_spec.rb +0 -33
  61. data/spec/code_hunk_spec.rb +0 -115
  62. data/spec/config/validator_spec.rb +0 -184
  63. data/spec/config_spec.rb +0 -154
  64. data/spec/deploy_notifier_spec.rb +0 -48
  65. data/spec/file_cache.rb +0 -36
  66. data/spec/filter_chain_spec.rb +0 -92
  67. data/spec/filters/context_filter_spec.rb +0 -23
  68. data/spec/filters/dependency_filter_spec.rb +0 -12
  69. data/spec/filters/exception_attributes_filter_spec.rb +0 -50
  70. data/spec/filters/gem_root_filter_spec.rb +0 -41
  71. data/spec/filters/git_last_checkout_filter_spec.rb +0 -46
  72. data/spec/filters/git_repository_filter.rb +0 -61
  73. data/spec/filters/git_revision_filter_spec.rb +0 -126
  74. data/spec/filters/keys_blacklist_spec.rb +0 -225
  75. data/spec/filters/keys_whitelist_spec.rb +0 -194
  76. data/spec/filters/root_directory_filter_spec.rb +0 -39
  77. data/spec/filters/sql_filter_spec.rb +0 -219
  78. data/spec/filters/system_exit_filter_spec.rb +0 -14
  79. data/spec/filters/thread_filter_spec.rb +0 -277
  80. data/spec/fixtures/notroot.txt +0 -7
  81. data/spec/fixtures/project_root/code.rb +0 -221
  82. data/spec/fixtures/project_root/empty_file.rb +0 -0
  83. data/spec/fixtures/project_root/long_line.txt +0 -1
  84. data/spec/fixtures/project_root/short_file.rb +0 -3
  85. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
  86. data/spec/helpers.rb +0 -9
  87. data/spec/ignorable_spec.rb +0 -14
  88. data/spec/inspectable_spec.rb +0 -45
  89. data/spec/monotonic_time_spec.rb +0 -12
  90. data/spec/nested_exception_spec.rb +0 -73
  91. data/spec/notice_notifier_spec/options_spec.rb +0 -259
  92. data/spec/notice_notifier_spec.rb +0 -356
  93. data/spec/notice_spec.rb +0 -296
  94. data/spec/performance_breakdown_spec.rb +0 -12
  95. data/spec/performance_notifier_spec.rb +0 -435
  96. data/spec/promise_spec.rb +0 -197
  97. data/spec/query_spec.rb +0 -11
  98. data/spec/request_spec.rb +0 -11
  99. data/spec/response_spec.rb +0 -88
  100. data/spec/spec_helper.rb +0 -100
  101. data/spec/stashable_spec.rb +0 -23
  102. data/spec/stat_spec.rb +0 -47
  103. data/spec/sync_sender_spec.rb +0 -133
  104. data/spec/tdigest_spec.rb +0 -230
  105. data/spec/time_truncate_spec.rb +0 -13
  106. data/spec/timed_trace_spec.rb +0 -125
  107. data/spec/truncator_spec.rb +0 -238
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cf81123734caec939f2b487e0ca743ea26659f46
4
- data.tar.gz: ba8e315a20b3f59d0863d027010e54311ba15d9d
2
+ SHA256:
3
+ metadata.gz: 0c1f0e8b950ee1f9b9e6270071d3513508fd2c342673d206a50951ecca0281e0
4
+ data.tar.gz: 31fb69c96a4aede059904726542f925df9066365f864765254896d89c67a7e30
5
5
  SHA512:
6
- metadata.gz: 551704a67996c2064820507d4fb3dd0218df863e7433d77356dd45bc62ecab112508372125a98c8d2f651c2693716e96c86b8e0172ffd265b60de0258d1b7acf
7
- data.tar.gz: c32da13a83d385bc91b7cc2f236324ae29918005bac9450076d03fc0b707f49b2474049dac7c13fb78f1b0c848c2a1e95579f3c5f1c2315581c8db7d8c1eb3e0
6
+ metadata.gz: e6f2d8369dbc2b58b257ab7e06e2abf1f893b90570eb1ec2012e2ecd35f98f6b03b44130605a689dc57f3367400b220821a511428bdd3dfa4841c2955a0ff9af
7
+ data.tar.gz: 217107fd6982ccea8b0f4158546688d78d6d2415b627f908016de3f036c4cbc1ccae6c2148b670c4eee5fc93a45f8fdf62d37431a492cc0f20d3ca662a8ae5de
@@ -1,142 +1,57 @@
1
1
  module Airbrake
2
- # Responsible for sending notices to Airbrake asynchronously. The class
3
- # supports an unlimited number of worker threads and an unlimited queue size
4
- # (both values are configurable).
2
+ # Responsible for sending notices to Airbrake asynchronously.
5
3
  #
6
4
  # @see SyncSender
7
5
  # @api private
8
6
  # @since v1.0.0
9
7
  class AsyncSender
10
- include Loggable
11
-
12
- # @return [String]
13
- WILL_NOT_DELIVER_MSG =
14
- "%<log_label>s AsyncSender has reached its capacity of %<capacity>s " \
15
- "and the following notice will not be delivered " \
16
- "Error: %<type>s - %<message>s\nBacktrace: %<backtrace>s\n".freeze
17
-
18
- # @return [ThreadGroup] the list of workers
19
- # @note This is exposed for eaiser unit testing
20
- # @since v4.0.0
21
- attr_reader :workers
22
-
23
- # @return [Array<[Airbrake::Notice,Airbrake::Promise]>] the list of unsent
24
- # payload
25
- # @note This is exposed for eaiser unit testing
26
- # @since v4.0.0
27
- attr_reader :unsent
28
-
29
- def initialize
8
+ def initialize(method = :post, name = 'async-sender')
30
9
  @config = Airbrake::Config.instance
31
- @unsent = SizedQueue.new(Airbrake::Config.instance.queue_size)
32
- @sender = SyncSender.new
33
- @closed = false
34
- @workers = ThreadGroup.new
35
- @mutex = Mutex.new
36
- @pid = nil
10
+ @sync_sender = SyncSender.new(method)
11
+ @name = name
37
12
  end
38
13
 
39
14
  # Asynchronously sends a notice to Airbrake.
40
15
  #
41
- # @param [Airbrake::Notice] notice A notice that was generated by the
42
- # library
16
+ # @param [Airbrake::Notice] data Whatever needs to be sent
17
+ # @param [Airbrake::Promise] promise
18
+ # @param [URI] endpoint Where to send +data+
43
19
  # @return [Airbrake::Promise]
44
- def send(notice, promise)
45
- return will_not_deliver(notice, promise) if @unsent.size >= @unsent.max
20
+ def send(data, promise, endpoint = @config.error_endpoint)
21
+ unless thread_pool << [data, promise, endpoint]
22
+ return promise.reject(
23
+ "AsyncSender has reached its capacity of #{@config.queue_size}",
24
+ )
25
+ end
46
26
 
47
- @unsent << [notice, promise]
48
27
  promise
49
28
  end
50
29
 
51
- # Closes the instance making it a no-op (it shut downs all worker
52
- # threads). Before closing, waits on all unsent notices to be sent.
53
- #
54
30
  # @return [void]
55
- # @raise [Airbrake::Error] when invoked more than one time
56
31
  def close
57
- threads = @mutex.synchronize do
58
- raise Airbrake::Error, 'attempted to close already closed sender' if closed?
59
-
60
- unless @unsent.empty?
61
- msg = "#{LOG_LABEL} waiting to send #{@unsent.size} unsent notice(s)..."
62
- logger.debug(msg + ' (Ctrl-C to abort)')
63
- end
64
-
65
- @config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
66
- @closed = true
67
- @workers.list.dup
68
- end
69
-
70
- threads.each(&:join)
71
- logger.debug("#{LOG_LABEL} closed")
32
+ @sync_sender.close
33
+ thread_pool.close
72
34
  end
73
35
 
74
- # Checks whether the sender is closed and thus usable.
75
36
  # @return [Boolean]
76
37
  def closed?
77
- @closed
38
+ thread_pool.closed?
78
39
  end
79
40
 
80
- # Checks if an active sender has any workers. A sender doesn't have any
81
- # workers only in two cases: when it was closed or when all workers
82
- # crashed. An *active* sender doesn't have any workers only when something
83
- # went wrong.
84
- #
85
- # Workers are expected to crash when you +fork+ the process the workers are
86
- # living in. In this case we detect a +fork+ and try to revive them here.
87
- #
88
- # Another possible scenario that crashes workers is when you close the
89
- # instance on +at_exit+, but some other +at_exit+ hook prevents the process
90
- # from exiting.
91
- #
92
- # @return [Boolean] true if an instance wasn't closed, but has no workers
93
- # @see https://goo.gl/oydz8h Example of at_exit that prevents exit
41
+ # @return [Boolean]
94
42
  def has_workers?
95
- @mutex.synchronize do
96
- return false if @closed
97
-
98
- if @pid != Process.pid && @workers.list.empty?
99
- @pid = Process.pid
100
- spawn_workers
101
- end
102
-
103
- !@closed && @workers.list.any?
104
- end
43
+ thread_pool.has_workers?
105
44
  end
106
45
 
107
46
  private
108
47
 
109
- def spawn_workers
110
- @workers = ThreadGroup.new
111
- @config.workers.times { @workers.add(spawn_worker) }
112
- @workers.enclose
113
- end
114
-
115
- def spawn_worker
116
- Thread.new do
117
- while (message = @unsent.pop)
118
- break if message.first == :stop
119
- @sender.send(*message)
120
- end
121
- end
122
- end
123
-
124
- def will_not_deliver(notice, promise)
125
- error = notice[:errors].first
126
-
127
- logger.error(
128
- format(
129
- WILL_NOT_DELIVER_MSG,
130
- log_label: LOG_LABEL,
131
- capacity: @unsent.max,
132
- type: error[:type],
133
- message: error[:message],
134
- backtrace: error[:backtrace].map do |line|
135
- "#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
136
- end.join("\n")
137
- )
48
+ def thread_pool
49
+ @thread_pool ||= ThreadPool.new(
50
+ name: @name,
51
+ worker_size: @config.workers,
52
+ queue_size: @config.queue_size,
53
+ block: proc { |args| @sync_sender.send(*args) },
138
54
  )
139
- promise.reject("AsyncSender has reached its capacity of #{@unsent.max}")
140
55
  end
141
56
  end
142
57
  end
@@ -0,0 +1,123 @@
1
+ module Airbrake
2
+ # Backlog accepts notices and APM events and synchronously sends them in the
3
+ # background at regular intervals. The backlog is a queue of data that failed
4
+ # to be sent due to some error. In a nutshell, it's a retry mechanism.
5
+ #
6
+ # @api private
7
+ # @since v6.2.0
8
+ class Backlog
9
+ include Loggable
10
+
11
+ # @return [Integer] how many records to keep in the backlog
12
+ BACKLOG_SIZE = 100
13
+
14
+ # @return [Integer] flush period in seconds
15
+ TWO_MINUTES = 60 * 2
16
+
17
+ def initialize(sync_sender, flush_period = TWO_MINUTES)
18
+ @sync_sender = sync_sender
19
+ @flush_period = flush_period
20
+ @queue = SizedQueue.new(BACKLOG_SIZE).extend(MonitorMixin)
21
+ @has_backlog_data = @queue.new_cond
22
+ @schedule_flush = nil
23
+
24
+ @seen = Set.new
25
+ end
26
+
27
+ # Appends data to the backlog. Once appended, the flush schedule will
28
+ # start. Chainable.
29
+ #
30
+ # @example
31
+ # backlog << [{ 'data' => 1 }, 'https://airbrake.io/api']
32
+ #
33
+ # @param [Array<#to_json, String>] data An array of two elements, where the
34
+ # first element is the data we are sending and the second element is the
35
+ # URL that we are sending to
36
+ # @return [self]
37
+ def <<(data)
38
+ @queue.synchronize do
39
+ return self if @seen.include?(data)
40
+
41
+ @seen << data
42
+
43
+ begin
44
+ @queue.push(data, true)
45
+ rescue ThreadError
46
+ logger.error("#{LOG_LABEL} Airbrake::Backlog full")
47
+ return self
48
+ end
49
+
50
+ @has_backlog_data.signal
51
+ schedule_flush
52
+
53
+ self
54
+ end
55
+ end
56
+
57
+ # Closes all the resources that this sender has allocated.
58
+ #
59
+ # @return [void]
60
+ # @since v6.2.0
61
+ def close
62
+ @queue.synchronize do
63
+ if @schedule_flush
64
+ @schedule_flush.kill
65
+ logger.debug("#{LOG_LABEL} Airbrake::Backlog closed")
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def schedule_flush
73
+ @schedule_flush ||= Thread.new do
74
+ loop do
75
+ @queue.synchronize do
76
+ wait
77
+ next if @queue.empty?
78
+
79
+ flush
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def wait
86
+ @has_backlog_data.wait(@flush_period) while time_elapsed < @flush_period
87
+ @last_flush = nil
88
+ end
89
+
90
+ def time_elapsed
91
+ MonotonicTime.time_in_s - last_flush
92
+ end
93
+
94
+ def last_flush
95
+ @last_flush ||= MonotonicTime.time_in_s
96
+ end
97
+
98
+ def flush
99
+ unless @queue.empty?
100
+ logger.debug(
101
+ "#{LOG_LABEL} Airbrake::Backlog flushing #{@queue.size} messages",
102
+ )
103
+ end
104
+
105
+ failed = 0
106
+
107
+ until @queue.empty?
108
+ data, endpoint = @queue.pop
109
+ promise = Airbrake::Promise.new
110
+ @sync_sender.send(data, promise, endpoint)
111
+ failed += 1 if promise.rejected?
112
+ end
113
+
114
+ if failed > 0
115
+ logger.debug(
116
+ "#{LOG_LABEL} Airbrake::Backlog #{failed} messages were not flushed",
117
+ )
118
+ end
119
+
120
+ @seen.clear
121
+ end
122
+ end
123
+ end
@@ -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
 
@@ -147,13 +148,13 @@ module Airbrake
147
148
  return {
148
149
  file: match[:file],
149
150
  line: (Integer(match[:line]) if match[:line]),
150
- function: match[:function]
151
+ function: match[:function],
151
152
  }
152
153
  end
153
154
 
154
155
  logger.error(
155
156
  "can't parse '#{stackframe}' (please file an issue so we can fix " \
156
- "it: https://github.com/airbrake/airbrake-ruby/issues/new)"
157
+ "it: https://github.com/airbrake/airbrake-ruby/issues/new)",
157
158
  )
158
159
  { file: nil, line: nil, function: stackframe }
159
160
  end
@@ -2,7 +2,7 @@ module Airbrake
2
2
  # Benchmark benchmarks Ruby code.
3
3
  #
4
4
  # @since v4.2.4
5
- # @api private
5
+ # @api public
6
6
  class Benchmark
7
7
  # Measures monotonic time for the given operation.
8
8
  #
@@ -30,7 +30,7 @@ module Airbrake
30
30
  Airbrake::FileCache[file] ||= File.foreach(file)
31
31
  rescue StandardError => ex
32
32
  logger.error(
33
- "#{self.class.name}: can't read code hunk for #{file}: #{ex}"
33
+ "#{self.class.name}: can't read code hunk for #{file}: #{ex}",
34
34
  )
35
35
  nil
36
36
  end
@@ -0,0 +1,77 @@
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 v5.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 @config.remote_config
45
+ return unless @project_id
46
+
47
+ # Never poll remote configuration in the test environment.
48
+ return if @config.environment == 'test'
49
+
50
+ # If the current environment is ignored, don't try to poll remote
51
+ # configuration.
52
+ return if @config.ignore_environments.include?(@config.environment)
53
+
54
+ RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
55
+ @poll_callback.call(data)
56
+ end
57
+ end
58
+
59
+ # @param [Airbrake::NoticeNotifier] notifier
60
+ # @return [void]
61
+ def add_filters(notifier)
62
+ return unless @config.root_directory
63
+
64
+ [
65
+ Airbrake::Filters::RootDirectoryFilter,
66
+ Airbrake::Filters::GitRevisionFilter,
67
+ Airbrake::Filters::GitRepositoryFilter,
68
+ Airbrake::Filters::GitLastCheckoutFilter,
69
+ ].each do |filter|
70
+ next if notifier.has_filter?(filter)
71
+
72
+ notifier.add_filter(filter.new(@config.root_directory))
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -29,7 +29,7 @@ module Airbrake
29
29
  return promise.reject(
30
30
  "the 'environment' option must be configured " \
31
31
  "with a Symbol (or String), but '#{config.environment.class}' was " \
32
- "provided: #{config.environment}"
32
+ "provided: #{config.environment}",
33
33
  )
34
34
  end
35
35
 
@@ -44,9 +44,13 @@ 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
- "current environment '#{config.environment}' is ignored"
53
+ "current environment '#{config.environment}' is ignored",
50
54
  )
51
55
  end
52
56
 
@@ -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
 
@@ -74,7 +80,7 @@ module Airbrake
74
80
  if config.ignore_environments.any? && config.environment.nil?
75
81
  config.logger.warn(
76
82
  "#{LOG_LABEL} the 'environment' option is not set, " \
77
- "'ignore_environments' has no effect"
83
+ "'ignore_environments' has no effect",
78
84
  )
79
85
  end
80
86
 
@@ -44,7 +44,14 @@ module Airbrake
44
44
  # @return [String] the host, which provides the API endpoint to which
45
45
  # exceptions should be sent
46
46
  # @api public
47
- attr_accessor :host
47
+ # @since v5.0.0
48
+ attr_accessor :error_host
49
+
50
+ # @return [String] the host, which provides the API endpoint to which
51
+ # APM data should be sent
52
+ # @api public
53
+ # @since v5.0.0
54
+ attr_accessor :apm_host
48
55
 
49
56
  # @return [String, Pathname] the working directory of your project
50
57
  # @api public
@@ -68,14 +75,14 @@ module Airbrake
68
75
  # @return [Array<String, Symbol, Regexp>] the keys, which should be
69
76
  # filtered
70
77
  # @api public
71
- # @since v1.2.0
72
- attr_accessor :blacklist_keys
78
+ # @since v4.15.0
79
+ attr_accessor :allowlist_keys
73
80
 
74
- # @return [Array<String, Symbol, Regexp>] the keys, which shouldn't be
81
+ # @return [Array<String, Symbol, Regexp>] the keys, which should be
75
82
  # filtered
76
83
  # @api public
77
- # @since v1.2.0
78
- attr_accessor :whitelist_keys
84
+ # @since v4.15.0
85
+ attr_accessor :blocklist_keys
79
86
 
80
87
  # @return [Boolean] true if the library should attach code hunks to each
81
88
  # frame in a backtrace, false otherwise
@@ -101,6 +108,36 @@ module Airbrake
101
108
  # @since v4.6.0
102
109
  attr_accessor :query_stats
103
110
 
111
+ # @return [Boolean] true if the library should send job/queue/worker stats
112
+ # to Airbrake, false otherwise
113
+ # @api public
114
+ # @since v4.12.0
115
+ attr_accessor :job_stats
116
+
117
+ # @return [Boolean] true if the library should send error reports to
118
+ # Airbrake, false otherwise
119
+ # @api public
120
+ # @since v5.0.0
121
+ attr_accessor :error_notifications
122
+
123
+ # @return [String] the host which should be used for fetching remote
124
+ # configuration options
125
+ # @api public
126
+ # @since v5.0.0
127
+ attr_accessor :remote_config_host
128
+
129
+ # @return [String] true if notifier should periodically fetch remote
130
+ # configuration, false otherwise
131
+ # @api public
132
+ # @since v5.2.0
133
+ attr_accessor :remote_config
134
+
135
+ # @return [Boolean] true if the library should keep a backlog of failed
136
+ # notices or APM events and retry them after an interval, false otherwise
137
+ # @api public
138
+ # @since v6.2.0
139
+ attr_accessor :backlog
140
+
104
141
  class << self
105
142
  # @return [Config]
106
143
  attr_writer :instance
@@ -113,44 +150,51 @@ module Airbrake
113
150
 
114
151
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
115
152
  # config
153
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
116
154
  def initialize(user_config = {})
117
155
  self.proxy = {}
118
156
  self.queue_size = 100
119
157
  self.workers = 1
120
158
  self.code_hunks = true
121
- self.logger = ::Logger.new(File::NULL)
159
+ self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
122
160
  self.project_id = user_config[:project_id]
123
161
  self.project_key = user_config[:project_key]
124
- self.host = 'https://api.airbrake.io'
162
+ self.error_host = self.apm_host = 'https://api.airbrake.io'
163
+ self.remote_config_host = 'https://notifier-configs.airbrake.io'
125
164
 
126
165
  self.ignore_environments = []
127
166
 
128
167
  self.timeout = user_config[:timeout]
129
168
 
130
- self.blacklist_keys = []
131
- self.whitelist_keys = []
169
+ self.blocklist_keys = []
170
+ self.allowlist_keys = []
132
171
 
133
172
  self.root_directory = File.realpath(
134
173
  (defined?(Bundler) && Bundler.root) ||
135
- Dir.pwd
174
+ Dir.pwd,
136
175
  )
137
176
 
138
177
  self.versions = {}
139
178
  self.performance_stats = true
140
179
  self.performance_stats_flush_period = 15
141
- self.query_stats = false
180
+ self.query_stats = true
181
+ self.job_stats = true
182
+ self.error_notifications = true
183
+ self.remote_config = true
184
+ self.backlog = true
142
185
 
143
186
  merge(user_config)
144
187
  end
188
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
145
189
 
146
- # The full URL to the Airbrake Notice API. Based on the +:host+ option.
190
+ # The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
147
191
  # @return [URI] the endpoint address
148
- def endpoint
149
- @endpoint ||=
192
+ def error_endpoint
193
+ @error_endpoint ||=
150
194
  begin
151
- self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
195
+ self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
152
196
  api = "api/v3/projects/#{project_id}/notices"
153
- URI.join(host, api)
197
+ URI.join(error_host, api)
154
198
  end
155
199
  end
156
200
 
@@ -206,18 +250,33 @@ module Airbrake
206
250
 
207
251
  # @return [Promise] resolved promise if neither of the performance options
208
252
  # reject it, false otherwise
209
- def check_performance_options(resource)
253
+ def check_performance_options(metric)
210
254
  promise = Airbrake::Promise.new
211
255
 
212
256
  if !performance_stats
213
257
  promise.reject("The Performance Stats feature is disabled")
214
- elsif resource.is_a?(Airbrake::Query) && !query_stats
258
+ elsif metric.is_a?(Airbrake::Query) && !query_stats
215
259
  promise.reject("The Query Stats feature is disabled")
260
+ elsif metric.is_a?(Airbrake::Queue) && !job_stats
261
+ promise.reject("The Job Stats feature is disabled")
216
262
  else
217
263
  promise
218
264
  end
219
265
  end
220
266
 
267
+ HOST_DEPRECATION_MSG = "**Airbrake: the 'host' option is deprecated. Use " \
268
+ "'error_host' instead".freeze
269
+
270
+ def host
271
+ logger.warn(HOST_DEPRECATION_MSG)
272
+ @error_host
273
+ end
274
+
275
+ def host=(value)
276
+ logger.warn(HOST_DEPRECATION_MSG)
277
+ @error_host = value
278
+ end
279
+
221
280
  private
222
281
 
223
282
  def set_option(option, value)