airbrake-ruby 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +40 -18
  3. data/lib/airbrake-ruby/async_sender.rb +0 -6
  4. data/lib/airbrake-ruby/backtrace.rb +0 -10
  5. data/lib/airbrake-ruby/code_hunk.rb +0 -4
  6. data/lib/airbrake-ruby/config.rb +23 -22
  7. data/lib/airbrake-ruby/config/validator.rb +0 -10
  8. data/lib/airbrake-ruby/file_cache.rb +0 -6
  9. data/lib/airbrake-ruby/filter_chain.rb +0 -5
  10. data/lib/airbrake-ruby/filters/context_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  12. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
  13. data/lib/airbrake-ruby/filters/gem_root_filter.rb +2 -3
  14. data/lib/airbrake-ruby/filters/keys_blacklist.rb +1 -2
  15. data/lib/airbrake-ruby/filters/keys_filter.rb +9 -14
  16. data/lib/airbrake-ruby/filters/keys_whitelist.rb +0 -2
  17. data/lib/airbrake-ruby/filters/root_directory_filter.rb +2 -3
  18. data/lib/airbrake-ruby/filters/system_exit_filter.rb +2 -3
  19. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -4
  20. data/lib/airbrake-ruby/nested_exception.rb +0 -2
  21. data/lib/airbrake-ruby/notice.rb +6 -44
  22. data/lib/airbrake-ruby/notifier.rb +4 -40
  23. data/lib/airbrake-ruby/promise.rb +0 -6
  24. data/lib/airbrake-ruby/response.rb +0 -4
  25. data/lib/airbrake-ruby/sync_sender.rb +0 -4
  26. data/lib/airbrake-ruby/version.rb +1 -3
  27. data/spec/airbrake_spec.rb +71 -140
  28. data/spec/async_sender_spec.rb +9 -0
  29. data/spec/config_spec.rb +4 -0
  30. data/spec/filters/dependency_filter_spec.rb +16 -0
  31. data/spec/filters/exception_attributes_filter_spec.rb +65 -0
  32. data/spec/filters/keys_whitelist_spec.rb +17 -23
  33. data/spec/notice_spec.rb +111 -69
  34. data/spec/notifier_spec.rb +304 -495
  35. data/spec/response_spec.rb +82 -0
  36. data/spec/sync_sender_spec.rb +31 -14
  37. metadata +10 -2
@@ -1,16 +1,14 @@
1
1
  module Airbrake
2
- ##
3
2
  # This class is reponsible for sending notices to Airbrake. It supports
4
3
  # synchronous and asynchronous delivery.
5
4
  #
6
5
  # @see Airbrake::Config The list of options
7
6
  # @since v1.0.0
7
+ # @api private
8
8
  class Notifier
9
- ##
10
9
  # @return [String] the label to be prepended to the log output
11
10
  LOG_LABEL = '**Airbrake:'.freeze
12
11
 
13
- ##
14
12
  # Creates a new Airbrake notifier with the given config options.
15
13
  #
16
14
  # @example Configuring with a Hash
@@ -40,29 +38,21 @@ module Airbrake
40
38
  @sync_sender = SyncSender.new(@config)
41
39
  end
42
40
 
43
- ##
44
- # @!macro see_public_api_method
45
- # @see Airbrake.$0
46
-
47
- ##
48
41
  # @macro see_public_api_method
49
42
  def notify(exception, params = {}, &block)
50
43
  send_notice(exception, params, default_sender, &block)
51
44
  end
52
45
 
53
- ##
54
46
  # @macro see_public_api_method
55
47
  def notify_sync(exception, params = {}, &block)
56
48
  send_notice(exception, params, @sync_sender, &block).value
57
49
  end
58
50
 
59
- ##
60
51
  # @macro see_public_api_method
61
52
  def add_filter(filter = nil, &block)
62
53
  @filter_chain.add_filter(block_given? ? block : filter)
63
54
  end
64
55
 
65
- ##
66
56
  # @macro see_public_api_method
67
57
  def build_notice(exception, params = {})
68
58
  if @async_sender.closed?
@@ -78,13 +68,11 @@ module Airbrake
78
68
  end
79
69
  end
80
70
 
81
- ##
82
71
  # @macro see_public_api_method
83
72
  def close
84
73
  @async_sender.close
85
74
  end
86
75
 
87
- ##
88
76
  # @macro see_public_api_method
89
77
  def create_deploy(deploy_params)
90
78
  deploy_params[:environment] ||= @config.environment
@@ -94,13 +82,11 @@ module Airbrake
94
82
  promise
95
83
  end
96
84
 
97
- ##
98
85
  # @macro see_public_api_method
99
86
  def configured?
100
87
  @config.valid?
101
88
  end
102
89
 
103
- ##
104
90
  # @macro see_public_api_method
