airbrake-ruby 3.2.2-java

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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +554 -0
  3. data/lib/airbrake-ruby/async_sender.rb +119 -0
  4. data/lib/airbrake-ruby/backtrace.rb +194 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +53 -0
  6. data/lib/airbrake-ruby/config.rb +238 -0
  7. data/lib/airbrake-ruby/config/validator.rb +63 -0
  8. data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
  9. data/lib/airbrake-ruby/file_cache.rb +48 -0
  10. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  11. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +90 -0
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +42 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  18. data/lib/airbrake-ruby/filters/keys_blacklist.rb +50 -0
  19. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  20. data/lib/airbrake-ruby/filters/keys_whitelist.rb +49 -0
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  25. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  26. data/lib/airbrake-ruby/ignorable.rb +44 -0
  27. data/lib/airbrake-ruby/nested_exception.rb +39 -0
  28. data/lib/airbrake-ruby/notice.rb +165 -0
  29. data/lib/airbrake-ruby/notice_notifier.rb +228 -0
  30. data/lib/airbrake-ruby/performance_notifier.rb +161 -0
  31. data/lib/airbrake-ruby/promise.rb +99 -0
  32. data/lib/airbrake-ruby/response.rb +71 -0
  33. data/lib/airbrake-ruby/stat.rb +56 -0
  34. data/lib/airbrake-ruby/sync_sender.rb +111 -0
  35. data/lib/airbrake-ruby/tdigest.rb +393 -0
  36. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  37. data/lib/airbrake-ruby/truncator.rb +115 -0
  38. data/lib/airbrake-ruby/version.rb +6 -0
  39. data/spec/airbrake_spec.rb +171 -0
  40. data/spec/async_sender_spec.rb +154 -0
  41. data/spec/backtrace_spec.rb +438 -0
  42. data/spec/code_hunk_spec.rb +118 -0
  43. data/spec/config/validator_spec.rb +189 -0
  44. data/spec/config_spec.rb +281 -0
  45. data/spec/deploy_notifier_spec.rb +41 -0
  46. data/spec/file_cache.rb +36 -0
  47. data/spec/filter_chain_spec.rb +83 -0
  48. data/spec/filters/context_filter_spec.rb +25 -0
  49. data/spec/filters/dependency_filter_spec.rb +14 -0
  50. data/spec/filters/exception_attributes_filter_spec.rb +63 -0
  51. data/spec/filters/gem_root_filter_spec.rb +44 -0
  52. data/spec/filters/git_last_checkout_filter_spec.rb +48 -0
  53. data/spec/filters/git_repository_filter.rb +53 -0
  54. data/spec/filters/git_revision_filter_spec.rb +126 -0
  55. data/spec/filters/keys_blacklist_spec.rb +236 -0
  56. data/spec/filters/keys_whitelist_spec.rb +205 -0
  57. data/spec/filters/root_directory_filter_spec.rb +42 -0
  58. data/spec/filters/sql_filter_spec.rb +219 -0
  59. data/spec/filters/system_exit_filter_spec.rb +14 -0
  60. data/spec/filters/thread_filter_spec.rb +279 -0
  61. data/spec/fixtures/notroot.txt +7 -0
  62. data/spec/fixtures/project_root/code.rb +221 -0
  63. data/spec/fixtures/project_root/empty_file.rb +0 -0
  64. data/spec/fixtures/project_root/long_line.txt +1 -0
  65. data/spec/fixtures/project_root/short_file.rb +3 -0
  66. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  67. data/spec/helpers.rb +9 -0
  68. data/spec/ignorable_spec.rb +14 -0
  69. data/spec/nested_exception_spec.rb +75 -0
  70. data/spec/notice_notifier_spec.rb +436 -0
  71. data/spec/notice_notifier_spec/options_spec.rb +266 -0
  72. data/spec/notice_spec.rb +297 -0
  73. data/spec/performance_notifier_spec.rb +287 -0
  74. data/spec/promise_spec.rb +165 -0
  75. data/spec/response_spec.rb +82 -0
  76. data/spec/spec_helper.rb +102 -0
  77. data/spec/stat_spec.rb +35 -0
  78. data/spec/sync_sender_spec.rb +140 -0
  79. data/spec/tdigest_spec.rb +230 -0
  80. data/spec/time_truncate_spec.rb +13 -0
  81. data/spec/truncator_spec.rb +238 -0
  82. metadata +278 -0
