airbrake-ruby 2.9.0 → 2.10.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.
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