airbrake-ruby 2.12.0 → 2.13.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +27 -0
- data/lib/airbrake-ruby/config.rb +7 -0
- data/lib/airbrake-ruby/notifier.rb +7 -0
- data/lib/airbrake-ruby/response.rb +6 -3
- data/lib/airbrake-ruby/route_sender.rb +106 -0
- data/lib/airbrake-ruby/sync_sender.rb +30 -17
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +10 -0
- data/spec/async_sender_spec.rb +2 -2
- data/spec/config_spec.rb +4 -0
- data/spec/notifier_spec.rb +10 -0
- data/spec/response_spec.rb +6 -4
- data/spec/route_sender_spec.rb +86 -0
- data/spec/sync_sender_spec.rb +29 -11
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ae033f8b21b79ecf3902d570014f379e76b4c16
|
4
|
+
data.tar.gz: 131f71f8a92906a05aadf1ed3dbd4ffc35753162
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62a0b41680b377eefd25a0e9433ada861ce92841d954477de949f3ee16bbb32a198d3133433b0783e7e4f747a7c7bff39e76dcf4744bfbb8da8fffbcd54c4a6d
|
7
|
+
data.tar.gz: 6864d904a38696d892dcca249ddeff4b1c973e61735ef792a19518bf1e17c430fc9f1bd09ea59fe4c6d7685d6d1a1e911848a657a6647199bb3208d25183192c
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
require 'thread'
|
5
5
|
require 'set'
|
6
6
|
require 'socket'
|
7
|
+
require 'time'
|
7
8
|
|
8
9
|
require 'airbrake-ruby/version'
|
9
10
|
require 'airbrake-ruby/config'
|
@@ -33,6 +34,7 @@ require 'airbrake-ruby/filter_chain'
|
|
33
34
|
require 'airbrake-ruby/notifier'
|
34
35
|
require 'airbrake-ruby/code_hunk'
|
35
36
|
require 'airbrake-ruby/file_cache'
|
37
|
+
require 'airbrake-ruby/route_sender'
|
36
38
|
|
37
39
|
# This module defines the Airbrake API. The user is meant to interact with
|
38
40
|
# Airbrake via its public class methods. Before using the library, you must to
|
@@ -115,6 +117,9 @@ module Airbrake
|
|
115
117
|
|
116
118
|
# @macro see_public_api_method
|
117
119
|
def merge_context(_context); end
|
120
|
+
|
121
|
+
# @macro see_public_api_method
|
122
|
+
def inc_request(method, route, status_code, dur, time); end
|
118
123
|
end
|
119
124
|
|
120
125
|
# A Hash that holds all notifiers. The keys of the Hash are notifier
|
@@ -343,5 +348,27 @@ module Airbrake
|
|
343
348
|
def merge_context(context)
|
344
349
|
@notifiers[:default].merge_context(context)
|
345
350
|
end
|
351
|
+
|
352
|
+
# Increments request count of a certain +route+ that was invoked with
|
353
|
+
# +method+, and returned +status_code+ at +time+ and took +dur+
|
354
|
+
# milliseconds.
|
355
|
+
#
|
356
|
+
# After a certain amount of time (n seconds) the aggregated route
|
357
|
+
# information will be sent to Airbrake.
|
358
|
+
#
|
359
|
+
# @example
|
360
|
+
# Airbrake.inc_request('POST', '/thing/:id/create', 200, 123, Time.now)
|
361
|
+
#
|
362
|
+
# @param [String] method The HTTP method that was invoked
|
363
|
+
# @param [String] route The route that was invoked
|
364
|
+
# @param [Integer] status_code The respose code that the route returned
|
365
|
+
# @param [Float] dur How much time the processing of the request took in
|
366
|
+
# milliseconds
|
367
|
+
# @param [Time] time When the request happened
|
368
|
+
# @return [void]
|
369
|
+
# @since v2.13.0
|
370
|
+
def inc_request(method, route, status_code, dur, time)
|
371
|
+
@notifiers[:default].inc_request(method, route, status_code, dur, time)
|
372
|
+
end
|
346
373
|
end
|
347
374
|
end
|
data/lib/airbrake-ruby/config.rb
CHANGED
@@ -83,6 +83,12 @@ module Airbrake
|
|
83
83
|
# @since v2.5.0
|
84
84
|
attr_accessor :code_hunks
|
85
85
|
|
86
|
+
# @return [Integer] how many seconds to wait before sending collected route
|
87
|
+
# stats
|
88
|
+
# @api public
|
89
|
+
# @since v2.13.0
|
90
|
+
attr_accessor :route_stats_flush_period
|
91
|
+
|
86
92
|
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
87
93
|
# config
|
88
94
|
def initialize(user_config = {})
|
@@ -113,6 +119,7 @@ module Airbrake
|
|
113
119
|
)
|
114
120
|
|
115
121
|
self.versions = {}
|
122
|
+
self.route_stats_flush_period = 15
|
116
123
|
|
117
124
|
merge(user_config)
|
118
125
|
end
|
@@ -36,6 +36,8 @@ module Airbrake
|
|
36
36
|
|
37
37
|
@async_sender = AsyncSender.new(@config)
|
38
38
|
@sync_sender = SyncSender.new(@config)
|
39
|
+
|
40
|
+
@route_sender = RouteSender.new(@config)
|
39
41
|
end
|
40
42
|
|
41
43
|
# @macro see_public_api_method
|
@@ -95,6 +97,11 @@ module Airbrake
|
|
95
97
|
@context.merge!(context)
|
96
98
|
end
|
97
99
|
|
100
|
+
# @macro see_public_api_method
|
101
|
+
def inc_request(*args)
|
102
|
+
@route_sender.inc_request(*args)
|
103
|
+
end
|
104
|
+
|
98
105
|
private
|
99
106
|
|
100
107
|
def convert_to_exception(ex)
|
@@ -16,16 +16,19 @@ module Airbrake
|
|
16
16
|
# @param [Net::HTTPResponse] response
|
17
17
|
# @param [Logger] logger
|
18
18
|
# @return [Hash{String=>String}] parsed response
|
19
|
-
# rubocop:disable Metrics/MethodLength
|
19
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
20
20
|
def self.parse(response, logger)
|
21
21
|
code = response.code.to_i
|
22
22
|
body = response.body
|
23
23
|
|
24
24
|
begin
|
25
25
|
case code
|
26
|
+
when 200
|
27
|
+
logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}")
|
28
|
+
{ response.msg => response.body }
|
26
29
|
when 201
|
27
30
|
parsed_body = JSON.parse(body)
|
28
|
-
logger.debug("#{LOG_LABEL} #{parsed_body}")
|
31
|
+
logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}")
|
29
32
|
parsed_body
|
30
33
|
when 400, 401, 403, 420
|
31
34
|
parsed_body = JSON.parse(body)
|
@@ -47,7 +50,7 @@ module Airbrake
|
|
47
50
|
{ 'error' => ex.inspect }
|
48
51
|
end
|
49
52
|
end
|
50
|
-
# rubocop:enable Metrics/MethodLength
|
53
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
51
54
|
|
52
55
|
def self.truncated_body(body)
|
53
56
|
if body.nil?
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# RouteSender aggregates information about requests and periodically sends
|
3
|
+
# collected data to Airbrake.
|
4
|
+
# @since v2.13.0
|
5
|
+
class RouteSender
|
6
|
+
# The key that represents a route.
|
7
|
+
RouteKey = Struct.new(:method, :route, :statusCode, :time)
|
8
|
+
|
9
|
+
# RouteStat holds data that describes a route's performance.
|
10
|
+
RouteStat = Struct.new(:count, :sum, :sumsq, :min, :max) do
|
11
|
+
# @param [Integer] count The number of requests
|
12
|
+
# @param [Float] sum The sum of request duration in milliseconds
|
13
|
+
# @param [Float] sumsq The squared sum of request duration in milliseconds
|
14
|
+
# @param [Float] min The minimal request duration
|
15
|
+
# @param [Float] max The maximum request duration
|
16
|
+
def initialize(count: 0, sum: 0.0, sumsq: 0.0, min: 0.0, max: 0.0)
|
17
|
+
super(count, sum, sumsq, min, max)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Airbrake::Config] config
|
22
|
+
def initialize(config)
|
23
|
+
@config = config
|
24
|
+
@flush_period = config.route_stats_flush_period
|
25
|
+
@sender = SyncSender.new(config, :put)
|
26
|
+
@routes = {}
|
27
|
+
@thread = nil
|
28
|
+
@mutex = Mutex.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# @macro see_public_api_method
|
32
|
+
def inc_request(method, route, status_code, dur, tm)
|
33
|
+
route = create_route_key(method, route, status_code, tm)
|
34
|
+
|
35
|
+
promise = Airbrake::Promise.new
|
36
|
+
|
37
|
+
@mutex.synchronize do
|
38
|
+
@routes[route] ||= RouteStat.new
|
39
|
+
increment_stats(@routes[route], dur)
|
40
|
+
|
41
|
+
if @flush_period > 0
|
42
|
+
schedule_flush(promise)
|
43
|
+
else
|
44
|
+
send(@routes, promise)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
promise
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_route_key(method, route, status_code, tm)
|
54
|
+
# rubocop:disable Style/DateTime
|
55
|
+
time = DateTime.new(
|
56
|
+
tm.year, tm.month, tm.day, tm.hour, tm.min, 0, tm.zone || 0
|
57
|
+
)
|
58
|
+
# rubocop:enable Style/DateTime
|
59
|
+
RouteKey.new(method, route, status_code, time.rfc3339)
|
60
|
+
end
|
61
|
+
|
62
|
+
def increment_stats(stat, dur)
|
63
|
+
stat.count += 1
|
64
|
+
|
65
|
+
ms = dur.to_f
|
66
|
+
stat.sum += ms
|
67
|
+
stat.sumsq += ms * ms
|
68
|
+
|
69
|
+
stat.min = ms if ms < stat.min || stat.min == 0
|
70
|
+
stat.max = ms if ms > stat.max
|
71
|
+
end
|
72
|
+
|
73
|
+
def schedule_flush(promise)
|
74
|
+
@thread ||= Thread.new do
|
75
|
+
sleep(@flush_period)
|
76
|
+
|
77
|
+
routes = nil
|
78
|
+
@mutex.synchronize do
|
79
|
+
routes = @routes
|
80
|
+
@routes = {}
|
81
|
+
@thread = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
send(routes, promise)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Setting a name is needed to test the timer.
|
88
|
+
# Ruby <=2.2 doesn't support Thread#name, so we have this check.
|
89
|
+
@thread.name = 'route-stat-thread' if @thread.respond_to?(:name)
|
90
|
+
end
|
91
|
+
|
92
|
+
def send(routes, promise)
|
93
|
+
if routes.none?
|
94
|
+
raise "#{self.class.name}##{__method__}: routes cannot be empty. Race?"
|
95
|
+
end
|
96
|
+
|
97
|
+
@config.logger.debug("#{LOG_LABEL} RouteStats#send: #{routes}")
|
98
|
+
|
99
|
+
@sender.send(
|
100
|
+
{ routes: routes.map { |k, v| k.to_h.merge(v.to_h) } },
|
101
|
+
promise,
|
102
|
+
URI.join(@config.host, "api/v4/projects/#{@config.project_id}/routes-stats")
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Airbrake
|
2
|
-
# Responsible for sending
|
2
|
+
# Responsible for sending data to Airbrake synchronously via PUT or POST
|
3
|
+
# methods. Supports proxies.
|
3
4
|
#
|
4
5
|
# @see AsyncSender
|
5
6
|
# @api private
|
@@ -9,21 +10,22 @@ module Airbrake
|
|
9
10
|
CONTENT_TYPE = 'application/json'.freeze
|
10
11
|
|
11
12
|
# @param [Airbrake::Config] config
|
12
|
-
def initialize(config)
|
13
|
+
def initialize(config, method = :post)
|
13
14
|
@config = config
|
15
|
+
@method = method
|
14
16
|
@rate_limit_reset = Time.now
|
15
17
|
end
|
16
18
|
|
17
|
-
# Sends a POST request to the given +endpoint+ with the +
|
19
|
+
# Sends a POST or PUT request to the given +endpoint+ with the +data+ payload.
|
18
20
|
#
|
19
|
-
# @param [
|
20
|
-
# @param [
|
21
|
+
# @param [#to_json] data
|
22
|
+
# @param [URI::HTTPS] endpoint
|
21
23
|
# @return [Hash{String=>String}] the parsed HTTP response
|
22
|
-
def send(
|
24
|
+
def send(data, promise, endpoint = @config.endpoint)
|
23
25
|
return promise if rate_limited_ip?(promise)
|
24
26
|
|
25
27
|
response = nil
|
26
|
-
req =
|
28
|
+
req = build_request(endpoint, data)
|
27
29
|
|
28
30
|
return promise if missing_body?(req, promise)
|
29
31
|
|
@@ -58,16 +60,27 @@ module Airbrake
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
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
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
71
84
|
end
|
72
85
|
|
73
86
|
def proxy_params
|
@@ -87,7 +100,7 @@ module Airbrake
|
|
87
100
|
missing = req.body.nil?
|
88
101
|
|
89
102
|
if missing
|
90
|
-
reason = "#{LOG_LABEL}
|
103
|
+
reason = "#{LOG_LABEL} data was not sent because of missing body"
|
91
104
|
@config.logger.error(reason)
|
92
105
|
promise.reject(reason)
|
93
106
|
end
|
data/spec/airbrake_spec.rb
CHANGED
@@ -110,4 +110,14 @@ RSpec.describe Airbrake do
|
|
110
110
|
described_class.merge_context(foo: 'bar')
|
111
111
|
end
|
112
112
|
end
|
113
|
+
|
114
|
+
describe ".inc_request" do
|
115
|
+
it "forwards 'inc_request' to the notifier" do
|
116
|
+
t = Time.now
|
117
|
+
expect(default_notifier).to receive(:inc_request).with(
|
118
|
+
'GET', '/foo', 200, 1000, t
|
119
|
+
)
|
120
|
+
described_class.inc_request('GET', '/foo', 200, 1000, t)
|
121
|
+
end
|
122
|
+
end
|
113
123
|
end
|
data/spec/async_sender_spec.rb
CHANGED
@@ -21,7 +21,7 @@ RSpec.describe Airbrake::AsyncSender do
|
|
21
21
|
sender.close
|
22
22
|
|
23
23
|
log = stdout.string.split("\n")
|
24
|
-
notices_sent = log.grep(/\*\*Airbrake: \{\}/).size
|
24
|
+
notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
|
25
25
|
notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
|
26
26
|
expect(notices_sent).to be >= queue_size
|
27
27
|
expect(notices_sent + notices_dropped).to eq(notices_count)
|
@@ -60,7 +60,7 @@ RSpec.describe Airbrake::AsyncSender do
|
|
60
60
|
|
61
61
|
it "prints the correct number of log messages" do
|
62
62
|
log = @stderr.string.split("\n")
|
63
|
-
notices_sent = log.grep(/\*\*Airbrake: \{\}/).size
|
63
|
+
notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
|
64
64
|
notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
|
65
65
|
expect(notices_sent).to be >= @sender.instance_variable_get(:@unsent).max
|
66
66
|
expect(notices_sent + notices_dropped).to eq(300)
|
data/spec/config_spec.rb
CHANGED
@@ -78,6 +78,10 @@ RSpec.describe Airbrake::Config do
|
|
78
78
|
it "doesn't set default whitelist" do
|
79
79
|
expect(config.whitelist_keys).to be_empty
|
80
80
|
end
|
81
|
+
|
82
|
+
it "sets the default route_stats_flush_period" do
|
83
|
+
expect(config.route_stats_flush_period).to eq(15)
|
84
|
+
end
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
data/spec/notifier_spec.rb
CHANGED
@@ -449,5 +449,15 @@ RSpec.describe Airbrake::Notifier do
|
|
449
449
|
subject.merge_context(apples: 'oranges')
|
450
450
|
end
|
451
451
|
end
|
452
|
+
|
453
|
+
describe "#inc_request" do
|
454
|
+
it "forwards 'inc_request' to RouteSender" do
|
455
|
+
t = Time.now
|
456
|
+
expect_any_instance_of(Airbrake::RouteSender).to receive(:inc_request).with(
|
457
|
+
'GET', '/foo', 200, 1000, t
|
458
|
+
)
|
459
|
+
subject.inc_request('GET', '/foo', 200, 1000, t)
|
460
|
+
end
|
461
|
+
end
|
452
462
|
end
|
453
463
|
# rubocop:enable Layout/DotPosition
|
data/spec/response_spec.rb
CHANGED
@@ -5,10 +5,12 @@ RSpec.describe Airbrake::Response do
|
|
5
5
|
let(:out) { StringIO.new }
|
6
6
|
let(:logger) { Logger.new(out) }
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
[200, 201].each do |code|
|
9
|
+
context "when response code is #{code}" do
|
10
|
+
it "logs response body" do
|
11
|
+
described_class.parse(OpenStruct.new(code: code, body: '{}'), logger)
|
12
|
+
expect(out.string).to match(/Airbrake: Airbrake::Response \(#{code}\): {}/)
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Airbrake::RouteSender do
|
4
|
+
let(:endpoint) { 'https://api.airbrake.io/api/v4/projects/1/routes-stats' }
|
5
|
+
|
6
|
+
let(:config) do
|
7
|
+
Airbrake::Config.new(
|
8
|
+
project_id: 1,
|
9
|
+
project_key: 'banana',
|
10
|
+
route_stats_flush_period: 0.1
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
subject { described_class.new(config) }
|
15
|
+
|
16
|
+
describe "#inc_request" do
|
17
|
+
before do
|
18
|
+
stub_request(:put, endpoint).to_return(status: 200, body: '')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Let the request finish.
|
22
|
+
after { sleep 0.2 }
|
23
|
+
|
24
|
+
it "rounds time to the floor minute" do
|
25
|
+
subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
|
26
|
+
sleep 0.2
|
27
|
+
expect(
|
28
|
+
a_request(:put, endpoint).with(body: /"time":"2018-01-01T00:00:00\+00:00"/)
|
29
|
+
).to have_been_made
|
30
|
+
end
|
31
|
+
|
32
|
+
it "increments routes with the same key" do
|
33
|
+
subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
|
34
|
+
subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 50, 0))
|
35
|
+
sleep 0.2
|
36
|
+
expect(
|
37
|
+
a_request(:put, endpoint).with(body: /"count":2/)
|
38
|
+
).to have_been_made
|
39
|
+
end
|
40
|
+
|
41
|
+
it "groups routes by time" do
|
42
|
+
subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
|
43
|
+
subject.inc_request('GET', '/foo', 200, 10, Time.new(2018, 1, 1, 0, 1, 20, 0))
|
44
|
+
sleep 0.2
|
45
|
+
expect(
|
46
|
+
a_request(:put, endpoint).with(
|
47
|
+
body: %r|\A
|
48
|
+
{"routes":\[
|
49
|
+
{"method":"GET","route":"/foo","statusCode":200,
|
50
|
+
"time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
|
51
|
+
"sumsq":576.0,"min":24.0,"max":24.0},
|
52
|
+
{"method":"GET","route":"/foo","statusCode":200,
|
53
|
+
"time":"2018-01-01T00:01:00\+00:00","count":1,"sum":10.0,
|
54
|
+
"sumsq":100.0,"min":10.0,"max":10.0}\]}
|
55
|
+
\z|x
|
56
|
+
)
|
57
|
+
).to have_been_made
|
58
|
+
end
|
59
|
+
|
60
|
+
it "groups routes by route key" do
|
61
|
+
subject.inc_request('GET', '/foo', 200, 24, Time.new(2018, 1, 1, 0, 0, 20, 0))
|
62
|
+
subject.inc_request('POST', '/foo', 200, 10, Time.new(2018, 1, 1, 0, 0, 20, 0))
|
63
|
+
sleep 0.2
|
64
|
+
expect(
|
65
|
+
a_request(:put, endpoint).with(
|
66
|
+
body: %r|\A
|
67
|
+
{"routes":\[
|
68
|
+
{"method":"GET","route":"/foo","statusCode":200,
|
69
|
+
"time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
|
70
|
+
"sumsq":576.0,"min":24.0,"max":24.0},
|
71
|
+
{"method":"POST","route":"/foo","statusCode":200,
|
72
|
+
"time":"2018-01-01T00:00:00\+00:00","count":1,"sum":10.0,
|
73
|
+
"sumsq":100.0,"min":10.0,"max":10.0}\]}
|
74
|
+
\z|x
|
75
|
+
)
|
76
|
+
).to have_been_made
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns a promise" do
|
80
|
+
promise = subject.inc_request('GET', '/foo', 200, 24, Time.new)
|
81
|
+
sleep 0.2
|
82
|
+
expect(promise).to be_an(Airbrake::Promise)
|
83
|
+
expect(promise.value).to eq('' => nil)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/sync_sender_spec.rb
CHANGED
@@ -30,7 +30,7 @@ RSpec.describe Airbrake::SyncSender do
|
|
30
30
|
before { stub_request(:post, endpoint).to_return(body: '{}') }
|
31
31
|
|
32
32
|
it "sets the Content-Type header to JSON" do
|
33
|
-
sender.send(
|
33
|
+
sender.send({}, promise)
|
34
34
|
expect(
|
35
35
|
a_request(:post, endpoint).with(
|
36
36
|
headers: { 'Content-Type' => 'application/json' }
|
@@ -39,18 +39,18 @@ RSpec.describe Airbrake::SyncSender do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it "sets the User-Agent header to the notifier slug" do
|
42
|
-
sender.send(
|
42
|
+
sender.send({}, promise)
|
43
43
|
expect(
|
44
44
|
a_request(:post, endpoint).with(
|
45
45
|
headers: {
|
46
|
-
'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d
|
46
|
+
'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d+.+ Ruby/\d+\.\d+\.\d+}
|
47
47
|
}
|
48
48
|
)
|
49
49
|
).to have_been_made.once
|
50
50
|
end
|
51
51
|
|
52
52
|
it "sets the Authorization header to the project key" do
|
53
|
-
sender.send(
|
53
|
+
sender.send({}, promise)
|
54
54
|
expect(
|
55
55
|
a_request(:post, endpoint).with(
|
56
56
|
headers: { 'Authorization' => 'Bearer banana' }
|
@@ -62,13 +62,13 @@ RSpec.describe Airbrake::SyncSender do
|
|
62
62
|
https = double("foo")
|
63
63
|
allow(sender).to receive(:build_https).and_return(https)
|
64
64
|
allow(https).to receive(:request).and_raise(StandardError.new('foo'))
|
65
|
-
expect(sender.send(
|
65
|
+
expect(sender.send({}, promise)).to be_an(Airbrake::Promise)
|
66
66
|
expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
|
67
67
|
expect(stdout.string).to match(/ERROR -- : .+ HTTP error: foo/)
|
68
68
|
end
|
69
69
|
|
70
70
|
context "when request body is nil" do
|
71
|
-
it "doesn't send
|
71
|
+
it "doesn't send data" do
|
72
72
|
expect_any_instance_of(Airbrake::Truncator).
|
73
73
|
to receive(:reduce_max_size).and_return(0)
|
74
74
|
|
@@ -84,8 +84,8 @@ RSpec.describe Airbrake::SyncSender do
|
|
84
84
|
|
85
85
|
expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
|
86
86
|
expect(promise.value).
|
87
|
-
to match('error' => '**Airbrake:
|
88
|
-
expect(stdout.string).to match(/ERROR -- : .+
|
87
|
+
to match('error' => '**Airbrake: data was not sent because of missing body')
|
88
|
+
expect(stdout.string).to match(/ERROR -- : .+ data was not sent/)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
@@ -102,11 +102,11 @@ RSpec.describe Airbrake::SyncSender do
|
|
102
102
|
|
103
103
|
it "returns error" do
|
104
104
|
p1 = Airbrake::Promise.new
|
105
|
-
sender.send(
|
105
|
+
sender.send({}, p1)
|
106
106
|
expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
|
107
107
|
|
108
108
|
p2 = Airbrake::Promise.new
|
109
|
-
sender.send(
|
109
|
+
sender.send({}, p2)
|
110
110
|
expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
|
111
111
|
|
112
112
|
# Wait for X-RateLimit-Delay and then make a new request to make sure p2
|
@@ -114,11 +114,29 @@ RSpec.describe Airbrake::SyncSender do
|
|
114
114
|
sleep 1
|
115
115
|
|
116
116
|
p3 = Airbrake::Promise.new
|
117
|
-
sender.send(
|
117
|
+
sender.send({}, p3)
|
118
118
|
expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
|
119
119
|
|
120
120
|
expect(a_request(:post, endpoint)).to have_been_made.twice
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
context "when the provided method is :put" do
|
125
|
+
before { stub_request(:put, endpoint).to_return(status: 200, body: '') }
|
126
|
+
|
127
|
+
it "PUTs the request" do
|
128
|
+
sender = described_class.new(config, :put)
|
129
|
+
sender.send({}, promise)
|
130
|
+
expect(a_request(:put, endpoint)).to have_been_made
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "when the provided method is :post" do
|
135
|
+
it "POSTs the request" do
|
136
|
+
sender = described_class.new(config, :post)
|
137
|
+
sender.send({}, promise)
|
138
|
+
expect(a_request(:post, endpoint)).to have_been_made
|
139
|
+
end
|
140
|
+
end
|
123
141
|
end
|
124
142
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.13.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Airbrake Technologies, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/airbrake-ruby/notifier.rb
|
142
142
|
- lib/airbrake-ruby/promise.rb
|
143
143
|
- lib/airbrake-ruby/response.rb
|
144
|
+
- lib/airbrake-ruby/route_sender.rb
|
144
145
|
- lib/airbrake-ruby/sync_sender.rb
|
145
146
|
- lib/airbrake-ruby/truncator.rb
|
146
147
|
- lib/airbrake-ruby/version.rb
|
@@ -177,6 +178,7 @@ files:
|
|
177
178
|
- spec/notifier_spec/options_spec.rb
|
178
179
|
- spec/promise_spec.rb
|
179
180
|
- spec/response_spec.rb
|
181
|
+
- spec/route_sender_spec.rb
|
180
182
|
- spec/spec_helper.rb
|
181
183
|
- spec/sync_sender_spec.rb
|
182
184
|
- spec/truncator_spec.rb
|
@@ -195,9 +197,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
195
197
|
version: '2.0'
|
196
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
199
|
requirements:
|
198
|
-
- - "
|
200
|
+
- - ">"
|
199
201
|
- !ruby/object:Gem::Version
|
200
|
-
version:
|
202
|
+
version: 1.3.1
|
201
203
|
requirements: []
|
202
204
|
rubyforge_project:
|
203
205
|
rubygems_version: 2.6.13
|
@@ -205,6 +207,7 @@ signing_key:
|
|
205
207
|
specification_version: 4
|
206
208
|
summary: Ruby notifier for https://airbrake.io
|
207
209
|
test_files:
|
210
|
+
- spec/route_sender_spec.rb
|
208
211
|
- spec/truncator_spec.rb
|
209
212
|
- spec/helpers.rb
|
210
213
|
- spec/filters/exception_attributes_filter_spec.rb
|