105
91
  def merge_context(context)
106
92
  @context.merge!(context)
@@ -171,6 +157,9 @@ module Airbrake
171
157
  end
172
158
 
173
159
  @filter_chain.add_filter(Airbrake::Filters::ContextFilter.new(@context))
160
+ @filter_chain.add_filter(
161
+ Airbrake::Filters::ExceptionAttributesFilter.new(@config.logger)
162
+ )
174
163
 
175
164
  return unless (root_directory = @config.root_directory)
176
165
  @filter_chain.add_filter(
@@ -178,29 +167,4 @@ module Airbrake
178
167
  )
179
168
  end
180
169
  end
181
-
182
- ##
183
- # NilNotifier is a no-op notifier, which mimics +Airbrake::Notifier+ and
184
- # serves only for the purpose of making the library API easier to use.
185
- #
186
- # @since 2.1.0
187
- class NilNotifier
188
- def notify(_exception, _params = {}, &block); end
189
-
190
- def notify_sync(_exception, _params = {}, &block); end
191
-
192
- def add_filter(_filter = nil, &_block); end
193
-
194
- def build_notice(_exception, _params = {}); end
195
-
196
- def close; end
197
-
198
- def create_deploy(_deploy_params); end
199
-
200
- def configured?
201
- false
202
- end
203
-
204
- def merge_context(_context); end
205
- end
206
170
  end
@@ -1,5 +1,4 @@
1
1
  module Airbrake
2
- ##
3
2
  # Represents a simplified promise object (similar to promises found in
4
3
  # JavaScript), which allows chaining callbacks that are executed when the
5
4
  # promise is either resolved or rejected.
@@ -8,7 +7,6 @@ module Airbrake
8
7
  # @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/promise.rb
9
8
  # @since v1.7.0
10
9
  class Promise
11
- ##
12
10
  # @api private
13
11
  # @return [Hash<String,String>] either successful response containing the
14
12
  # +id+ key or unsuccessful response containing the +error+ key
@@ -22,7 +20,6 @@ module Airbrake
22
20
  @mutex = Mutex.new
23
21
  end
24
22
 
25
- ##
26
23
  # Attaches a callback to be executed when the promise is resolved.
27
24
  #
28
25
  # @example
@@ -46,7 +43,6 @@ module Airbrake
46
43
  self
47
44
  end
48
45
 
49
- ##
50
46
  # Attaches a callback to be executed when the promise is rejected.
51
47
  #
52
48
  # @example
@@ -68,7 +64,6 @@ module Airbrake
68
64
  self
69
65
  end
70
66
 
71
- ##
72
67
  # Resolves the promise.
73
68
  #
74
69
  # @example
@@ -85,7 +80,6 @@ module Airbrake
85
80
  self
86
81
  end
87
82
 
88
- ##
89
83
  # Rejects the promise.
90
84
  #
91
85
  # @example
@@ -1,20 +1,16 @@
1
1
  module Airbrake
2
- ##
3
2
  # Parses responses coming from the Airbrake API. Handles HTTP errors by
4
3
  # logging them.
5
4
  #
6
5
  # @api private
7
6
  # @since v1.0.0
8
7
  module Response
9
- ##
10
8
  # @return [Integer] the limit of the response body
11
9
  TRUNCATE_LIMIT = 100
12
10
 
13
- ##
14
11
  # @return [Integer] HTTP code returned when an IP sends over 10k/min notices
15
12
  TOO_MANY_REQUESTS = 429
16
13
 
17
- ##
18
14
  # Parses HTTP responses from the Airbrake API.
19
15
  #
20
16
  # @param [Net::HTTPResponse] response
@@ -1,23 +1,19 @@
1
1
  module Airbrake
2
- ##
3
2
  # Responsible for sending notices to Airbrake synchronously. Supports proxies.
4
3
  #
5
4
  # @see AsyncSender
6
5
  # @api private
7
6
  # @since v1.0.0
8
7
  class SyncSender
9
- ##
10
8
  # @return [String] body for HTTP requests
11
9
  CONTENT_TYPE = 'application/json'.freeze
12
10
 
13
- ##
14
11
  # @param [Airbrake::Config] config
15
12
  def initialize(config)
16
13
  @config = config
17
14
  @rate_limit_reset = Time.now
18
15
  end
19
16
 
20
- ##
21
17
  # Sends a POST request to the given +endpoint+ with the +notice+ payload.
22
18
  #
23
19
  # @param [Airbrake::Notice] notice
@@ -1,8 +1,6 @@
1
- ##
2
1
  # We use Semantic Versioning v2.0.0
3
2
  # More information: http://semver.org/
4
3
  module Airbrake
5
- ##
6
4
  # @return [String] the library version
