airbrake-ruby 2.3.2 → 2.4.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 +4 -4
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +4 -1
- data/lib/airbrake-ruby/filters/keys_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +4 -1
- data/lib/airbrake-ruby/notice.rb +4 -2
- data/lib/airbrake-ruby/response.rb +18 -2
- data/lib/airbrake-ruby/sync_sender.rb +27 -6
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/notice_spec.rb +5 -0
- data/spec/notifier_spec.rb +9 -5
- data/spec/spec_helper.rb +4 -4
- data/spec/sync_sender_spec.rb +36 -8
- data/spec/truncator_spec.rb +0 -2
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c845fb70bdbfade4ce4922090cf89ddb1d31dc8b
|
4
|
+
data.tar.gz: 322b65cb8938b1f8d102736cf0f95ee0a68bb3e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 833738116b0076013905ebe90ac3fe582a3302682fc72037bdf17b1bd7e1eef4879ae3f58c1570b4bb8aa36b548805706e734a92532cc5622647affbfc25258f
|
7
|
+
data.tar.gz: fc6b031950da579efea3de3ea1bd78e08fef5dbf9793a12e39ed6318f8d17d578820c35bee1a99f9248c3a2153effd07ab16ef7f2349ee9e72a0334ed211901f
|
@@ -5,7 +5,10 @@ module Airbrake
|
|
5
5
|
# list of parameters in the payload of a notice.
|
6
6
|
#
|
7
7
|
# @example
|
8
|
-
# filter = Airbrake::Filters::KeysBlacklist.new(
|
8
|
+
# filter = Airbrake::Filters::KeysBlacklist.new(
|
9
|
+
# Logger.new(STDOUT),
|
10
|
+
# [:email, /credit/i, 'password']
|
11
|
+
# )
|
9
12
|
# airbrake.add_filter(filter)
|
10
13
|
# airbrake.notify(StandardError.new('App crashed!'), {
|
11
14
|
# user: 'John'
|
@@ -31,6 +31,7 @@ module Airbrake
|
|
31
31
|
# Creates a new KeysBlacklist or KeysWhitelist filter that uses the given
|
32
32
|
# +patterns+ for filtering a notice's payload.
|
33
33
|
#
|
34
|
+
# @param [Logger, #error] logger
|
34
35
|
# @param [Array<String,Regexp,Symbol>] patterns
|
35
36
|
def initialize(logger, patterns)
|
36
37
|
@logger = logger
|
@@ -5,7 +5,10 @@ module Airbrake
|
|
5
5
|
# notice, but specified keys.
|
6
6
|
#
|
7
7
|
# @example
|
8
|
-
# filter = Airbrake::Filters::
|
8
|
+
# filter = Airbrake::Filters::KeysBlacklist.new(
|
9
|
+
# Logger.new(STDOUT),
|
10
|
+
# [:email, /credit/i, 'password']
|
11
|
+
# )
|
9
12
|
# airbrake.add_filter(filter)
|
10
13
|
# airbrake.notify(StandardError.new('App crashed!'), {
|
11
14
|
# user: 'John',
|
data/lib/airbrake-ruby/notice.rb
CHANGED
@@ -70,7 +70,9 @@ module Airbrake
|
|
70
70
|
@payload = {
|
71
71
|
errors: NestedException.new(exception, @config.logger).as_json,
|
72
72
|
context: context,
|
73
|
-
environment: {
|
73
|
+
environment: {
|
74
|
+
program_name: $PROGRAM_NAME
|
75
|
+
},
|
74
76
|
session: {},
|
75
77
|
params: params
|
76
78
|
}
|
@@ -198,7 +200,7 @@ module Airbrake
|
|
198
200
|
|
199
201
|
begin
|
200
202
|
attributes = exception.to_airbrake
|
201
|
-
rescue => ex
|
203
|
+
rescue StandardError => ex
|
202
204
|
@config.logger.error(
|
203
205
|
"#{LOG_LABEL} #{exception.class}#to_airbrake failed: #{ex.class}: #{ex}"
|
204
206
|
)
|
@@ -10,12 +10,17 @@ module Airbrake
|
|
10
10
|
# @return [Integer] the limit of the response body
|
11
11
|
TRUNCATE_LIMIT = 100
|
12
12
|
|
13
|
+
##
|
14
|
+
# @return [Integer] HTTP code returned when an IP sends over 10k/min notices
|
15
|
+
TOO_MANY_REQUESTS = 429
|
16
|
+
|
13
17
|
##
|
14
18
|
# Parses HTTP responses from the Airbrake API.
|
15
19
|
#
|
16
20
|
# @param [Net::HTTPResponse] response
|
17
21
|
# @param [Logger] logger
|
18
22
|
# @return [Hash{String=>String}] parsed response
|
23
|
+
# rubocop:disable Metrics/MethodLength
|
19
24
|
def self.parse(response, logger)
|
20
25
|
code = response.code.to_i
|
21
26
|
body = response.body
|
@@ -26,21 +31,27 @@ module Airbrake
|
|
26
31
|
parsed_body = JSON.parse(body)
|
27
32
|
logger.debug("#{LOG_LABEL} #{parsed_body}")
|
28
33
|
parsed_body
|
29
|
-
when 400, 401, 403,
|
34
|
+
when 400, 401, 403, 420
|
30
35
|
parsed_body = JSON.parse(body)
|
31
36
|
logger.error("#{LOG_LABEL} #{parsed_body['message']}")
|
32
37
|
parsed_body
|
38
|
+
when TOO_MANY_REQUESTS
|
39
|
+
parsed_body = JSON.parse(body)
|
40
|
+
msg = "#{LOG_LABEL} #{parsed_body['message']}"
|
41
|
+
logger.error(msg)
|
42
|
+
{ 'error' => msg, 'rate_limit_reset' => rate_limit_reset(response) }
|
33
43
|
else
|
34
44
|
body_msg = truncated_body(body)
|
35
45
|
logger.error("#{LOG_LABEL} unexpected code (#{code}). Body: #{body_msg}")
|
36
46
|
{ 'error' => body_msg }
|
37
47
|
end
|
38
|
-
rescue => ex
|
48
|
+
rescue StandardError => ex
|
39
49
|
body_msg = truncated_body(body)
|
40
50
|
logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}")
|
41
51
|
{ 'error' => ex.inspect }
|
42
52
|
end
|
43
53
|
end
|
54
|
+
# rubocop:enable Metrics/MethodLength
|
44
55
|
|
45
56
|
def self.truncated_body(body)
|
46
57
|
if body.nil?
|
@@ -52,5 +63,10 @@ module Airbrake
|
|
52
63
|
end
|
53
64
|
end
|
54
65
|
private_class_method :truncated_body
|
66
|
+
|
67
|
+
def self.rate_limit_reset(response)
|
68
|
+
Time.now + response['X-RateLimit-Delay'].to_i
|
69
|
+
end
|
70
|
+
private_class_method :rate_limit_reset
|
55
71
|
end
|
56
72
|
end
|
@@ -14,6 +14,7 @@ module Airbrake
|
|
14
14
|
# @param [Airbrake::Config] config
|
15
15
|
def initialize(config)
|
16
16
|
@config = config
|
17
|
+
@rate_limit_reset = Time.now
|
17
18
|
end
|
18
19
|
|
19
20
|
##
|
@@ -23,26 +24,28 @@ module Airbrake
|
|
23
24
|
# @param [Airbrake::Notice] endpoint
|
24
25
|
# @return [Hash{String=>String}] the parsed HTTP response
|
25
26
|
def send(notice, promise, endpoint = @config.endpoint)
|
27
|
+
return promise if rate_limited_ip?(promise)
|
28
|
+
|
26
29
|
response = nil
|
27
30
|
req = build_post_request(endpoint, notice)
|
28
31
|
|
29
|
-
if req
|
30
|
-
reason = "#{LOG_LABEL} notice was not sent because of missing body"
|
31
|
-
@config.logger.error(reason)
|
32
|
-
return promise.reject(reason)
|
33
|
-
end
|
32
|
+
return promise if missing_body?(req, promise)
|
34
33
|
|
35
34
|
https = build_https(endpoint)
|
36
35
|
|
37
36
|
begin
|
38
37
|
response = https.request(req)
|
39
|
-
rescue => ex
|
38
|
+
rescue StandardError => ex
|
40
39
|
reason = "#{LOG_LABEL} HTTP error: #{ex}"
|
41
40
|
@config.logger.error(reason)
|
42
41
|
return promise.reject(reason)
|
43
42
|
end
|
44
43
|
|
45
44
|
parsed_resp = Response.parse(response, @config.logger)
|
45
|
+
if parsed_resp.key?('rate_limit_reset')
|
46
|
+
@rate_limit_reset = parsed_resp['rate_limit_reset']
|
47
|
+
end
|
48
|
+
|
46
49
|
return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
|
47
50
|
promise.resolve(parsed_resp)
|
48
51
|
end
|
@@ -76,5 +79,23 @@ module Airbrake
|
|
76
79
|
[@config.proxy[:host], @config.proxy[:port], @config.proxy[:user],
|
77
80
|
@config.proxy[:password]]
|
78
81
|
end
|
82
|
+
|
83
|
+
def rate_limited_ip?(promise)
|
84
|
+
rate_limited = Time.now < @rate_limit_reset
|
85
|
+
promise.reject("#{LOG_LABEL} IP is rate limited") if rate_limited
|
86
|
+
rate_limited
|
87
|
+
end
|
88
|
+
|
89
|
+
def missing_body?(req, promise)
|
90
|
+
missing = req.body.nil?
|
91
|
+
|
92
|
+
if missing
|
93
|
+
reason = "#{LOG_LABEL} notice was not sent because of missing body"
|
94
|
+
@config.logger.error(reason)
|
95
|
+
promise.reject(reason)
|
96
|
+
end
|
97
|
+
|
98
|
+
missing
|
99
|
+
end
|
79
100
|
end
|
80
101
|
end
|
data/spec/notice_spec.rb
CHANGED
@@ -204,6 +204,11 @@ RSpec.describe Airbrake::Notice do
|
|
204
204
|
it "defaults to the error severity" do
|
205
205
|
expect(notice.to_json).to match(/"context":{.*"severity":"error".*}/)
|
206
206
|
end
|
207
|
+
|
208
|
+
it "always contains environment/program_name" do
|
209
|
+
expect(notice.to_json).
|
210
|
+
to match(%r|"environment":{"program_name":.+/exe/rspec.*|)
|
211
|
+
end
|
207
212
|
end
|
208
213
|
|
209
214
|
describe "#[]" do
|
data/spec/notifier_spec.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
1
|
require 'spec_helper'
|
4
2
|
|
5
3
|
RSpec.describe Airbrake::Notifier do
|
@@ -225,9 +223,15 @@ RSpec.describe Airbrake::Notifier do
|
|
225
223
|
/ERROR -- : .+ Project not found or access denied./
|
226
224
|
end
|
227
225
|
|
228
|
-
context "
|
229
|
-
include_examples 'HTTP codes',
|
230
|
-
|
226
|
+
context "error 420" do
|
227
|
+
include_examples 'HTTP codes', 420,
|
228
|
+
'{"message":"Project is rate limited."}',
|
229
|
+
/ERROR -- : .+ Project is rate limited./
|
230
|
+
end
|
231
|
+
|
232
|
+
context "the IP rate limit message" do
|
233
|
+
include_examples 'HTTP codes', 429, '{"message": "IP is rate limited"}',
|
234
|
+
/ERROR -- : .+ IP is rate limited/
|
231
235
|
end
|
232
236
|
|
233
237
|
context "the internal server error" do
|
data/spec/spec_helper.rb
CHANGED
@@ -43,11 +43,11 @@ class AirbrakeTestError < RuntimeError
|
|
43
43
|
# rubocop:enable Metrics/LineLength
|
44
44
|
end
|
45
45
|
|
46
|
-
# rubocop:disable
|
46
|
+
# rubocop:disable Naming/AccessorMethodName
|
47
47
|
def set_backtrace(backtrace)
|
48
48
|
@backtrace = backtrace
|
49
49
|
end
|
50
|
-
# rubocop:enable
|
50
|
+
# rubocop:enable Naming/AccessorMethodName
|
51
51
|
|
52
52
|
def message
|
53
53
|
'App crashed!'
|
@@ -90,9 +90,9 @@ class Ruby21Error < RuntimeError
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
puts <<
|
93
|
+
puts <<BANNER
|
94
94
|
#{'#' * 80}
|
95
95
|
# RUBY_VERSION: #{RUBY_VERSION}
|
96
96
|
# RUBY_ENGINE: #{RUBY_ENGINE}
|
97
97
|
#{'#' * 80}
|
98
|
-
|
98
|
+
BANNER
|
data/spec/sync_sender_spec.rb
CHANGED
@@ -13,12 +13,12 @@ RSpec.describe Airbrake::SyncSender do
|
|
13
13
|
|
14
14
|
describe "#send" do
|
15
15
|
let(:promise) { Airbrake::Promise.new }
|
16
|
+
let(:stdout) { StringIO.new }
|
17
|
+
let(:config) { Airbrake::Config.new(logger: Logger.new(stdout)) }
|
18
|
+
let(:sender) { described_class.new(config) }
|
19
|
+
let(:notice) { Airbrake::Notice.new(config, AirbrakeTestError.new) }
|
16
20
|
|
17
21
|
it "catches exceptions raised while sending" do
|
18
|
-
stdout = StringIO.new
|
19
|
-
config = Airbrake::Config.new(logger: Logger.new(stdout))
|
20
|
-
sender = described_class.new(config)
|
21
|
-
notice = Airbrake::Notice.new(config, AirbrakeTestError.new)
|
22
22
|
https = double("foo")
|
23
23
|
allow(sender).to receive(:build_https).and_return(https)
|
24
24
|
allow(https).to receive(:request).and_raise(StandardError.new('foo'))
|
@@ -40,10 +40,6 @@ RSpec.describe Airbrake::SyncSender do
|
|
40
40
|
10.times { backtrace << "bin/rails:3:in `<#{bad_string}>'" }
|
41
41
|
ex.set_backtrace(backtrace)
|
42
42
|
|
43
|
-
stdout = StringIO.new
|
44
|
-
config = Airbrake::Config.new(logger: Logger.new(stdout))
|
45
|
-
|
46
|
-
sender = described_class.new(config)
|
47
43
|
notice = Airbrake::Notice.new(config, ex)
|
48
44
|
|
49
45
|
expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
|
@@ -52,5 +48,37 @@ RSpec.describe Airbrake::SyncSender do
|
|
52
48
|
expect(stdout.string).to match(/ERROR -- : .+ notice was not sent/)
|
53
49
|
end
|
54
50
|
end
|
51
|
+
|
52
|
+
context "when IP is rate limited" do
|
53
|
+
let(:endpoint) { %r{https://airbrake.io/api/v3/projects/notices} }
|
54
|
+
|
55
|
+
before do
|
56
|
+
stub_request(:post, endpoint).to_return(
|
57
|
+
status: 429,
|
58
|
+
body: '{"message":"IP is rate limited"}',
|
59
|
+
headers: { 'X-RateLimit-Delay' => '1' }
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns error" do
|
64
|
+
p1 = Airbrake::Promise.new
|
65
|
+
sender.send(notice, p1)
|
66
|
+
expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
|
67
|
+
|
68
|
+
p2 = Airbrake::Promise.new
|
69
|
+
sender.send(notice, p2)
|
70
|
+
expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
|
71
|
+
|
72
|
+
# Wait for X-RateLimit-Delay and then make a new request to make sure p2
|
73
|
+
# was ignored (no request made for it).
|
74
|
+
sleep 1
|
75
|
+
|
76
|
+
p3 = Airbrake::Promise.new
|
77
|
+
sender.send(notice, p3)
|
78
|
+
expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
|
79
|
+
|
80
|
+
expect(a_request(:post, endpoint)).to have_been_made.twice
|
81
|
+
end
|
82
|
+
end
|
55
83
|
end
|
56
84
|
end
|
data/spec/truncator_spec.rb
CHANGED
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.4.0
|
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: 2017-
|
11
|
+
date: 2017-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -86,14 +86,34 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
89
|
+
version: '0.50'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
96
|
+
version: '0.50'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: public_suffix
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
- - "<"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '3.0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '2.0'
|
114
|
+
- - "<"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '3.0'
|
97
117
|
description: |
|
98
118
|
Airbrake Ruby is a plain Ruby notifier for Airbrake (https://airbrake.io), the
|
99
119
|
leading exception reporting service. Airbrake Ruby provides minimalist API that
|