airbrake-ruby 4.1.0 → 5.0.0

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 (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +22 -96
  3. data/lib/airbrake-ruby/backtrace.rb +8 -7
  4. data/lib/airbrake-ruby/benchmark.rb +39 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +9 -3
  8. data/lib/airbrake-ruby/config.rb +76 -20
  9. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  10. data/lib/airbrake-ruby/file_cache.rb +6 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  18. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  19. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +30 -6
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -2
  25. data/lib/airbrake-ruby/grouppable.rb +12 -0
  26. data/lib/airbrake-ruby/ignorable.rb +1 -0
  27. data/lib/airbrake-ruby/inspectable.rb +2 -2
  28. data/lib/airbrake-ruby/loggable.rb +2 -2
  29. data/lib/airbrake-ruby/mergeable.rb +12 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +48 -0
  31. data/lib/airbrake-ruby/notice.rb +10 -20
  32. data/lib/airbrake-ruby/notice_notifier.rb +23 -42
  33. data/lib/airbrake-ruby/performance_breakdown.rb +52 -0
  34. data/lib/airbrake-ruby/performance_notifier.rb +126 -49
  35. data/lib/airbrake-ruby/promise.rb +1 -0
  36. data/lib/airbrake-ruby/query.rb +26 -11
  37. data/lib/airbrake-ruby/queue.rb +65 -0
  38. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  40. data/lib/airbrake-ruby/request.rb +20 -6
  41. data/lib/airbrake-ruby/stashable.rb +15 -0
  42. data/lib/airbrake-ruby/stat.rb +34 -24
  43. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  44. data/lib/airbrake-ruby/tdigest.rb +43 -58
  45. data/lib/airbrake-ruby/thread_pool.rb +138 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/lib/airbrake-ruby.rb +219 -53
  50. data/spec/airbrake_spec.rb +428 -9
  51. data/spec/async_sender_spec.rb +26 -110
  52. data/spec/backtrace_spec.rb +44 -44
  53. data/spec/benchmark_spec.rb +33 -0
  54. data/spec/code_hunk_spec.rb +11 -11
  55. data/spec/config/processor_spec.rb +209 -0
  56. data/spec/config/validator_spec.rb +23 -6
  57. data/spec/config_spec.rb +77 -7
  58. data/spec/deploy_notifier_spec.rb +2 -2
  59. data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
  60. data/spec/filter_chain_spec.rb +28 -1
  61. data/spec/filters/dependency_filter_spec.rb +1 -1
  62. data/spec/filters/gem_root_filter_spec.rb +9 -9
  63. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  64. data/spec/filters/git_repository_filter.rb +1 -1
  65. data/spec/filters/git_revision_filter_spec.rb +13 -11
  66. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  67. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  68. data/spec/filters/root_directory_filter_spec.rb +9 -9
  69. data/spec/filters/sql_filter_spec.rb +110 -55
  70. data/spec/filters/system_exit_filter_spec.rb +1 -1
  71. data/spec/filters/thread_filter_spec.rb +33 -31
  72. data/spec/fixtures/project_root/code.rb +9 -9
  73. data/spec/loggable_spec.rb +17 -0
  74. data/spec/monotonic_time_spec.rb +23 -0
  75. data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +19 -21
  76. data/spec/notice_notifier_spec.rb +20 -80
  77. data/spec/notice_spec.rb +9 -11
  78. data/spec/performance_breakdown_spec.rb +11 -0
  79. data/spec/performance_notifier_spec.rb +360 -85
  80. data/spec/query_spec.rb +11 -0
  81. data/spec/queue_spec.rb +18 -0
  82. data/spec/remote_settings/settings_data_spec.rb +365 -0
  83. data/spec/remote_settings_spec.rb +230 -0
  84. data/spec/request_spec.rb +9 -0
  85. data/spec/response_spec.rb +8 -8
  86. data/spec/spec_helper.rb +9 -13
  87. data/spec/stashable_spec.rb +23 -0
  88. data/spec/stat_spec.rb +17 -15
  89. data/spec/sync_sender_spec.rb +14 -12
  90. data/spec/tdigest_spec.rb +6 -6
  91. data/spec/thread_pool_spec.rb +187 -0
  92. data/spec/timed_trace_spec.rb +125 -0
  93. data/spec/truncator_spec.rb +12 -12
  94. metadata +55 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 25d739899ae0250aa01c5e81ab01d76bbcfac28d
4
- data.tar.gz: fca6e05b839805cb5d4b4171145c1dfeb4b4890b
2
+ SHA256:
3
+ metadata.gz: e98183a856071ce3d105c36adc9aa22a8ee7071b93958fd70fb673d2735b3977
4
+ data.tar.gz: 80464931a1362c4d845ffee737ed7aa1e46693136023b1f08006c49de98e62eb
5
5
  SHA512:
6
- metadata.gz: 13fd52e48ce2f8405640785323f2cc330d9364496b58ce0b8fb416b28baff5584a95b97ded5441ee47aca872af43cf7251ebb7cfd6d3b1660c523ee774e6a870
7
- data.tar.gz: d96f9bb73da48029a993bda925ec727a945177300721eb521f5966bac962f6d9ae2941cd191b4bc2d5497c07da7764c5d7c740bf5bb7fc0a30b61c50b263c08f
6
+ metadata.gz: 8c32fc631e3af6348a871094dde31452d4a6fdef834596933e179d2acdbbd4935751715007fa7c4dcfce3639ebbd55f61b15f06428015647e4cbcac590ef7aaf
7
+ data.tar.gz: 23968940c6f1dfb500be5ab479c1d524cdc9f80e40808b4ff08d7f4b03c97baad603d97504c58a5300ddfa7ec9b1a4b5bd44a24a6a4a15776a82778ef2a0c324
@@ -1,7 +1,5 @@
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
@@ -9,123 +7,51 @@ module Airbrake
9
7
  class AsyncSender
10
8
  include Loggable
11
9
 
12
- # @return [ThreadGroup] the list of workers
13
- # @note This is exposed for eaiser unit testing
14
- # @since v4.0.0
15
- attr_reader :workers
16
-
17
- # @return [Array<[Airbrake::Notice,Airbrake::Promise]>] the list of unsent
18
- # payload
19
- # @note This is exposed for eaiser unit testing
20
- # @since v4.0.0
21
- attr_reader :unsent
22
-
23
- def initialize
10
+ def initialize(method = :post)
24
11
  @config = Airbrake::Config.instance
25
- @unsent = SizedQueue.new(Airbrake::Config.instance.queue_size)
26
- @sender = SyncSender.new
27
- @closed = false
28
- @workers = ThreadGroup.new
29
- @mutex = Mutex.new
30
- @pid = nil
12
+ @method = method
31
13
  end
32
14
 
33
15
  # Asynchronously sends a notice to Airbrake.
34
16
  #
35
- # @param [Airbrake::Notice] notice A notice that was generated by the
36
- # library
17
+ # @param [Hash] payload Whatever needs to be sent
37
18
  # @return [Airbrake::Promise]
38
- def send(notice, promise)
39
- return will_not_deliver(notice) if @unsent.size >= @unsent.max
19
+ def send(payload, promise, endpoint = @config.error_endpoint)
20
+ unless thread_pool << [payload, promise, endpoint]
21
+ return promise.reject(
22
+ "AsyncSender has reached its capacity of #{@config.queue_size}",
23
+ )
24
+ end
40
25
 
41
- @unsent << [notice, promise]
42
26
  promise
43
27
  end
44
28
 
45
- # Closes the instance making it a no-op (it shut downs all worker
46
- # threads). Before closing, waits on all unsent notices to be sent.
47
- #
48
29
  # @return [void]
49
- # @raise [Airbrake::Error] when invoked more than one time
50
30
  def close
51
- threads = @mutex.synchronize do
52
- raise Airbrake::Error, 'attempted to close already closed sender' if closed?
53
-
54
- unless @unsent.empty?
55
- msg = "#{LOG_LABEL} waiting to send #{@unsent.size} unsent notice(s)..."
56
- logger.debug(msg + ' (Ctrl-C to abort)')
57
- end
58
-
59
- @config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
60
- @closed = true
61
- @workers.list.dup
62
- end
63
-
64
- threads.each(&:join)
65
- logger.debug("#{LOG_LABEL} closed")
31
+ thread_pool.close
66
32
  end
67
33
 
68
- # Checks whether the sender is closed and thus usable.
69
34
  # @return [Boolean]
70
35
  def closed?
71
- @closed
36
+ thread_pool.closed?
72
37
  end
73
38
 
74
- # Checks if an active sender has any workers. A sender doesn't have any
75
- # workers only in two cases: when it was closed or when all workers
76
- # crashed. An *active* sender doesn't have any workers only when something
77
- # went wrong.
78
- #
79
- # Workers are expected to crash when you +fork+ the process the workers are
80
- # living in. In this case we detect a +fork+ and try to revive them here.
81
- #
82
- # Another possible scenario that crashes workers is when you close the
83
- # instance on +at_exit+, but some other +at_exit+ hook prevents the process
84
- # from exiting.
85
- #
86
- # @return [Boolean] true if an instance wasn't closed, but has no workers
87
- # @see https://goo.gl/oydz8h Example of at_exit that prevents exit
39
+ # @return [Boolean]
88
40
  def has_workers?
89
- @mutex.synchronize do
90
- return false if @closed
91
-
92
- if @pid != Process.pid && @workers.list.empty?
93
- @pid = Process.pid
94
- spawn_workers
95
- end
96
-
97
- !@closed && @workers.list.any?
98
- end
41
+ thread_pool.has_workers?
99
42
  end
100
43
 
101
44
  private
102
45
 
103
- def spawn_workers
104
- @workers = ThreadGroup.new
105
- @config.workers.times { @workers.add(spawn_worker) }
106
- @workers.enclose
107
- end
108
-
109
- def spawn_worker
110
- Thread.new do
111
- while (message = @unsent.pop)
112
- break if message.first == :stop
113
- @sender.send(*message)
114
- end
115
- end
116
- end
117
-
118
- def will_not_deliver(notice)
119
- backtrace = notice[:errors][0][:backtrace].map do |line|
120
- "#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
46
+ def thread_pool
47
+ @thread_pool ||= begin
48
+ sender = SyncSender.new(@method)
49
+ ThreadPool.new(
50
+ worker_size: @config.workers,
51
+ queue_size: @config.queue_size,
52
+ block: proc { |args| sender.send(*args) },
53
+ )
121
54
  end
122
- logger.error(
123
- "#{LOG_LABEL} AsyncSender has reached its capacity of " \
124
- "#{@unsent.max} and the following notice will not be delivered " \
125
- "Error: #{notice[:errors][0][:type]} - #{notice[:errors][0][:message]}\n" \
126
- "Backtrace: \n" + backtrace.join("\n")
127
- )
128
- nil
129
55
  end
130
56
  end
131
57
  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
@@ -0,0 +1,39 @@
1
+ module Airbrake
2
+ # Benchmark benchmarks Ruby code.
3
+ #
4
+ # @since v4.2.4
5
+ # @api public
6
+ class Benchmark
7
+ # Measures monotonic time for the given operation.
8
+ #
9
+ # @yieldreturn [void]
10
+ def self.measure
11
+ benchmark = new
12
+
13
+ yield
14
+
15
+ benchmark.stop
16
+ benchmark.duration
17
+ end
18
+
19
+ # @return [Float]
20
+ attr_reader :duration
21
+
22
+ # @since v4.3.0
23
+ def initialize
24
+ @start = MonotonicTime.time_in_ms
25
+ @duration = 0.0
26
+ end
27
+
28
+ # Stops the benchmark and stores `duration`.
29
+ #
30
+ # @since v4.3.0
31
+ # @return [Boolean] true for the first invocation, false in all other cases
32
+ def stop
33
+ return false if @duration > 0.0
34
+
35
+ @duration = MonotonicTime.time_in_ms - @start
36
+ true
37
+ end
38
+ end
39
+ end
@@ -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,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
@@ -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
 
@@ -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
@@ -83,20 +94,42 @@ module Airbrake
83
94
  # @since v2.5.0
84
95
  attr_accessor :code_hunks
85
96
 
86
- # @return [Boolean] true if the library should send performance stats
87
- # information to Airbrake (routes, SQL queries), false otherwise
97
+ # @return [Boolean] true if the library should send route performance stats
98
+ # to Airbrake, false otherwise
88
99
  # @api public
89
100
  # @since v3.2.0
90
101
  attr_accessor :performance_stats
91
102
 
92
103
  # @return [Integer] how many seconds to wait before sending collected route
93
104
  # stats
94
- # @api public
105
+ # @api private
95
106
  # @since v3.2.0
96
107
  attr_accessor :performance_stats_flush_period
97
108
 
109
+ # @return [Boolean] true if the library should send SQL stats to Airbrake,
110
+ # false otherwise
111
+ # @api public
112
+ # @since v4.6.0
113
+ attr_accessor :query_stats
114
+
115
+ # @return [Boolean] true if the library should send job/queue/worker stats
116
+ # to Airbrake, false otherwise
117
+ # @api public
118
+ # @since v4.12.0
119
+ attr_accessor :job_stats
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
+
98
131
  class << self
99
- # @param [Config] new_instance
132
+ # @return [Config]
100
133
  attr_writer :instance
101
134
 
102
135
  # @return [Config]
@@ -107,43 +140,50 @@ module Airbrake
107
140
 
108
141
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
109
142
  # config
143
+ # rubocop:disable Metrics/AbcSize
110
144
  def initialize(user_config = {})
111
145
  self.proxy = {}
112
146
  self.queue_size = 100
113
147
  self.workers = 1
114
148
  self.code_hunks = true
115
- self.logger = ::Logger.new(File::NULL)
149
+ self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
116
150
  self.project_id = user_config[:project_id]
117
151
  self.project_key = user_config[:project_key]
118
- 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'
119
155
 
120
156
  self.ignore_environments = []
121
157
 
122
158
  self.timeout = user_config[:timeout]
123
159
 
124
- self.blacklist_keys = []
125
- self.whitelist_keys = []
160
+ self.blocklist_keys = []
161
+ self.allowlist_keys = []
126
162
 
127
163
  self.root_directory = File.realpath(
128
164
  (defined?(Bundler) && Bundler.root) ||
129
- Dir.pwd
165
+ Dir.pwd,
130
166
  )
131
167
 
132
168
  self.versions = {}
133
- self.performance_stats = false
169
+ self.performance_stats = true
134
170
  self.performance_stats_flush_period = 15
171
+ self.query_stats = true
172
+ self.job_stats = true
173
+ self.error_notifications = true
135
174
 
136
175
  merge(user_config)
137
176
  end
177
+ # rubocop:enable Metrics/AbcSize
138
178
 
139
- # 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.
140
180
  # @return [URI] the endpoint address
141
- def endpoint
142
- @endpoint ||=
181
+ def error_endpoint
182
+ @error_endpoint ||=
143
183
  begin
144
- self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
184
+ self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
145
185
  api = "api/v3/projects/#{project_id}/notices"
146
- URI.join(host, api)
186
+ URI.join(error_host, api)
147
187
  end
148
188
  end
149
189
 
@@ -197,6 +237,22 @@ module Airbrake
197
237
  check_notify_ability
198
238
  end
199
239
 
240
+ # @return [Promise] resolved promise if neither of the performance options
241
+ # reject it, false otherwise
242
+ def check_performance_options(resource)
243
+ promise = Airbrake::Promise.new
244
+
245
+ if !performance_stats
246
+ promise.reject("The Performance Stats feature is disabled")
247
+ elsif resource.is_a?(Airbrake::Query) && !query_stats
248
+ promise.reject("The Query Stats feature is disabled")
249
+ elsif resource.is_a?(Airbrake::Queue) && !job_stats
250
+ promise.reject("The Job Stats feature is disabled")
251
+ else
252
+ promise
253
+ end
254
+ end
255
+
200
256
  private
201
257
 
202
258
  def set_option(option, value)
@@ -27,7 +27,7 @@ module Airbrake
27
27
  @sender.send(
28
28
  deploy_info,
29
29
  promise,
30
- URI.join(@config.host, "api/v4/projects/#{@config.project_id}/deploys")
30
+ URI.join(@config.host, "api/v4/projects/#{@config.project_id}/deploys"),
31
31
  )
32
32
 
33
33
  promise
@@ -40,6 +40,12 @@ module Airbrake
40
40
  data.empty?
41
41
  end
42
42
 
43
+ # @since 4.7.0
44
+ # @return [void]
45
+ def self.reset
46
+ @data = {}
47
+ end
48
+
43
49
  def self.data
44
50
  @data ||= {}
45
51
  end
@@ -70,13 +70,14 @@ 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
76
77
 
77
78
  # @return [String] customized inspect to lessen the amount of clutter
78
79
  def inspect
79
- @filters.map(&:class).to_s
80
+ filter_classes.to_s
80
81
  end
81
82
 
82
83
  # @return [String] {#inspect} for PrettyPrint
@@ -91,5 +92,19 @@ module Airbrake
91
92
  end
92
93
  q.text(']')
93
94
  end
95
+
96
+ # @param [Class] filter_class
97
+ # @return [Boolean] true if the current chain has an instance of the given
98
+ # class, false otherwise
99
+ # @since v4.14.0
100
+ def includes?(filter_class)
101
+ filter_classes.include?(filter_class)
102
+ end
103
+
104
+ private
105
+
106
+ def filter_classes
107
+ @filters.map(&:class)
108
+ end
94
109
  end
95
110
  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
@@ -22,13 +22,13 @@ module Airbrake
22
22
  attributes = exception.to_airbrake
23
23
  rescue StandardError => ex
24
24
  logger.error(
25
- "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
25
+ "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}",
26
26
  )
27
27
  end
28
28
 
29
29
  unless attributes.is_a?(Hash)
30
30
  logger.error(
31
- "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
31
+ "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}",
32
32
  )
33
33
  return
34
34
  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