airbrake-ruby 1.0.0.rc.1
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 +292 -0
- data/lib/airbrake-ruby/async_sender.rb +90 -0
- data/lib/airbrake-ruby/backtrace.rb +75 -0
- data/lib/airbrake-ruby/config.rb +120 -0
- data/lib/airbrake-ruby/filter_chain.rb +86 -0
- data/lib/airbrake-ruby/filters.rb +10 -0
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +37 -0
- data/lib/airbrake-ruby/filters/keys_filter.rb +65 -0
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +37 -0
- data/lib/airbrake-ruby/notice.rb +207 -0
- data/lib/airbrake-ruby/notifier.rb +145 -0
- data/lib/airbrake-ruby/payload_truncator.rb +141 -0
- data/lib/airbrake-ruby/response.rb +53 -0
- data/lib/airbrake-ruby/sync_sender.rb +76 -0
- data/lib/airbrake-ruby/version.rb +7 -0
- data/spec/airbrake_spec.rb +177 -0
- data/spec/async_sender_spec.rb +121 -0
- data/spec/backtrace_spec.rb +77 -0
- data/spec/config_spec.rb +67 -0
- data/spec/filter_chain_spec.rb +157 -0
- data/spec/notice_spec.rb +190 -0
- data/spec/notifier_spec.rb +690 -0
- data/spec/notifier_spec/options_spec.rb +217 -0
- data/spec/payload_truncator_spec.rb +458 -0
- data/spec/spec_helper.rb +98 -0
- metadata +158 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
module Airbrake
|
2
|
+
##
|
3
|
+
# Represents the Airbrake config. A config contains all the options that you
|
4
|
+
# can use to configure an Airbrake instance.
|
5
|
+
class Config
|
6
|
+
##
|
7
|
+
# @return [Integer] the project identificator. This value *must* be set.
|
8
|
+
attr_accessor :project_id
|
9
|
+
|
10
|
+
##
|
11
|
+
# @return [String] the project key. This value *must* be set.
|
12
|
+
attr_accessor :project_key
|
13
|
+
|
14
|
+
##
|
15
|
+
# @return [Hash] the proxy parameters such as (:host, :port, :user and
|
16
|
+
# :password)
|
17
|
+
attr_accessor :proxy
|
18
|
+
|
19
|
+
##
|
20
|
+
# @return [Logger] the default logger used for debug output
|
21
|
+
attr_reader :logger
|
22
|
+
|
23
|
+
##
|
24
|
+
# @return [String] the version of the user's application
|
25
|
+
attr_accessor :app_version
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [Integer] the max number of notices that can be queued up
|
29
|
+
attr_accessor :queue_size
|
30
|
+
|
31
|
+
##
|
32
|
+
# @return [Integer] the number of worker threads that process the notice
|
33
|
+
# queue
|
34
|
+
attr_accessor :workers
|
35
|
+
|
36
|
+
##
|
37
|
+
# @return [String] the host, which provides the API endpoint to which
|
38
|
+
# exceptions should be sent
|
39
|
+
attr_accessor :host
|
40
|
+
|
41
|
+
##
|
42
|
+
# @return [String, Pathname] the working directory of your project
|
43
|
+
attr_accessor :root_directory
|
44
|
+
|
45
|
+
##
|
46
|
+
# @return [String, Symbol] the environment the application is running in
|
47
|
+
attr_accessor :environment
|
48
|
+
|
49
|
+
##
|
50
|
+
# @return [Array<String, Symbol>] the array of environments that forbids
|
51
|
+
# sending exceptions when the application is running in them. Other
|
52
|
+
# possible environments not listed in the array will allow sending
|
53
|
+
# occurring exceptions.
|
54
|
+
attr_accessor :ignore_environments
|
55
|
+
|
56
|
+
##
|
57
|
+
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
58
|
+
# config
|
59
|
+
def initialize(user_config = {})
|
60
|
+
self.proxy = {}
|
61
|
+
self.queue_size = 100
|
62
|
+
self.workers = 1
|
63
|
+
|
64
|
+
self.logger = Logger.new(STDOUT)
|
65
|
+
logger.level = Logger::WARN
|
66
|
+
|
67
|
+
self.project_id = user_config[:project_id]
|
68
|
+
self.project_key = user_config[:project_key]
|
69
|
+
self.host = 'https://airbrake.io'
|
70
|
+
|
71
|
+
self.ignore_environments = []
|
72
|
+
|
73
|
+
merge(user_config)
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# The full URL to the Airbrake Notice API. Based on the +:host+ option.
|
78
|
+
# @return [URI] the endpoint address
|
79
|
+
def endpoint
|
80
|
+
@endpoint ||=
|
81
|
+
begin
|
82
|
+
self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
|
83
|
+
api = "/api/v3/projects/#{project_id}/notices?key=#{project_key}"
|
84
|
+
URI.join(host, api)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Sets the logger. Never allows to assign `nil` as the logger.
|
90
|
+
# @return [Logger] the logger
|
91
|
+
def logger=(logger)
|
92
|
+
@logger = logger || @logger
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Merges the given +config_hash+ with itself.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# config.merge(host: 'localhost:8080')
|
100
|
+
#
|
101
|
+
# @return [self] the merged config
|
102
|
+
def merge(config_hash)
|
103
|
+
config_hash.each_pair { |option, value| set_option(option, value) }
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def set_option(option, value)
|
110
|
+
__send__("#{option}=", value)
|
111
|
+
rescue NoMethodError
|
112
|
+
raise Airbrake::Error, "unknown option '#{option}'"
|
113
|
+
end
|
114
|
+
|
115
|
+
def set_endpoint(id, key, host)
|
116
|
+
host = ('https://' << host) if host !~ %r{\Ahttps?://}
|
117
|
+
@endpoint = URI.join(host, "/api/v3/projects/#{id}/notices?key=#{key}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Airbrake
|
2
|
+
##
|
3
|
+
# Represents the mechanism for filtering notices. Defines a few default
|
4
|
+
# filters.
|
5
|
+
# @see Airbrake.add_filter
|
6
|
+
class FilterChain
|
7
|
+
##
|
8
|
+
# Replaces paths to gems with a placeholder.
|
9
|
+
# @return [Proc]
|
10
|
+
GEM_ROOT_FILTER = proc do |notice|
|
11
|
+
return unless defined?(Gem)
|
12
|
+
|
13
|
+
notice[:errors].each do |error|
|
14
|
+
Gem.path.each do |gem_path|
|
15
|
+
error[:backtrace].each do |frame|
|
16
|
+
frame[:file].sub!(/\A#{gem_path}/, '[GEM_ROOT]'.freeze)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Skip over SystemExit exceptions, because they're just noise.
|
24
|
+
# @return [Proc]
|
25
|
+
SYSTEM_EXIT_FILTER = proc do |notice|
|
26
|
+
if notice[:errors].any? { |error| error[:type] == 'SystemExit' }
|
27
|
+
notice.ignore!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# @param [Airbrake::Config] config
|
33
|
+
def initialize(config)
|
34
|
+
@filters = []
|
35
|
+
|
36
|
+
if config.ignore_environments.any?
|
37
|
+
add_filter(env_filter(config.environment, config.ignore_environments))
|
38
|
+
end
|
39
|
+
|
40
|
+
[SYSTEM_EXIT_FILTER, GEM_ROOT_FILTER].each do |filter|
|
41
|
+
add_filter(filter)
|
42
|
+
end
|
43
|
+
|
44
|
+
root_directory = config.root_directory
|
45
|
+
add_filter(root_directory_filter(root_directory)) if root_directory
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Adds a filter to the filter chain.
|
50
|
+
# @param [#call] filter The filter object (proc, class, module, etc)
|
51
|
+
def add_filter(filter)
|
52
|
+
@filters << filter
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Applies all the filters in the filter chain to the given notice. Does not
|
57
|
+
# filter ignored notices.
|
58
|
+
#
|
59
|
+
# @param [Airbrake::Notice] notice The notice to be filtered
|
60
|
+
# @return [void]
|
61
|
+
def refine(notice)
|
62
|
+
@filters.each do |filter|
|
63
|
+
break if notice.ignored?
|
64
|
+
filter.call(notice)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def root_directory_filter(root_directory)
|
71
|
+
proc do |notice|
|
72
|
+
notice[:errors].each do |error|
|
73
|
+
error[:backtrace].each do |frame|
|
74
|
+
frame[:file].sub!(/\A#{root_directory}/, '[PROJECT_ROOT]'.freeze)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def env_filter(environment, ignore_environments)
|
81
|
+
proc do |notice|
|
82
|
+
notice.ignore! if ignore_environments.include?(environment)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Airbrake
|
2
|
+
##
|
3
|
+
# Represents a namespace for default Airbrake Ruby filters.
|
4
|
+
module Filters
|
5
|
+
##
|
6
|
+
# @return [Array<Symbol>] parts of a Notice's payload that can be modified
|
7
|
+
# by various filters
|
8
|
+
FILTERABLE_KEYS = [:environment, :session, :params].freeze
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Filters
|
3
|
+
##
|
4
|
+
# A default Airbrake notice filter. Filters only specific keys listed in the
|
5
|
+
# list of parameters in the modifiable payload of a notice.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# filter = Airbrake::Filters::KeysBlacklist.new(:email, /credit/i, 'password')
|
9
|
+
# airbrake.add_filter(filter)
|
10
|
+
# airbrake.notify(StandardError.new('App crashed!'), {
|
11
|
+
# user: 'John'
|
12
|
+
# password: 's3kr3t',
|
13
|
+
# email: 'john@example.com',
|
14
|
+
# credit_card: '5555555555554444'
|
15
|
+
# })
|
16
|
+
#
|
17
|
+
# # The dashboard will display this parameter as is, but all other
|
18
|
+
# # values will be filtered:
|
19
|
+
# # { user: 'John',
|
20
|
+
# # password: '[Filtered]',
|
21
|
+
# # email: '[Filtered]',
|
22
|
+
# # credit_card: '[Filtered]' }
|
23
|
+
#
|
24
|
+
# @see KeysWhitelist
|
25
|
+
# @see KeysFilter
|
26
|
+
class KeysBlacklist
|
27
|
+
include KeysFilter
|
28
|
+
|
29
|
+
##
|
30
|
+
# @return [Boolean] true if the key matches at least one pattern, false
|
31
|
+
# otherwise
|
32
|
+
def should_filter?(key)
|
33
|
+
@patterns.any? { |pattern| key.to_s.match(pattern) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Filters
|
3
|
+
##
|
4
|
+
# This is a filter helper that endows a class ability to filter notices'
|
5
|
+
# payload based on the return value of the +should_filter?+ method that a
|
6
|
+
# class that includes this module must implement.
|
7
|
+
#
|
8
|
+
# @see Notice
|
9
|
+
# @see KeysWhitelist
|
10
|
+
# @see KeysBlacklist
|
11
|
+
module KeysFilter
|
12
|
+
##
|
13
|
+
# Creates a new KeysBlacklist or KeysWhitelist filter that uses the given
|
14
|
+
# +patterns+ for filtering a notice's payload.
|
15
|
+
#
|
16
|
+
# @param [Array<String,Regexp,Symbol>] patterns
|
17
|
+
def initialize(*patterns)
|
18
|
+
@patterns = patterns.map(&:to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# This is a mandatory method required by any filter integrated with
|
23
|
+
# FilterChain.
|
24
|
+
#
|
25
|
+
# @param [Notice] notice the notice to be filtered
|
26
|
+
# @return [void]
|
27
|
+
# @see FilterChain
|
28
|
+
def call(notice)
|
29
|
+
FILTERABLE_KEYS.each { |key| filter_hash(notice[key]) }
|
30
|
+
|
31
|
+
return unless notice[:context][:url]
|
32
|
+
url = URI(notice[:context][:url])
|
33
|
+
return if url.nil? || url.query.nil?
|
34
|
+
|
35
|
+
notice[:context][:url] = filter_url_params(url)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @raise [NotImplementedError] if called directly
|
40
|
+
def should_filter?(_key)
|
41
|
+
raise NotImplementedError, 'method must be implemented in the included class'
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def filter_hash(hash)
|
47
|
+
hash.each_key do |key|
|
48
|
+
if should_filter?(key)
|
49
|
+
hash[key] = '[Filtered]'.freeze
|
50
|
+
else
|
51
|
+
filter_hash(hash[key]) if hash[key].is_a?(Hash)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def filter_url_params(url)
|
57
|
+
url.query = Hash[URI.decode_www_form(url.query)].map do |key, val|
|
58
|
+
should_filter?(key) ? "#{key}=[Filtered]" : "#{key}=#{val}"
|
59
|
+
end.join('&')
|
60
|
+
|
61
|
+
url.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Filters
|
3
|
+
##
|
4
|
+
# A default Airbrake notice filter. Filters everything in the modifiable
|
5
|
+
# payload of a notice, but specified keys.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# filter = Airbrake::Filters::KeysWhitelist.new(:email, /user/i, 'account_id')
|
9
|
+
# airbrake.add_filter(filter)
|
10
|
+
# airbrake.notify(StandardError.new('App crashed!'), {
|
11
|
+
# user: 'John',
|
12
|
+
# password: 's3kr3t',
|
13
|
+
# email: 'john@example.com',
|
14
|
+
# account_id: 42
|
15
|
+
# })
|
16
|
+
#
|
17
|
+
# # The dashboard will display this parameters as filtered, but other
|
18
|
+
# # values won't be affected:
|
19
|
+
# # { user: 'John',
|
20
|
+
# # password: '[Filtered]',
|
21
|
+
# # email: 'john@example.com',
|
22
|
+
# # account_id: 42 }
|
23
|
+
#
|
24
|
+
# @see KeysBlacklist
|
25
|
+
# @see KeysFilter
|
26
|
+
class KeysWhitelist
|
27
|
+
include KeysFilter
|
28
|
+
|
29
|
+
##
|
30
|
+
# @return [Boolean] true if the key doesn't match any pattern, false
|
31
|
+
# otherwise.
|
32
|
+
def should_filter?(key)
|
33
|
+
@patterns.none? { |pattern| key.to_s.match(pattern) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module Airbrake
|
2
|
+
##
|
3
|
+
# Represents a chunk of information that is meant to be either sent to
|
4
|
+
# Airbrake or ignored completely.
|
5
|
+
class Notice
|
6
|
+
# @return [Hash{Symbol=>String}] the information about the notifier library
|
7
|
+
NOTIFIER = {
|
8
|
+
name: 'airbrake-ruby'.freeze,
|
9
|
+
version: Airbrake::AIRBRAKE_RUBY_VERSION,
|
10
|
+
url: 'https://github.com/airbrake/airbrake-ruby'.freeze
|
11
|
+
}.freeze
|
12
|
+
|
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_VERSION,
|
19
|
+
notifier: NOTIFIER
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
##
|
23
|
+
# @return [Integer] the maxium size of the JSON payload in bytes
|
24
|
+
MAX_NOTICE_SIZE = 64000
|
25
|
+
|
26
|
+
##
|
27
|
+
# @return [Integer] the maximum number of nested exceptions that a notice
|
28
|
+
# can unwrap. Exceptions that have a longer cause chain will be ignored
|
29
|
+
MAX_NESTED_EXCEPTIONS = 3
|
30
|
+
|
31
|
+
##
|
32
|
+
# @return [Integer] the maximum size of hashes, arrays and strings in the
|
33
|
+
# notice.
|
34
|
+
PAYLOAD_MAX_SIZE = 10000
|
35
|
+
|
36
|
+
##
|
37
|
+
# @return [Array<StandardError>] the list of possible exceptions that might
|
38
|
+
# be raised when an object is converted to JSON
|
39
|
+
JSON_EXCEPTIONS = [
|
40
|
+
IOError,
|
41
|
+
NotImplementedError,
|
42
|
+
JSON::GeneratorError,
|
43
|
+
Encoding::UndefinedConversionError
|
44
|
+
]
|
45
|
+
|
46
|
+
# @return [Array<Symbol>] the list of keys that can be be overwritten with
|
47
|
+
# {Airbrake::Notice#[]=}
|
48
|
+
WRITABLE_KEYS = [
|
49
|
+
:notifier,
|
50
|
+
:context,
|
51
|
+
:environment,
|
52
|
+
:session,
|
53
|
+
:params
|
54
|
+
]
|
55
|
+
|
56
|
+
def initialize(config, exception, params = {})
|
57
|
+
@config = config
|
58
|
+
|
59
|
+
@private_payload = {
|
60
|
+
notifier: NOTIFIER
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
@modifiable_payload = {
|
64
|
+
errors: errors(exception),
|
65
|
+
context: context(params),
|
66
|
+
environment: {},
|
67
|
+
session: {},
|
68
|
+
params: params
|
69
|
+
}
|
70
|
+
|
71
|
+
@truncator = PayloadTruncator.new(PAYLOAD_MAX_SIZE, @config.logger)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Converts the notice to JSON. Calls +to_json+ on each object inside
|
76
|
+
# notice's payload. Truncates notices, JSON representation of which is
|
77
|
+
# bigger than {MAX_NOTICE_SIZE}.
|
78
|
+
#
|
79
|
+
# @return [Hash{String=>String}]
|
80
|
+
def to_json
|
81
|
+
loop do
|
82
|
+
begin
|
83
|
+
json = payload.to_json
|
84
|
+
rescue *JSON_EXCEPTIONS => ex
|
85
|
+
@config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.to_s.chomp}")
|
86
|
+
else
|
87
|
+
return json if json && json.bytesize <= MAX_NOTICE_SIZE
|
88
|
+
end
|
89
|
+
|
90
|
+
truncate_payload
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Ignores a notice. Ignored notices never reach the Airbrake dashboard.
|
96
|
+
#
|
97
|
+
# @return [void]
|
98
|
+
# @see #ignored?
|
99
|
+
# @note Ignored noticed can't be unignored
|
100
|
+
def ignore!
|
101
|
+
@modifiable_payload = nil
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Checks whether the notice was ignored.
|
106
|
+
#
|
107
|
+
# @return [Boolean]
|
108
|
+
# @see #ignore!
|
109
|
+
def ignored?
|
110
|
+
@modifiable_payload.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Reads a value from notice's modifiable payload.
|
115
|
+
# @return [Object]
|
116
|
+
#
|
117
|
+
# @raise [Airbrake::Error] if the notice is ignored
|
118
|
+
def [](key)
|
119
|
+
raise_if_ignored
|
120
|
+
@modifiable_payload[key]
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Writes a value to the modifiable payload hash. Restricts unrecognized
|
125
|
+
# writes.
|
126
|
+
# @example
|
127
|
+
# notice[:params][:my_param] = 'foobar'
|
128
|
+
#
|
129
|
+
# @return [void]
|
130
|
+
# @raise [Airbrake::Error] if the notice is ignored
|
131
|
+
# @raise [Airbrake::Error] if the +key+ is not recognized
|
132
|
+
# @raise [Airbrake::Error] if the root value is not a Hash
|
133
|
+
def []=(key, value)
|
134
|
+
raise_if_ignored
|
135
|
+
raise_if_unrecognized_key(key)
|
136
|
+
raise_if_non_hash_value(value)
|
137
|
+
|
138
|
+
@modifiable_payload[key] = value.to_hash
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def context(params)
|
144
|
+
{ version: @config.app_version,
|
145
|
+
# We ensure that root_directory is always a String, so it can always be
|
146
|
+
# converted to JSON in a predictable manner (when it's a Pathname and in
|
147
|
+
# Rails environment, it converts to unexpected JSON).
|
148
|
+
rootDirectory: @config.root_directory.to_s,
|
149
|
+
environment: @config.environment,
|
150
|
+
|
151
|
+
# Legacy Airbrake v4 behaviour.
|
152
|
+
component: params.delete(:component),
|
153
|
+
action: params.delete(:action)
|
154
|
+
}.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
|
155
|
+
end
|
156
|
+
|
157
|
+
def raise_if_ignored
|
158
|
+
return unless self.ignored?
|
159
|
+
raise Airbrake::Error, 'cannot access ignored notice'
|
160
|
+
end
|
161
|
+
|
162
|
+
def raise_if_unrecognized_key(key)
|
163
|
+
return if WRITABLE_KEYS.include?(key)
|
164
|
+
raise Airbrake::Error,
|
165
|
+
":#{key} is not recognized among #{WRITABLE_KEYS}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def raise_if_non_hash_value(value)
|
169
|
+
return if value.respond_to?(:to_hash)
|
170
|
+
raise Airbrake::Error, "Got #{value.class} value, wanted a Hash"
|
171
|
+
end
|
172
|
+
|
173
|
+
def payload
|
174
|
+
@modifiable_payload.merge(@private_payload)
|
175
|
+
end
|
176
|
+
|
177
|
+
def errors(exception)
|
178
|
+
exception_list = []
|
179
|
+
|
180
|
+
while exception && exception_list.size < MAX_NESTED_EXCEPTIONS
|
181
|
+
exception_list << exception
|
182
|
+
|
183
|
+
exception = if exception.respond_to?(:cause) && exception.cause
|
184
|
+
exception.cause
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
exception_list.map do |e|
|
189
|
+
{ type: e.class.name,
|
190
|
+
message: e.message,
|
191
|
+
backtrace: Backtrace.parse(e) }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def truncate_payload
|
196
|
+
@modifiable_payload[:errors].each do |error|
|
197
|
+
@truncator.truncate_error(error)
|
198
|
+
end
|
199
|
+
|
200
|
+
Filters::FILTERABLE_KEYS.each do |key|
|
201
|
+
@truncator.truncate_object(@modifiable_payload[key])
|
202
|
+
end
|
203
|
+
|
204
|
+
@truncator.reduce_max_size
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|