airbrake-ruby 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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