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.
- checksums.yaml +7 -0
- data/lib/airbrake-ruby.rb +554 -0
- data/lib/airbrake-ruby/async_sender.rb +119 -0
- data/lib/airbrake-ruby/backtrace.rb +194 -0
- data/lib/airbrake-ruby/code_hunk.rb +53 -0
- data/lib/airbrake-ruby/config.rb +238 -0
- data/lib/airbrake-ruby/config/validator.rb +63 -0
- data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
- data/lib/airbrake-ruby/file_cache.rb +48 -0
- data/lib/airbrake-ruby/filter_chain.rb +95 -0
- data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
- data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +90 -0
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +42 -0
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +50 -0
- data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +49 -0
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
- data/lib/airbrake-ruby/hash_keyable.rb +37 -0
- data/lib/airbrake-ruby/ignorable.rb +44 -0
- data/lib/airbrake-ruby/nested_exception.rb +39 -0
- data/lib/airbrake-ruby/notice.rb +165 -0
- data/lib/airbrake-ruby/notice_notifier.rb +228 -0
- data/lib/airbrake-ruby/performance_notifier.rb +161 -0
- data/lib/airbrake-ruby/promise.rb +99 -0
- data/lib/airbrake-ruby/response.rb +71 -0
- data/lib/airbrake-ruby/stat.rb +56 -0
- data/lib/airbrake-ruby/sync_sender.rb +111 -0
- data/lib/airbrake-ruby/tdigest.rb +393 -0
- data/lib/airbrake-ruby/time_truncate.rb +17 -0
- data/lib/airbrake-ruby/truncator.rb +115 -0
- data/lib/airbrake-ruby/version.rb +6 -0
- data/spec/airbrake_spec.rb +171 -0
- data/spec/async_sender_spec.rb +154 -0
- data/spec/backtrace_spec.rb +438 -0
- data/spec/code_hunk_spec.rb +118 -0
- data/spec/config/validator_spec.rb +189 -0
- data/spec/config_spec.rb +281 -0
- data/spec/deploy_notifier_spec.rb +41 -0
- data/spec/file_cache.rb +36 -0
- data/spec/filter_chain_spec.rb +83 -0
- data/spec/filters/context_filter_spec.rb +25 -0
- data/spec/filters/dependency_filter_spec.rb +14 -0
- data/spec/filters/exception_attributes_filter_spec.rb +63 -0
- data/spec/filters/gem_root_filter_spec.rb +44 -0
- data/spec/filters/git_last_checkout_filter_spec.rb +48 -0
- data/spec/filters/git_repository_filter.rb +53 -0
- data/spec/filters/git_revision_filter_spec.rb +126 -0
- data/spec/filters/keys_blacklist_spec.rb +236 -0
- data/spec/filters/keys_whitelist_spec.rb +205 -0
- data/spec/filters/root_directory_filter_spec.rb +42 -0
- data/spec/filters/sql_filter_spec.rb +219 -0
- data/spec/filters/system_exit_filter_spec.rb +14 -0
- data/spec/filters/thread_filter_spec.rb +279 -0
- data/spec/fixtures/notroot.txt +7 -0
- data/spec/fixtures/project_root/code.rb +221 -0
- data/spec/fixtures/project_root/empty_file.rb +0 -0
- data/spec/fixtures/project_root/long_line.txt +1 -0
- data/spec/fixtures/project_root/short_file.rb +3 -0
- data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
- data/spec/helpers.rb +9 -0
- data/spec/ignorable_spec.rb +14 -0
- data/spec/nested_exception_spec.rb +75 -0
- data/spec/notice_notifier_spec.rb +436 -0
- data/spec/notice_notifier_spec/options_spec.rb +266 -0
- data/spec/notice_spec.rb +297 -0
- data/spec/performance_notifier_spec.rb +287 -0
- data/spec/promise_spec.rb +165 -0
- data/spec/response_spec.rb +82 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/stat_spec.rb +35 -0
- data/spec/sync_sender_spec.rb +140 -0
- data/spec/tdigest_spec.rb +230 -0
- data/spec/time_truncate_spec.rb +13 -0
- data/spec/truncator_spec.rb +238 -0
- 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
|