airbrake-ruby 2.12.0 → 2.13.0.pre.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 +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
|