airbrake-ruby 3.2.2-java
Sign up to get free protection for your applications and to get access to all the features.
- 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,161 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# QueryNotifier aggregates information about SQL queries and periodically sends
|
3
|
+
# collected data to Airbrake.
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
# @since v3.2.0
|
7
|
+
class PerformanceNotifier
|
8
|
+
# @param [Airbrake::Config] config
|
9
|
+
def initialize(config)
|
10
|
+
@config =
|
11
|
+
if config.is_a?(Config)
|
12
|
+
config
|
13
|
+
else
|
14
|
+
loc = caller_locations(1..1).first
|
15
|
+
signature = "#{self.class.name}##{__method__}"
|
16
|
+
warn(
|
17
|
+
"#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
|
18
|
+
'is deprecated. Pass `Airbrake::Config` instead'
|
19
|
+
)
|
20
|
+
Config.new(config)
|
21
|
+
end
|
22
|
+
|
23
|
+
@flush_period = @config.performance_stats_flush_period
|
24
|
+
@sender = SyncSender.new(@config, :put)
|
25
|
+
@payload = {}
|
26
|
+
@schedule_flush = nil
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@filter_chain = FilterChain.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [Hash] resource
|
32
|
+
# @param [Airbrake::Promise] promise
|
33
|
+
# @see Airbrake.notify_query
|
34
|
+
# @see Airbrake.notify_request
|
35
|
+
def notify(resource, promise = Airbrake::Promise.new)
|
36
|
+
if @config.ignored_environment?
|
37
|
+
return promise.reject("The '#{@config.environment}' environment is ignored")
|
38
|
+
end
|
39
|
+
|
40
|
+
unless @config.performance_stats
|
41
|
+
return promise.reject("The Performance Stats feature is disabled")
|
42
|
+
end
|
43
|
+
|
44
|
+
@filter_chain.refine(resource)
|
45
|
+
return if resource.ignored?
|
46
|
+
|
47
|
+
@mutex.synchronize do
|
48
|
+
@payload[resource] ||= Airbrake::Stat.new
|
49
|
+
@payload[resource].increment(resource.start_time, resource.end_time)
|
50
|
+
|
51
|
+
if @flush_period > 0
|
52
|
+
schedule_flush(promise)
|
53
|
+
else
|
54
|
+
send(@payload, promise)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
promise
|
59
|
+
end
|
60
|
+
|
61
|
+
# @see Airbrake.add_performance_filter
|
62
|
+
def add_filter(filter = nil, &block)
|
63
|
+
@filter_chain.add_filter(block_given? ? block : filter)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @see Airbrake.delete_performance_filter
|
67
|
+
def delete_filter(filter_class)
|
68
|
+
@filter_chain.delete_filter(filter_class)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def schedule_flush(promise)
|
74
|
+
@schedule_flush ||= Thread.new do
|
75
|
+
sleep(@flush_period)
|
76
|
+
|
77
|
+
payload = nil
|
78
|
+
@mutex.synchronize do
|
79
|
+
payload = @payload
|
80
|
+
@payload = {}
|
81
|
+
@schedule_flush = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
send(payload, promise)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def send(payload, promise)
|
89
|
+
signature = "#{self.class.name}##{__method__}"
|
90
|
+
raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
|
91
|
+
|
92
|
+
@config.logger.debug("#{LOG_LABEL} #{signature}: #{payload}")
|
93
|
+
|
94
|
+
payload.group_by { |k, _v| k.name }.each do |resource_name, data|
|
95
|
+
@sender.send(
|
96
|
+
{ resource_name => data.map { |k, v| k.to_h.merge!(v.to_h) } },
|
97
|
+
promise,
|
98
|
+
URI.join(
|
99
|
+
@config.host,
|
100
|
+
"api/v5/projects/#{@config.project_id}/#{resource_name}-stats"
|
101
|
+
)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Request holds request data that powers route stats.
|
108
|
+
#
|
109
|
+
# @see Airbrake.notify_request
|
110
|
+
# @api public
|
111
|
+
# @since v3.2.0
|
112
|
+
Request = Struct.new(:method, :route, :status_code, :start_time, :end_time) do
|
113
|
+
include HashKeyable
|
114
|
+
include Ignorable
|
115
|
+
|
116
|
+
def initialize(method:, route:, status_code:, start_time:, end_time: Time.now)
|
117
|
+
@ignored = false
|
118
|
+
super(method, route, status_code, start_time, end_time)
|
119
|
+
end
|
120
|
+
|
121
|
+
def name
|
122
|
+
'routes'
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_h
|
126
|
+
{
|
127
|
+
'method' => method,
|
128
|
+
'route' => route,
|
129
|
+
'statusCode' => status_code,
|
130
|
+
'time' => TimeTruncate.utc_truncate_minutes(start_time)
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Query holds SQL query data that powers SQL query collection.
|
136
|
+
#
|
137
|
+
# @see Airbrake.notify_query
|
138
|
+
# @api public
|
139
|
+
# @since v3.2.0
|
140
|
+
Query = Struct.new(:method, :route, :query, :start_time, :end_time) do
|
141
|
+
include HashKeyable
|
142
|
+
include Ignorable
|
143
|
+
|
144
|
+
def initialize(method:, route:, query:, start_time:, end_time: Time.now)
|
145
|
+
super(method, route, query, start_time, end_time)
|
146
|
+
end
|
147
|
+
|
148
|
+
def name
|
149
|
+
'queries'
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_h
|
153
|
+
{
|
154
|
+
'method' => method,
|
155
|
+
'route' => route,
|
156
|
+
'query' => query,
|
157
|
+
'time' => TimeTruncate.utc_truncate_minutes(start_time)
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Represents a simplified promise object (similar to promises found in
|
3
|
+
# JavaScript), which allows chaining callbacks that are executed when the
|
4
|
+
# promise is either resolved or rejected.
|
5
|
+
#
|
6
|
+
# @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
7
|
+
# @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/promise.rb
|
8
|
+
# @since v1.7.0
|
9
|
+
class Promise
|
10
|
+
# @api private
|
11
|
+
# @return [Hash<String,String>] either successful response containing the
|
12
|
+
# +id+ key or unsuccessful response containing the +error+ key
|
13
|
+
# @note This is a non-blocking call!
|
14
|
+
attr_reader :value
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@on_resolved = []
|
18
|
+
@on_rejected = []
|
19
|
+
@value = {}
|
20
|
+
@mutex = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Attaches a callback to be executed when the promise is resolved.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# Airbrake::Promise.new.then { |response| puts response }
|
27
|
+
# #=> {"id"=>"00054415-8201-e9c6-65d6-fc4d231d2871",
|
28
|
+
# # "url"=>"http://localhost/locate/00054415-8201-e9c6-65d6-fc4d231d2871"}
|
29
|
+
#
|
30
|
+
# @yield [response]
|
31
|
+
# @yieldparam response [Hash<String,String>] Contains the `id` & `url` keys
|
32
|
+
# @return [self]
|
33
|
+
def then(&block)
|
34
|
+
@mutex.synchronize do
|
35
|
+
if @value.key?('id')
|
36
|
+
yield(@value)
|
37
|
+
return self
|
38
|
+
end
|
39
|
+
|
40
|
+
@on_resolved << block
|
41
|
+
end
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Attaches a callback to be executed when the promise is rejected.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# Airbrake::Promise.new.rescue { |error| raise error }
|
50
|
+
#
|
51
|
+
# @yield [error] The error message from the API
|
52
|
+
# @yieldparam error [String]
|
53
|
+
# @return [self]
|
54
|
+
def rescue(&block)
|
55
|
+
@mutex.synchronize do
|
56
|
+
if @value.key?('error')
|
57
|
+
yield(@value['error'])
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
@on_rejected << block
|
62
|
+
end
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Resolves the promise.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# Airbrake::Promise.new.resolve('id' => '123')
|
71
|
+
#
|
72
|
+
# @param response [Hash<String,String>]
|
73
|
+
# @return [self]
|
74
|
+
def resolve(response)
|
75
|
+
@mutex.synchronize do
|
76
|
+
@value = response
|
77
|
+
@on_resolved.each { |callback| callback.call(response) }
|
78
|
+
end
|
79
|
+
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Rejects the promise.
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# Airbrake::Promise.new.reject('Something went wrong')
|
87
|
+
#
|
88
|
+
# @param error [String]
|
89
|
+
# @return [self]
|
90
|
+
def reject(error)
|
91
|
+
@mutex.synchronize do
|
92
|
+
@value['error'] = error
|
93
|
+
@on_rejected.each { |callback| callback.call(error) }
|
94
|
+
end
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Parses responses coming from the Airbrake API. Handles HTTP errors by
|
3
|
+
# logging them.
|
4
|
+
#
|
5
|
+
# @api private
|
6
|
+
# @since v1.0.0
|
7
|
+
module Response
|
8
|
+
# @return [Integer] the limit of the response body
|
9
|
+
TRUNCATE_LIMIT = 100
|
10
|
+
|
11
|
+
# @return [Integer] HTTP code returned when an IP sends over 10k/min notices
|
12
|
+
TOO_MANY_REQUESTS = 429
|
13
|
+
|
14
|
+
# Parses HTTP responses from the Airbrake API.
|
15
|
+
#
|
16
|
+
# @param [Net::HTTPResponse] response
|
17
|
+
# @param [Logger] logger
|
18
|
+
# @return [Hash{String=>String}] parsed response
|
19
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
20
|
+
def self.parse(response, logger)
|
21
|
+
code = response.code.to_i
|
22
|
+
body = response.body
|
23
|
+
|
24
|
+
begin
|
25
|
+
case code
|
26
|
+
when 200, 204
|
27
|
+
logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}")
|
28
|
+
{ response.msg => response.body }
|
29
|
+
when 201
|
30
|
+
parsed_body = JSON.parse(body)
|
31
|
+
logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}")
|
32
|
+
parsed_body
|
33
|
+
when 400, 401, 403, 420
|
34
|
+
parsed_body = JSON.parse(body)
|
35
|
+
logger.error("#{LOG_LABEL} #{parsed_body['message']}")
|
36
|
+
parsed_body
|
37
|
+
when TOO_MANY_REQUESTS
|
38
|
+
parsed_body = JSON.parse(body)
|
39
|
+
msg = "#{LOG_LABEL} #{parsed_body['message']}"
|
40
|
+
logger.error(msg)
|
41
|
+
{ 'error' => msg, 'rate_limit_reset' => rate_limit_reset(response) }
|
42
|
+
else
|
43
|
+
body_msg = truncated_body(body)
|
44
|
+
logger.error("#{LOG_LABEL} unexpected code (#{code}). Body: #{body_msg}")
|
45
|
+
{ 'error' => body_msg }
|
46
|
+
end
|
47
|
+
rescue StandardError => ex
|
48
|
+
body_msg = truncated_body(body)
|
49
|
+
logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}")
|
50
|
+
{ 'error' => ex.inspect }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
54
|
+
|
55
|
+
def self.truncated_body(body)
|
56
|
+
if body.nil?
|
57
|
+
'[EMPTY_BODY]'.freeze
|
58
|
+
elsif body.length > TRUNCATE_LIMIT
|
59
|
+
body[0..TRUNCATE_LIMIT] << '...'
|
60
|
+
else
|
61
|
+
body
|
62
|
+
end
|
63
|
+
end
|
64
|
+
private_class_method :truncated_body
|
65
|
+
|
66
|
+
def self.rate_limit_reset(response)
|
67
|
+
Time.now + response['X-RateLimit-Delay'].to_i
|
68
|
+
end
|
69
|
+
private_class_method :rate_limit_reset
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Airbrake
|
4
|
+
# Stat is a data structure that allows accumulating performance data (route
|
5
|
+
# performance, SQL query performance and such). It's powered by TDigests.
|
6
|
+
#
|
7
|
+
# Usually, one Stat corresponds to one resource (route or query,
|
8
|
+
# etc.). Incrementing a stat means pushing new performance statistics.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# stat = Airbrake::Stat.new
|
12
|
+
# stat.increment(Time.now - 200)
|
13
|
+
# stat.to_h # Pack and serialize data so it can be transmitted.
|
14
|
+
#
|
15
|
+
# @since v3.2.0
|
16
|
+
Stat = Struct.new(:count, :sum, :sumsq, :tdigest) do
|
17
|
+
# @param [Integer] count How many times this stat was incremented
|
18
|
+
# @param [Float] sum The sum of duration in milliseconds
|
19
|
+
# @param [Float] sumsq The squared sum of duration in milliseconds
|
20
|
+
# @param [TDigest::TDigest] tdigest Packed durations. By default,
|
21
|
+
# compression is 20
|
22
|
+
def initialize(count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
|
23
|
+
super(count, sum, sumsq, tdigest)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash{String=>Object}] stats as a hash with compressed TDigest
|
27
|
+
# (serialized as base64)
|
28
|
+
def to_h
|
29
|
+
tdigest.compress!
|
30
|
+
{
|
31
|
+
'count' => count,
|
32
|
+
'sum' => sum,
|
33
|
+
'sumsq' => sumsq,
|
34
|
+
'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Increments count and updates performance with the difference of +end_time+
|
39
|
+
# and +start_time+.
|
40
|
+
#
|
41
|
+
# @param [Date] start_time
|
42
|
+
# @param [Date] end_time
|
43
|
+
# @return [void]
|
44
|
+
def increment(start_time, end_time = nil)
|
45
|
+
end_time ||= Time.new
|
46
|
+
|
47
|
+
self.count += 1
|
48
|
+
|
49
|
+
ms = (end_time - start_time) * 1000
|
50
|
+
self.sum += ms
|
51
|
+
self.sumsq += ms * ms
|
52
|
+
|
53
|
+
tdigest.push(ms)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Responsible for sending data to Airbrake synchronously via PUT or POST
|
3
|
+
# methods. Supports proxies.
|
4
|
+
#
|
5
|
+
# @see AsyncSender
|
6
|
+
# @api private
|
7
|
+
# @since v1.0.0
|
8
|
+
class SyncSender
|
9
|
+
# @return [String] body for HTTP requests
|
10
|
+
CONTENT_TYPE = 'application/json'.freeze
|
11
|
+
|
12
|
+
# @param [Airbrake::Config] config
|
13
|
+
def initialize(config, method = :post)
|
14
|
+
@config = config
|
15
|
+
@method = method
|
16
|
+
@rate_limit_reset = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sends a POST or PUT request to the given +endpoint+ with the +data+ payload.
|
20
|
+
#
|
21
|
+
# @param [#to_json] data
|
22
|
+
# @param [URI::HTTPS] endpoint
|
23
|
+
# @return [Hash{String=>String}] the parsed HTTP response
|
24
|
+
def send(data, promise, endpoint = @config.endpoint)
|
25
|
+
return promise if rate_limited_ip?(promise)
|
26
|
+
|
27
|
+
response = nil
|
28
|
+
req = build_request(endpoint, data)
|
29
|
+
|
30
|
+
return promise if missing_body?(req, promise)
|
31
|
+
|
32
|
+
https = build_https(endpoint)
|
33
|
+
|
34
|
+
begin
|
35
|
+
response = https.request(req)
|
36
|
+
rescue StandardError => ex
|
37
|
+
reason = "#{LOG_LABEL} HTTP error: #{ex}"
|
38
|
+
@config.logger.error(reason)
|
39
|
+
return promise.reject(reason)
|
40
|
+
end
|
41
|
+
|
42
|
+
parsed_resp = Response.parse(response, @config.logger)
|
43
|
+
if parsed_resp.key?('rate_limit_reset')
|
44
|
+
@rate_limit_reset = parsed_resp['rate_limit_reset']
|
45
|
+
end
|
46
|
+
|
47
|
+
return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
|
48
|
+
promise.resolve(parsed_resp)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def build_https(uri)
|
54
|
+
Net::HTTP.new(uri.host, uri.port, *proxy_params).tap do |https|
|
55
|
+
https.use_ssl = uri.is_a?(URI::HTTPS)
|
56
|
+
if @config.timeout
|
57
|
+
https.open_timeout = @config.timeout
|
58
|
+
https.read_timeout = @config.timeout
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_request(uri, data)
|
64
|
+
req =
|
65
|
+
if @method == :put
|
66
|
+
Net::HTTP::Put.new(uri.request_uri)
|
67
|
+
else
|
68
|
+
Net::HTTP::Post.new(uri.request_uri)
|
69
|
+
end
|
70
|
+
|
71
|
+
build_request_body(req, data)
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_request_body(req, data)
|
75
|
+
req.body = data.to_json
|
76
|
+
|
77
|
+
req['Authorization'] = "Bearer #{@config.project_key}"
|
78
|
+
req['Content-Type'] = CONTENT_TYPE
|
79
|
+
req['User-Agent'] =
|
80
|
+
"#{Airbrake::Notice::NOTIFIER[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
|
81
|
+
" Ruby/#{RUBY_VERSION}"
|
82
|
+
|
83
|
+
req
|
84
|
+
end
|
85
|
+
|
86
|
+
def proxy_params
|
87
|
+
return unless @config.proxy.key?(:host)
|
88
|
+
|
89
|
+
[@config.proxy[:host], @config.proxy[:port], @config.proxy[:user],
|
90
|
+
@config.proxy[:password]]
|
91
|
+
end
|
92
|
+
|
93
|
+
def rate_limited_ip?(promise)
|
94
|
+
rate_limited = Time.now < @rate_limit_reset
|
95
|
+
promise.reject("#{LOG_LABEL} IP is rate limited") if rate_limited
|
96
|
+
rate_limited
|
97
|
+
end
|
98
|
+
|
99
|
+
def missing_body?(req, promise)
|
100
|
+
missing = req.body.nil?
|
101
|
+
|
102
|
+
if missing
|
103
|
+
reason = "#{LOG_LABEL} data was not sent because of missing body"
|
104
|
+
@config.logger.error(reason)
|
105
|
+
promise.reject(reason)
|
106
|
+
end
|
107
|
+
|
108
|
+
missing
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|