7
- AIRBRAKE_RUBY_VERSION = '2.9.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '2.10.0'.freeze
8
6
  end
@@ -1,182 +1,113 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Airbrake do
4
- let(:endpoint) { 'https://airbrake.io/api/v3/projects/113743/notices' }
5
-
6
- let!(:notifier) do
7
- described_class.configure do |c|
8
- c.project_id = 113743
9
- c.project_key = 'fd04e13d806a90f96614ad8e529b2822'
4
+ describe ".[]" do
5
+ it "returns a NilNotifier" do
6
+ expect(described_class[:test]).to be_an(Airbrake::NilNotifier)
10
7
  end
11
8
  end
12
9
 
13
- before do
14
- stub_request(:post, endpoint).to_return(status: 201, body: '{}')
15
- end
16
-
17
- after do
18
- described_class.instance_variable_set(
19
- :@notifiers,
20
- Hash.new(Airbrake::NilNotifier.new)
21
- )
10
+ let(:default_notifier) do
11
+ described_class.instance_variable_get(:@notifiers)[:default]
22
12
  end
23
13
 
24
- shared_examples 'non-configured notifier handling' do |method|
25
- it "returns nil if there is no configured notifier when using #{method}" do
26
- described_class.instance_variable_set(
27
- :@notifiers,
28
- Hash.new(Airbrake::NilNotifier.new)
29
- )
30
- expect(described_class.__send__(method, 'bingo')).to be_nil
31
- end
32
- end
14
+ describe ".configure" do
15
+ let(:config_params) { { project_id: 1, project_key: 'abc' } }
33
16
 
34
- describe ".notify" do
35
- include_examples 'non-configured notifier handling', :notify
17
+ after { described_class.instance_variable_get(:@notifiers).clear }
36
18
 
37
- it "sends exceptions asynchronously" do
38
- described_class.notify('bingo')
39
- sleep 2
40
- expect(a_request(:post, endpoint)).to have_been_made.once
19
+ it "yields the config" do
20
+ expect do |b|
21
+ begin
22
+ described_class.configure(&b)
23
+ rescue Airbrake::Error
24
+ nil
25
+ end
26
+ end.to yield_with_args(Airbrake::Config)
41
27
  end
42
28
 
43
- it "yields a notice" do
44
- described_class.notify('bongo') do |notice|
45
- notice[:params][:bingo] = :bango
29
+ context "when invoked with a notifier name" do
30
+ it "sets notifier name to the provided name" do
31
+ described_class.configure(:test) { |c| c.merge(config_params) }
32
+ expect(described_class[:test]).to be_an(Airbrake::Notifier)
46
33
  end