@@ -0,0 +1,23 @@
1
+ module Airbrake
2
+ module Filters
3
+ # Skip over SystemExit exceptions, because they're just noise.
4
+ # @api private
5
+ class SystemExitFilter
6
+ # @return [String]
7
+ SYSTEM_EXIT_TYPE = 'SystemExit'.freeze
8
+
9
+ # @return [Integer]
10
+ attr_reader :weight
11
+
12
+ def initialize
13
+ @weight = 130
14
+ end
15
+
16
+ # @macro call_filter
17
+ def call(notice)
18
+ return if notice[:errors].none? { |error| error[:type] == SYSTEM_EXIT_TYPE }
19
+ notice.ignore!
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,92 @@
1
+ module Airbrake
2
+ module Filters
3
+ # Attaches thread & fiber local variables along with general thread
4
+ # information.
5
+ # @api private
6
+ class ThreadFilter
7
+ # @return [Integer]
8
+ attr_reader :weight
9
+
10
+ # @return [Array<Class>] the list of classes that can be safely converted
11
+ # to JSON
12
+ SAFE_CLASSES = [
13
+ NilClass,
14
+ TrueClass,
15
+ FalseClass,
16
+ String,
17
+ Symbol,
18
+ Regexp,
19
+ Numeric
20
+ ].freeze
21
+
22
+ # Variables starting with this prefix are not attached to a notice.
23
+ # @see https://github.com/airbrake/airbrake-ruby/issues/229
24
+ # @return [String]
25
+ IGNORE_PREFIX = '_'.freeze
26
+
27
+ def initialize
28
+ @weight = 110
29
+ end
30
+
31
+ # @macro call_filter
32
+ def call(notice)
33
+ th = Thread.current
34
+ thread_info = {}
35
+
36
+ if (vars = thread_variables(th)).any?
37
+ thread_info[:thread_variables] = vars
38
+ end
39
+
40
+ if (vars = fiber_variables(th)).any?
41
+ thread_info[:fiber_variables] = vars
42
+ end
43
+
44
+ # Present in Ruby 2.3+.
45
+ if th.respond_to?(:name) && (name = th.name)
46
+ thread_info[:name] = name
47
+ end
48
+
49
+ add_thread_info(th, thread_info)
50
+
51
+ notice[:params][:thread] = thread_info
52
+ end
53
+
54
+ private
55
+
56
+ def thread_variables(th)
57
+ th.thread_variables.map.with_object({}) do |var, h|
58
+ next if var.to_s.start_with?(IGNORE_PREFIX)
59
+ h[var] = sanitize_value(th.thread_variable_get(var))
60
+ end
61
+ end
62
+
63
+ def fiber_variables(th)
64
+ th.keys.map.with_object({}) do |key, h|
65
+ next if key.to_s.start_with?(IGNORE_PREFIX)
66
+ h[key] = sanitize_value(th[key])
67
+ end
68
+ end
69
+
70
+ def add_thread_info(th, thread_info)
71
+ thread_info[:self] = th.inspect
72
+ thread_info[:group] = th.group.list.map(&:inspect)
73
+ thread_info[:priority] = th.priority
74
+
75
+ thread_info[:safe_level] = th.safe_level unless Airbrake::JRUBY
76
+ end
77
+
78
+ def sanitize_value(value)
79
+ return value if SAFE_CLASSES.any? { |klass| value.is_a?(klass) }
80
+
81
+ case value
82
+ when Array
83
+ value = value.map { |elem| sanitize_value(elem) }
84
+ when Hash
85
+ Hash[value.map { |k, v| [k, sanitize_value(v)] }]
86
+ else
87
+ value.to_s
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,37 @@
1
+ module Airbrake
2
+ # HashKeyable allows instances of the class to be used as a Hash key in a
3
+ # consistent manner.
4
+ #
5
+ # The class that includes it must implement *to_h*, which defines properties
6
+ # that all of the instances must share in order to produce the same {#hash}.
7
+ #
8
+ # @example
9
+ # class C
10
+ # include Airbrake::HashKeyable
11
+ #
12
+ # def initialize(key)
13
+ # @key = key
14
+ # end
15
+ #
16
+ # def to_h
17
+ # { 'key' => @key }
18
+ # end
19
+ # end
20
+ #
21
+ # h = {}
22
+ # h[C.new('key1')] = 1
23
+ # h[C.new('key1')] #=> 1
24
+ # h[C.new('key2')] #=> nil
25
+ module HashKeyable
26
+ # @param [Object] other
27
+ # @return [Boolean]
28
+ def eql?(other)
29
+ other.is_a?(self.class) && other.hash == hash
30
+ end
31
+
32
+ # @return [Integer]
33
+ def hash
34
+ to_h.hash
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ module Airbrake
2
+ # Ignorable contains methods that allow the includee to be ignored.
3
+ #
4
+ # @example
5
+ # class A
6
+ # include Airbrake::Ignorable
7
+ # end
8
+ #
9
+ # a = A.new
10
+ # a.ignore!
11
+ # a.ignored? #=> true
12
+ #
13
+ # @since v3.2.0
14
+ # @api private
15
+ module Ignorable
16
+ attr_accessor :ignored
17
+
18
+ # Checks whether the instance was ignored.
19
+ # @return [Boolean]
20
+ # @see #ignore!
21
+ # rubocop:disable Style/DoubleNegation
22
+ def ignored?
23
+ !!ignored
24
+ end
25
+ # rubocop:enable Style/DoubleNegation
26
+
27
+ # Ignores an instance. Ignored instances must never reach the Airbrake
28
+ # dashboard.
29
+ # @return [void]
30
+ # @see #ignored?
31
+ def ignore!
32
+ self.ignored = true
33
+ end
34
+
35
+ private
36
+
37
+ # A method that is meant to be used as a guard.
38
+ # @raise [Airbrake::Error] when instance is ignored
39
+ def raise_if_ignored
40
+ return unless ignored?
41
+ raise Airbrake::Error, "cannot access ignored #{self.class}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ module Airbrake
2
+ # A class that is capable of unwinding nested exceptions and representing them
3
+ # as JSON-like hash.
4
+ #
5
+ # @api private
6
+ # @since v1.0.4
7
+ class NestedException
8
+ # @return [Integer] the maximum number of nested exceptions that a notice
9
+ # can unwrap. Exceptions that have a longer cause chain will be ignored
10
+ MAX_NESTED_EXCEPTIONS = 3
11
+
12
+ def initialize(config, exception)
13
+ @config = config
14
+ @exception = exception
15
+ end
16
+
17
+ def as_json
18
+ unwind_exceptions.map do |exception|
19
+ { type: exception.class.name,
20
+ message: exception.message,
21
+ backtrace: Backtrace.parse(@config, exception) }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def unwind_exceptions
28
+ exception_list = []
29
+ exception = @exception
30
+
31
+ while exception && exception_list.size < MAX_NESTED_EXCEPTIONS
32
+ exception_list << exception
33
+ exception = (exception.cause if exception.respond_to?(:cause))
34
+ end
35
+
36
+ exception_list
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,165 @@
1
+ module Airbrake
2
+ # Represents a chunk of information that is meant to be either sent to
3
+ # Airbrake or ignored completely.
4
+ #
5
+ # @since v1.0.0
6
+ class Notice
7
+ # @return [Hash{Symbol=>String}] the information about the notifier library
8
+ NOTIFIER = {
9
+ name: 'airbrake-ruby'.freeze,
10
+ version: Airbrake::AIRBRAKE_RUBY_VERSION,
11
+ url: 'https://github.com/airbrake/airbrake-ruby'.freeze
12
+ }.freeze
13
+
14
+ # @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
15
+ # Context tab in the dashboard
16
+ CONTEXT = {
17
+ os: RUBY_PLATFORM,
18
+ language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
19
+ notifier: NOTIFIER
20
+ }.freeze
21
+
22
+ # @return [Integer] the maxium size of the JSON payload in bytes
23
+ MAX_NOTICE_SIZE = 64000
24
+
25
+ # @return [Integer] the maximum size of hashes, arrays and strings in the
26
+ # notice.
27
+ PAYLOAD_MAX_SIZE = 10000
28
+
29
+ # @return [Array<StandardError>] the list of possible exceptions that might
30
+ # be raised when an object is converted to JSON
31
+ JSON_EXCEPTIONS = [
32
+ IOError,
33
+ NotImplementedError,
34
+ JSON::GeneratorError,
35
+ Encoding::UndefinedConversionError
36
+ ].freeze
37
+
38
+ # @return [Array<Symbol>] the list of keys that can be be overwritten with
39
+ # {Airbrake::Notice#[]=}
40
+ WRITABLE_KEYS = %i[notifier context environment session params].freeze
41
+
42
+ # @return [Array<Symbol>] parts of a Notice's payload that can be modified
43
+ # by the truncator
44
+ TRUNCATABLE_KEYS = %i[errors environment session params].freeze
45
+
46
+ # @return [String] the name of the host machine
47
+ HOSTNAME = Socket.gethostname.freeze
48
+
49
+ # @return [String]
50
+ DEFAULT_SEVERITY = 'error'.freeze
51
+
52
+ include Ignorable
53
+
54
+ # @since v1.7.0
55
+ # @return [Hash{Symbol=>Object}] the hash with arbitrary objects to be used
56
+ # in filters
57
+ attr_reader :stash
58
+
59
+ # @api private
60
+ def initialize(config, exception, params = {})
61
+ @config = config
62
+
63
+ @payload = {
64
+ errors: NestedException.new(config, exception).as_json,
65
+ context: context,
66
+ environment: {
67
+ program_name: $PROGRAM_NAME
68
+ },
69
+ session: {},
70
+ params: params
71
+ }
72
+ @stash = { exception: exception }
73
+ @truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
74
+ end
75
+
76
+ # Converts the notice to JSON. Calls +to_json+ on each object inside
77
+ # notice's payload. Truncates notices, JSON representation of which is
78
+ # bigger than {MAX_NOTICE_SIZE}.
79
+ #
80
+ # @return [Hash{String=>String}, nil]
81
+ # @api private
82
+ def to_json
83
+ loop do
84
+ begin
85
+ json = @payload.to_json
86
+ rescue *JSON_EXCEPTIONS => ex
87
+ @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")
88
+ else
89
+ return json if json && json.bytesize <= MAX_NOTICE_SIZE
90
+ end
91
+
92
+ break if truncate == 0
93
+ end
94
+ end
95
+
96
+ # Reads a value from notice's payload.
97
+ #
98
+ # @return [Object]
99
+ # @raise [Airbrake::Error] if the notice is ignored
100
+ def [](key)
101
+ raise_if_ignored
102
+ @payload[key]
103
+ end
104
+
105
+ # Writes a value to the payload hash. Restricts unrecognized writes.
106
+ #
107
+ # @example
108
+ # notice[:params][:my_param] = 'foobar'
109
+ #
110
+ # @return [void]
111
+ # @raise [Airbrake::Error] if the notice is ignored
112
+ # @raise [Airbrake::Error] if the +key+ is not recognized
113
+ # @raise [Airbrake::Error] if the root value is not a Hash
114
+ def []=(key, value)
115
+ raise_if_ignored
116
+
117
+ unless WRITABLE_KEYS.include?(key)
118
+ raise Airbrake::Error,
119
+ ":#{key} is not recognized among #{WRITABLE_KEYS}"
120
+ end
121
+
122
+ unless value.respond_to?(:to_hash)
123
+ raise Airbrake::Error, "Got #{value.class} value, wanted a Hash"
124
+ end
125
+
126
+ @payload[key] = value.to_hash
127
+ end
128
+
129
+ private
130
+
131
+ def context
132
+ {
133
+ version: @config.app_version,
134
+ versions: @config.versions,
135
+ # We ensure that root_directory is always a String, so it can always be
136
+ # converted to JSON in a predictable manner (when it's a Pathname and in
137
+ # Rails environment, it converts to unexpected JSON).
138
+ rootDirectory: @config.root_directory.to_s,
139
+ environment: @config.environment,
140
+
141
+ # Make sure we always send hostname.
142
+ hostname: HOSTNAME,
143
+
144
+ severity: DEFAULT_SEVERITY
145
+ }.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
146
+ end
147
+
148
+ def truncate
149
+ TRUNCATABLE_KEYS.each do |key|
150
+ @payload[key] = @truncator.truncate(@payload[key])
151
+ end
152
+
153
+ new_max_size = @truncator.reduce_max_size
154
+ if new_max_size == 0
155
+ @config.logger.error(
156
+ "#{LOG_LABEL} truncation failed. File an issue at " \
157
+ "https://github.com/airbrake/airbrake-ruby " \
158
+ "and attach the following payload: #{@payload}"
159
+ )
160
+ end
161
+
162
+ new_max_size
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,228 @@
1
+ module Airbrake
2
+ # NoticeNotifier is reponsible for sending notices to Airbrake. It supports
3
+ # synchronous and asynchronous delivery.
4
+ #
5
+ # @see Airbrake::Config The list of options
6
+ # @since v1.0.0
7
+ # @api public
8
+ # rubocop:disable Metrics/ClassLength
9
+ class NoticeNotifier
10
+ # @return [String] the label to be prepended to the log output
11
+ LOG_LABEL = '**Airbrake:'.freeze
12
+
13
+ # @return [String] inspect output template
14
+ INSPECT_TEMPLATE =
15
+ "#<#{self}:0x%<id>s project_id=\"%<project_id>s\" " \
16
+ "project_key=\"%<project_key>s\" " \
17
+ "host=\"%<host>s\" filter_chain=%<filter_chain>s>".freeze
18
+
19
+ # @return [Array<Class>] filters to be executed first
20
+ DEFAULT_FILTERS = [
21
+ Airbrake::Filters::SystemExitFilter,
22
+ Airbrake::Filters::GemRootFilter
23
+
24
+ # Optional filters (must be included by users):
25
+ # Airbrake::Filters::ThreadFilter
26
+ ].freeze
27
+
28
+ # Creates a new notice notifier with the given config options.
29
+ #
30
+ # @example
31
+ # config = Airbrake::Config.new
32
+ # config.project_id = 123
33
+ # config.project_key = '321'
34
+ # notice_notifier = Airbrake::NoticeNotifier.new(config)
35
+ #
36
+ # @param [Airbrake::Config] config
37
+ def initialize(config, perf_notifier = nil)
38
+ @config =
39
+ if config.is_a?(Config)
40
+ config
41
+ else
42
+ loc = caller_locations(1..1).first
43
+ signature = "#{self.class.name}##{__method__}"
44
+ warn(
45
+ "#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
46
+ 'is deprecated. Pass `Airbrake::Config` instead'
47
+ )
48
+ Config.new(config)
49
+ end
50
+
51
+ @context = {}
52
+ @filter_chain = FilterChain.new
53
+ @async_sender = AsyncSender.new(@config)
54
+ @sync_sender = SyncSender.new(@config)
55
+ @perf_notifier = perf_notifier
56
+
57
+ add_default_filters
58
+ end
59
+
60
+ # @macro see_public_api_method
61
+ def notify(exception, params = {}, &block)
62
+ send_notice(exception, params, default_sender, &block)
63
+ end
64
+
65
+ # @macro see_public_api_method
66
+ def notify_sync(exception, params = {}, &block)
67
+ send_notice(exception, params, @sync_sender, &block).value
68
+ end
69
+
70
+ # @deprecated Update the airbrake gem to v8.1.0 or higher
71
+ def notify_request(request_info, promise = Promise.new)
72
+ @config.logger.info(
73
+ "#{LOG_LABEL} #{self.class}##{__method__} is deprecated. Update " \
74
+ 'the airbrake gem to v8.1.0 or higher'
75
+ )
76
+ @perf_notifier.notify(Request.new(request_info), promise)
77
+ end
78
+
79
+ # @macro see_public_api_method
80
+ def add_filter(filter = nil, &block)
81
+ @filter_chain.add_filter(block_given? ? block : filter)
82
+ end
83
+
84
+ # @macro see_public_api_method
85
+ def delete_filter(filter_class)
86
+ @filter_chain.delete_filter(filter_class)
87
+ end
88
+
89
+ # @macro see_public_api_method
90
+ def build_notice(exception, params = {})
91
+ if @async_sender.closed?
92
+ raise Airbrake::Error,
93
+ "attempted to build #{exception} with closed Airbrake instance"
94
+ end
95
+
96
+ if exception.is_a?(Airbrake::Notice)
97
+ exception[:params].merge!(params)
98
+ exception
99
+ else
100
+ Notice.new(@config, convert_to_exception(exception), params.dup)
101
+ end
102
+ end
103
+
104
+ # @macro see_public_api_method
105
+ def close
106
+ @async_sender.close
107
+ end
108
+
109
+ # @macro see_public_api_method
110
+ def configured?
111
+ @config.valid?
112
+ end
113
+
114
+ # @macro see_public_api_method
115
+ def merge_context(context)
116
+ @context.merge!(context)
117
+ end
118
+
119
+ # @return [String] customized inspect to lessen the amount of clutter
120
+ def inspect
121
+ format(
122
+ INSPECT_TEMPLATE,
123
+ id: (object_id << 1).to_s(16).rjust(16, '0'),
124
+ project_id: @config.project_id,
125
+ project_key: @config.project_key,
126
+ host: @config.host,
127
+ filter_chain: @filter_chain.inspect
128
+ )
129
+ end
130
+
131
+ # @return [String] {#inspect} for PrettyPrint
132
+ def pretty_print(q)
133
+ q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
134
+ q.text(
135
+ "project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
136
+ "host=\"#{@config.host}\" filter_chain="
137
+ )
138
+ q.pp(@filter_chain)
139
+ q.text('>')
140
+ end
141
+
142
+ private
143
+
144
+ def convert_to_exception(ex)
145
+ if ex.is_a?(Exception) || Backtrace.java_exception?(ex)
146
+ # Manually created exceptions don't have backtraces, so we create a fake
147
+ # one, whose first frame points to the place where Airbrake was called
148
+ # (normally via `notify`).
149
+ ex.set_backtrace(clean_backtrace) unless ex.backtrace
150
+ return ex
151
+ end
152
+
153
+ e = RuntimeError.new(ex.to_s)
154
+ e.set_backtrace(clean_backtrace)
155
+ e
156
+ end
157
+
158
+ def send_notice(exception, params, sender)
159
+ promise = Airbrake::Promise.new
160
+ if @config.ignored_environment?
161
+ return promise.reject("The '#{@config.environment}' environment is ignored")
162
+ end
163
+
164
+ notice = build_notice(exception, params)
165
+ yield notice if block_given?
166
+ @filter_chain.refine(notice)
167
+
168
+ return promise.reject("#{notice} was marked as ignored") if notice.ignored?
169
+
170
+ sender.send(notice, promise)
171
+ end
172
+
173
+ def default_sender
174
+ return @async_sender if @async_sender.has_workers?
175
+
176
+ @config.logger.warn(
177
+ "#{LOG_LABEL} falling back to sync delivery because there are no " \
178
+ "running async workers"
179
+ )
180
+ @sync_sender
181
+ end
182
+
183
+ def clean_backtrace
184
+ caller_copy = Kernel.caller
185
+ clean_bt = caller_copy.drop_while { |frame| frame.include?('/lib/airbrake') }
186
+
187
+ # If true, then it's likely an internal library error. In this case return
188
+ # at least some backtrace to simplify debugging.
189
+ return caller_copy if clean_bt.empty?
190
+ clean_bt
191
+ end
192
+
193
+ # rubocop:disable Metrics/AbcSize
194
+ def add_default_filters
195
+ DEFAULT_FILTERS.each { |f| add_filter(f.new) }
196
+
197
+ if (whitelist_keys = @config.whitelist_keys).any?
198
+ add_filter(
199
+ Airbrake::Filters::KeysWhitelist.new(@config.logger, whitelist_keys)
200
+ )
201
+ end
202
+
203
+ if (blacklist_keys = @config.blacklist_keys).any?
204
+ add_filter(
205
+ Airbrake::Filters::KeysBlacklist.new(@config.logger, blacklist_keys)
206
+ )
207
+ end
208
+
209
+ add_filter(Airbrake::Filters::ContextFilter.new(@context))
210
+ add_filter(Airbrake::Filters::ExceptionAttributesFilter.new(@config.logger))
211
+
212
+ return unless (root_directory = @config.root_directory)
213
+ [
214
+ Airbrake::Filters::RootDirectoryFilter,
215
+ Airbrake::Filters::GitRevisionFilter,
216
+ Airbrake::Filters::GitRepositoryFilter
217
+ ].each do |filter|
218
+ add_filter(filter.new(root_directory))
219
+ end
220
+
221
+ add_filter(
222
+ Airbrake::Filters::GitLastCheckoutFilter.new(@config.logger, root_directory)
223
+ )
224
+ end
225
+ # rubocop:enable Metrics/AbcSize
226
+ end
227
+ # rubocop:enable Metrics/ClassLength
228
+ end