airbrake-ruby 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/airbrake-ruby.rb +513 -0
- data/lib/airbrake-ruby/async_sender.rb +142 -0
- data/lib/airbrake-ruby/backtrace.rb +196 -0
- data/lib/airbrake-ruby/benchmark.rb +39 -0
- data/lib/airbrake-ruby/code_hunk.rb +51 -0
- data/lib/airbrake-ruby/config.rb +229 -0
- data/lib/airbrake-ruby/config/validator.rb +91 -0
- data/lib/airbrake-ruby/deploy_notifier.rb +36 -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 +46 -0
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
- data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -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/inspectable.rb +39 -0
- data/lib/airbrake-ruby/loggable.rb +34 -0
- data/lib/airbrake-ruby/monotonic_time.rb +43 -0
- data/lib/airbrake-ruby/nested_exception.rb +38 -0
- data/lib/airbrake-ruby/notice.rb +162 -0
- data/lib/airbrake-ruby/notice_notifier.rb +134 -0
- data/lib/airbrake-ruby/performance_breakdown.rb +45 -0
- data/lib/airbrake-ruby/performance_notifier.rb +125 -0
- data/lib/airbrake-ruby/promise.rb +109 -0
- data/lib/airbrake-ruby/query.rb +53 -0
- data/lib/airbrake-ruby/request.rb +45 -0
- data/lib/airbrake-ruby/response.rb +74 -0
- data/lib/airbrake-ruby/stashable.rb +15 -0
- data/lib/airbrake-ruby/stat.rb +73 -0
- data/lib/airbrake-ruby/sync_sender.rb +113 -0
- data/lib/airbrake-ruby/tdigest.rb +393 -0
- data/lib/airbrake-ruby/time_truncate.rb +17 -0
- data/lib/airbrake-ruby/timed_trace.rb +58 -0
- data/lib/airbrake-ruby/truncator.rb +115 -0
- data/lib/airbrake-ruby/version.rb +6 -0
- data/spec/airbrake_spec.rb +324 -0
- data/spec/async_sender_spec.rb +155 -0
- data/spec/backtrace_spec.rb +427 -0
- data/spec/benchmark_spec.rb +33 -0
- data/spec/code_hunk_spec.rb +115 -0
- data/spec/config/validator_spec.rb +184 -0
- data/spec/config_spec.rb +154 -0
- data/spec/deploy_notifier_spec.rb +48 -0
- data/spec/file_cache.rb +36 -0
- data/spec/filter_chain_spec.rb +92 -0
- data/spec/filters/context_filter_spec.rb +23 -0
- data/spec/filters/dependency_filter_spec.rb +12 -0
- data/spec/filters/exception_attributes_filter_spec.rb +50 -0
- data/spec/filters/gem_root_filter_spec.rb +41 -0
- data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
- data/spec/filters/git_repository_filter.rb +61 -0
- data/spec/filters/git_revision_filter_spec.rb +126 -0
- data/spec/filters/keys_blacklist_spec.rb +225 -0
- data/spec/filters/keys_whitelist_spec.rb +194 -0
- data/spec/filters/root_directory_filter_spec.rb +39 -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 +277 -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/inspectable_spec.rb +45 -0
- data/spec/monotonic_time_spec.rb +12 -0
- data/spec/nested_exception_spec.rb +73 -0
- data/spec/notice_notifier_spec.rb +356 -0
- data/spec/notice_notifier_spec/options_spec.rb +259 -0
- data/spec/notice_spec.rb +296 -0
- data/spec/performance_breakdown_spec.rb +12 -0
- data/spec/performance_notifier_spec.rb +435 -0
- data/spec/promise_spec.rb +197 -0
- data/spec/query_spec.rb +11 -0
- data/spec/request_spec.rb +11 -0
- data/spec/response_spec.rb +88 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/stashable_spec.rb +23 -0
- data/spec/stat_spec.rb +47 -0
- data/spec/sync_sender_spec.rb +133 -0
- data/spec/tdigest_spec.rb +230 -0
- data/spec/time_truncate_spec.rb +13 -0
- data/spec/timed_trace_spec.rb +125 -0
- data/spec/truncator_spec.rb +238 -0
- metadata +213 -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
|
+
# Inspectable provides custom inspect methods that reduce clutter printed in
|
3
|
+
# REPLs for notifier objects. These custom methods display only essential
|
4
|
+
# information such as project id/key and filters.
|
5
|
+
#
|
6
|
+
# @since v3.2.6
|
7
|
+
# @api private
|
8
|
+
module Inspectable
|
9
|
+
# @return [String] inspect output template
|
10
|
+
INSPECT_TEMPLATE =
|
11
|
+
"#<%<classname>s:0x%<id>s project_id=\"%<project_id>s\" " \
|
12
|
+
"project_key=\"%<project_key>s\" " \
|
13
|
+
"host=\"%<host>s\" filter_chain=%<filter_chain>s>".freeze
|
14
|
+
|
15
|
+
# @return [String] customized inspect to lessen the amount of clutter
|
16
|
+
def inspect
|
17
|
+
format(
|
18
|
+
INSPECT_TEMPLATE,
|
19
|
+
classname: self.class.name,
|
20
|
+
id: (object_id << 1).to_s(16).rjust(16, '0'),
|
21
|
+
project_id: @config.project_id,
|
22
|
+
project_key: @config.project_key,
|
23
|
+
host: @config.host,
|
24
|
+
filter_chain: @filter_chain.inspect
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String] {#inspect} for PrettyPrint
|
29
|
+
def pretty_print(q)
|
30
|
+
q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
|
31
|
+
q.text(
|
32
|
+
"project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
|
33
|
+
"host=\"#{@config.host}\" filter_chain="
|
34
|
+
)
|
35
|
+
q.pp(@filter_chain)
|
36
|
+
q.text('>')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Loggable is included into any class that wants to be able to log.
|
3
|
+
#
|
4
|
+
# By default, Loggable defines a null logger that doesn't do anything. You are
|
5
|
+
# supposed to overwrite it via the {instance} method before calling {logger}.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class A
|
9
|
+
# include Loggable
|
10
|
+
#
|
11
|
+
# def initialize
|
12
|
+
# logger.debug('Initialized A')
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @since v4.0.0
|
17
|
+
# @api private
|
18
|
+
module Loggable
|
19
|
+
class << self
|
20
|
+
# @return [Logger]
|
21
|
+
attr_writer :instance
|
22
|
+
|
23
|
+
# @return [Logger]
|
24
|
+
def instance
|
25
|
+
@instance ||= ::Logger.new(File::NULL)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Logger] standard Ruby logger object
|
30
|
+
def logger
|
31
|
+
Loggable.instance
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# MonotonicTime is a helper for getting monotonic time suitable for
|
3
|
+
# performance measurements. It guarantees that the time is strictly linearly
|
4
|
+
# increasing (unlike realtime).
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# MonotonicTime.time_in_ms #=> 287138801.144576
|
8
|
+
#
|
9
|
+
# @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
|
10
|
+
# @since v4.2.4
|
11
|
+
# @api private
|
12
|
+
module MonotonicTime
|
13
|
+
class << self
|
14
|
+
# @return [Integer] current monotonic time in milliseconds
|
15
|
+
def time_in_ms
|
16
|
+
time_in_nanoseconds / (10.0**6)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
22
|
+
|
23
|
+
def time_in_nanoseconds
|
24
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
25
|
+
end
|
26
|
+
|
27
|
+
elsif RUBY_ENGINE == 'jruby'
|
28
|
+
|
29
|
+
def time_in_nanoseconds
|
30
|
+
java.lang.System.nanoTime
|
31
|
+
end
|
32
|
+
|
33
|
+
else
|
34
|
+
|
35
|
+
def time_in_nanoseconds
|
36
|
+
time = Time.now
|
37
|
+
time.to_i * (10**9) + time.nsec
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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(exception)
|
13
|
+
@exception = exception
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_json
|
17
|
+
unwind_exceptions.map do |exception|
|
18
|
+
{ type: exception.class.name,
|
19
|
+
message: exception.message,
|
20
|
+
backtrace: Backtrace.parse(exception) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def unwind_exceptions
|
27
|
+
exception_list = []
|
28
|
+
exception = @exception
|
29
|
+
|
30
|
+
while exception && exception_list.size < MAX_NESTED_EXCEPTIONS
|
31
|
+
exception_list << exception
|
32
|
+
exception = (exception.cause if exception.respond_to?(:cause))
|
33
|
+
end
|
34
|
+
|
35
|
+
exception_list
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,162 @@
|
|
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
|
+
include Loggable
|
54
|
+
include Stashable
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def initialize(exception, params = {})
|
58
|
+
@config = Airbrake::Config.instance
|
59
|
+
@payload = {
|
60
|
+
errors: NestedException.new(exception).as_json,
|
61
|
+
context: context,
|
62
|
+
environment: {
|
63
|
+
program_name: $PROGRAM_NAME
|
64
|
+
},
|
65
|
+
session: {},
|
66
|
+
params: params
|
67
|
+
}
|
68
|
+
@truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
|
69
|
+
|
70
|
+
stash[:exception] = exception
|
71
|
+
end
|
72
|
+
|
73
|
+
# Converts the notice to JSON. Calls +to_json+ on each object inside
|
74
|
+
# notice's payload. Truncates notices, JSON representation of which is
|
75
|
+
# bigger than {MAX_NOTICE_SIZE}.
|
76
|
+
#
|
77
|
+
# @return [Hash{String=>String}, nil]
|
78
|
+
# @api private
|
79
|
+
def to_json
|
80
|
+
loop do
|
81
|
+
begin
|
82
|
+
json = @payload.to_json
|
83
|
+
rescue *JSON_EXCEPTIONS => ex
|
84
|
+
logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")
|
85
|
+
else
|
86
|
+
return json if json && json.bytesize <= MAX_NOTICE_SIZE
|
87
|
+
end
|
88
|
+
|
89
|
+
break if truncate == 0
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reads a value from notice's payload.
|
94
|
+
#
|
95
|
+
# @return [Object]
|
96
|
+
# @raise [Airbrake::Error] if the notice is ignored
|
97
|
+
def [](key)
|
98
|
+
raise_if_ignored
|
99
|
+
@payload[key]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Writes a value to the payload hash. Restricts unrecognized writes.
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# notice[:params][:my_param] = 'foobar'
|
106
|
+
#
|
107
|
+
# @return [void]
|
108
|
+
# @raise [Airbrake::Error] if the notice is ignored
|
109
|
+
# @raise [Airbrake::Error] if the +key+ is not recognized
|
110
|
+
# @raise [Airbrake::Error] if the root value is not a Hash
|
111
|
+
def []=(key, value)
|
112
|
+
raise_if_ignored
|
113
|
+
|
114
|
+
unless WRITABLE_KEYS.include?(key)
|
115
|
+
raise Airbrake::Error,
|
116
|
+
":#{key} is not recognized among #{WRITABLE_KEYS}"
|
117
|
+
end
|
118
|
+
|
119
|
+
unless value.respond_to?(:to_hash)
|
120
|
+
raise Airbrake::Error, "Got #{value.class} value, wanted a Hash"
|
121
|
+
end
|
122
|
+
|
123
|
+
@payload[key] = value.to_hash
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def context
|
129
|
+
{
|
130
|
+
version: @config.app_version,
|
131
|
+
versions: @config.versions,
|
132
|
+
# We ensure that root_directory is always a String, so it can always be
|
133
|
+
# converted to JSON in a predictable manner (when it's a Pathname and in
|
134
|
+
# Rails environment, it converts to unexpected JSON).
|
135
|
+
rootDirectory: @config.root_directory.to_s,
|
136
|
+
environment: @config.environment,
|
137
|
+
|
138
|
+
# Make sure we always send hostname.
|
139
|
+
hostname: HOSTNAME,
|
140
|
+
|
141
|
+
severity: DEFAULT_SEVERITY
|
142
|
+
}.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
|
143
|
+
end
|
144
|
+
|
145
|
+
def truncate
|
146
|
+
TRUNCATABLE_KEYS.each do |key|
|
147
|
+
@payload[key] = @truncator.truncate(@payload[key])
|
148
|
+
end
|
149
|
+
|
150
|
+
new_max_size = @truncator.reduce_max_size
|
151
|
+
if new_max_size == 0
|
152
|
+
logger.error(
|
153
|
+
"#{LOG_LABEL} truncation failed. File an issue at " \
|
154
|
+
"https://github.com/airbrake/airbrake-ruby " \
|
155
|
+
"and attach the following payload: #{@payload}"
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
new_max_size
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|