47
-
48
- sleep 1
49
-
50
- expect(
51
- a_request(:post, endpoint).
52
- with(body: /params":{.*"bingo":"bango".*}/)
53
- ).to have_been_made.once
54
- end
55
- end
56
-
57
- describe ".notify_sync" do
58
- include_examples 'non-configured notifier handling', :notify_sync
59
-
60
- it "sends exceptions synchronously" do
61
- expect(described_class.notify_sync('bingo')).to be_a(Hash)
62
- expect(a_request(:post, endpoint)).to have_been_made.once
63
34
  end
64
35
 
65
- it "yields a notice" do
66
- described_class.notify_sync('bongo') do |notice|
67
- notice[:params][:bingo] = :bango
36
+ context "when invoked without a notifier name" do
37
+ it "defaults to the :default notifier name" do
38
+ described_class.configure { |c| c.merge(config_params) }
39
+ expect(described_class[:default]).to be_an(Airbrake::Notifier)
68
40
  end
69
-
70
- expect(
71
- a_request(:post, endpoint).
72
- with(body: /params":{.*"bingo":"bango".*}/)
73
- ).to have_been_made.once
74
41
  end
75
42
 
76
- describe "clean backtrace" do
77
- shared_examples 'backtrace building' do |msg, argument|
78
- it(msg) do
79
- described_class.notify_sync(argument)
80
-
81
- # rubocop:disable Metrics/LineLength
82
- expected_body = %r|
83
- {"errors":\[{"type":"RuntimeError","message":"bingo","backtrace":\[
84
- {"file":"/PROJECT_ROOT/spec/airbrake_spec.rb","line":\d+,"function":"[\w/\s\(\)<>]+","code".+},
85
- {"file":"/GEM_ROOT/gems/rspec-core-.+/.+","line":\d+,"function":"[\w/\s\(\)<>]+".+
86
- |x
87
- # rubocop:enable Metrics/LineLength
88
-
89
- expect(
90
- a_request(:post, endpoint).
91
- with(body: expected_body)
92
- ).to have_been_made.once
93
- end
94
- end
95
-
96
- context "given a String" do
97
- include_examples(
98
- 'backtrace building',
99
- 'converts it to a RuntimeException and builds a fake backtrace',
100
- 'bingo'
101
- )
102
- end
103
-
104
- context "given an Exception with missing backtrace" do
105
- include_examples(
106
- 'backtrace building',
107
- 'builds a backtrace for it and sends the notice',
108
- RuntimeError.new('bingo')
43
+ context "when invoked twice with the same notifier name" do
44
+ it "raises Airbrake::Error" do
45
+ described_class.configure { |c| c.merge(config_params) }
46
+ expect do
47
+ described_class.configure { |c| c.merge(config_params) }
48
+ end.to raise_error(
49
+ Airbrake::Error, "the 'default' notifier was already configured"
109
50
  )
110
51
  end
111
52
  end
112
53
  end
113
54
 
114
- describe ".configure" do
115
- context "given an argument" do
116
- it "configures a notifier with the given name" do
117
- described_class.configure(:bingo) do |c|
118
- c.project_id = 123
119
- c.project_key = '321'
120
- end
121
-
122
- notifiers = described_class.instance_variable_get(:@notifiers)
123
-
124
- expect(notifiers).to be_a(Hash)
125
- expect(notifiers.keys).to eq(%i[default bingo])
126
- expect(notifiers.values).to all(satisfy { |v| v.is_a?(Airbrake::Notifier) })
127
- end
128
-
129
- it "raises error when a notifier of the given type was already configured" do
130
- described_class.configure(:bingo) do |c|
131
- c.project_id = 123
132
- c.project_key = '321'
133
- end
134
-
135
- expect do
136
- described_class.configure(:bingo) do |c|
137
- c.project_id = 123
138
- c.project_key = '321'
139
- end
140
- end.to raise_error(Airbrake::Error,
141
- "the 'bingo' notifier was already configured")
142
- end
55
+ describe ".configured?" do
56
+ it "forwards 'configured?' to the notifier" do
57
+ expect(default_notifier).to receive(:configured?)
58
+ described_class.configured?
143
59
  end
144
60
  end
145
61
 
146
- describe ".configured?" do
147
- before do
148
- described_class.instance_variable_set(
149
- :@notifiers,
150
- Hash.new(Airbrake::NilNotifier.new)
151
- )
62
+ describe ".notify" do
63
+ it "forwards 'notify' to the notifier" do
64
+ block = proc {}
65
+ expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
66
+ described_class.notify('ex', foo: 'bar', &block)
152
67
  end
68
+ end
153
69
 
154
- it "returns false" do
155
- expect(described_class).not_to be_configured
70
+ describe ".notify_sync" do
71
+ it "forwards 'notify_sync' to the notifier" do
72
+ block = proc {}
73
+ expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
74
+ described_class.notify('ex', foo: 'bar', &block)
156
75
  end
157
76
  end
158
77
 
159
78
  describe ".add_filter" do
160
- include_examples 'non-configured notifier handling', :add_filter
161
-
162
- it "adds filters with help of blocks" do
163
- filter_chain = notifier.instance_variable_get(:@filter_chain)
164
- filters = filter_chain.instance_variable_get(:@filters)
165
-
166
- expect(filters.size).to eq(4)
79
+ it "forwards 'add_filter' to the notifier" do
80
+ block = proc {}
81
+ expect(default_notifier).to receive(:add_filter).with(nil, &block)
82
+ described_class.add_filter(&block)
83
+ end
84
+ end
167
85
 
168
- described_class.add_filter {}
86
+ describe ".build_notice" do
87
+ it "forwards 'build_notice' to the notifier" do
88
+ expect(default_notifier).to receive(:build_notice).with('ex', foo: 'bar')
89
+ described_class.build_notice('ex', foo: 'bar')
90
+ end
91
+ end
169
92
 
170
- expect(filters.size).to eq(5)
171
- expect(filters.last).to be_a(Proc)
93
+ describe ".close" do
94
+ it "forwards 'close' to the notifier" do
95
+ expect(default_notifier).to receive(:close)
96
+ described_class.close
172
97
  end
173
98
  end
174
99
 
175
- describe ".build_notice" do
176
- include_examples 'non-configured notifier handling', :build_notice
100
+ describe ".create_deploy" do
101
+ it "forwards 'create_deploy' to the notifier" do
102
+ expect(default_notifier).to receive(:create_deploy).with(foo: 'bar')
103
+ described_class.create_deploy(foo: 'bar')
104
+ end
177
105
  end
178
106
 
179
107
  describe ".merge_context" do
180
- include_examples 'non-configured notifier handling', :merge_context
108
+ it "forwards 'merge_context' to the notifier" do
109
+ expect(default_notifier).to receive(:merge_context).with(foo: 'bar')
110
+ described_class.merge_context(foo: 'bar')
111
+ end
181
112
  end
182
113
  end