airbrake-ruby 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab4ce2485ab1b938a76f15f2b71d92db0fe6e0e2
4
- data.tar.gz: a48ae79d2523577c1400d2ed6b914e5340e9f5ff
3
+ metadata.gz: 4447483250d34ac608ceac9356647902e55a8c58
4
+ data.tar.gz: 550d498d5f8f36b5cff9a4f02a45e813646c2833
5
5
  SHA512:
6
- metadata.gz: 05d26b46d931156979aa32411b8d722c46023056eee2d0545be53df86a26e5ee565640a59999ddd37d1b947db44f113ac6dc636df5e9b6cb800682d66ef87c55
7
- data.tar.gz: ec26b798eb2c9139ffbd44a0bd42385886663fade0ddcab147ea0b6d68b94c4635813398fe9c2b0b4e87398d26099751d016900e3b026cfac79acd0dc29d9998
6
+ metadata.gz: 4913a170dcebd29645730f83bba8135a251b07f77f57933eefcaa596b22ad44db84599dbe444ea8bd6fcee57407351f4e1caab8c9d71b292506aa8a89242894e
7
+ data.tar.gz: d471bba8e00eb16117d6eb8a8842becff961f69d7e3fedfe878fa94939c2aad5919bf9ed5393c91e875995b07de2c4c3c4ae72839b1c600ac4a97f021d651176
@@ -8,6 +8,7 @@ require 'socket'
8
8
  require 'airbrake-ruby/version'
9
9
  require 'airbrake-ruby/config'
10
10
  require 'airbrake-ruby/config/validator'
11
+ require 'airbrake-ruby/promise'
11
12
  require 'airbrake-ruby/sync_sender'
12
13
  require 'airbrake-ruby/async_sender'
13
14
  require 'airbrake-ruby/response'
@@ -55,6 +56,7 @@ require 'airbrake-ruby/notifier'
55
56
  # Airbrake.notify('Oops', params, :my_other_project)
56
57
  #
57
58
  # @see Airbrake::Notifier
59
+ # @since v1.0.0
58
60
  module Airbrake
59
61
  ##
60
62
  # The general error that this library uses when it wants to raise.
@@ -64,11 +66,6 @@ module Airbrake
64
66
  # @return [String] the label to be prepended to the log output
65
67
  LOG_LABEL = '**Airbrake:'.freeze
66
68
 
67
- ##
68
- # @return [Boolean] true if current Ruby is Ruby 1.9.*. The result is used
69
- # for special cases where we need to work around older implementations
70
- RUBY_19 = RUBY_VERSION.start_with?('1.9')
71
-
72
69
  ##
73
70
  # @return [Boolean] true if current Ruby is Ruby 2.0.*. The result is used
74
71
  # for special cases where we need to work around older implementations
@@ -140,7 +137,7 @@ module Airbrake
140
137
  # @param [Hash] params The additional payload to be sent to Airbrake. Can
141
138
  # contain any values. The provided values will be displayed in the Params
142
139
  # tab in your project's dashboard
143
- # @return [nil]
140
+ # @return [Airbrake::Promise]
144
141
  # @see .notify_sync
145
142
  def notify(exception, params = {}, notifier = :default)
146
143
  call_notifier(notifier, __method__, exception, params)
@@ -156,7 +153,6 @@ module Airbrake
156
153
  #
157
154
  # @return [Hash{String=>String}] the reponse from the server
158
155
  # @see .notify
159
- # @since v5.0.0
160
156
  def notify_sync(exception, params = {}, notifier = :default)
161
157
  call_notifier(notifier, __method__, exception, params)
162
158
  end
@@ -187,7 +183,6 @@ module Airbrake
187
183
  # @yieldparam [Airbrake::Notice]
188
184
  # @yieldreturn [void]
189
185
  # @return [void]
190
- # @since v5.0.0
191
186
  # @note Once a filter was added, there's no way to delete it
192
187
  def add_filter(filter = nil, notifier = :default, &block)
193
188
  call_notifier(notifier, __method__, filter, &block)
@@ -209,7 +204,6 @@ module Airbrake
209
204
  # @param [Hash] params The additional params attached to the notice
210
205
  # @return [Airbrake::Notice] the notice built with help of the given
211
206
  # arguments
212
- # @since v5.0.0
213
207
  def build_notice(exception, params = {}, notifier = :default)
214
208
  call_notifier(notifier, __method__, exception, params)
215
209
  end
@@ -225,7 +219,6 @@ module Airbrake
225
219
  # Airbrake.notify('App crashed!') #=> raises Airbrake::Error
226
220
  #
227
221
  # @return [void]
228
- # @since v5.0.0
229
222
  def close(notifier = :default)
230
223
  call_notifier(notifier, __method__)
231
224
  end
@@ -242,8 +235,6 @@ module Airbrake
242
235
  # @option deploy_params [Symbol] :revision
243
236
  # @option deploy_params [Symbol] :version
244
237
  # @return [void]
245
- # @since v5.0.0
246
- # @api private
247
238
  def create_deploy(deploy_params, notifier = :default)
248
239
  call_notifier(notifier, __method__, deploy_params)
249
240
  end
@@ -5,6 +5,8 @@ module Airbrake
5
5
  # (both values are configurable).
6
6
  #
7
7
  # @see SyncSender
8
+ # @api private
9
+ # @since v1.0.0
8
10
  class AsyncSender
9
11
  ##
10
12
  # @param [Airbrake::Config] config
@@ -23,12 +25,12 @@ module Airbrake
23
25
  #
24
26
  # @param [Airbrake::Notice] notice A notice that was generated by the
25
27
  # library
26
- # @return [nil]
27
- def send(notice)
28
+ # @return [Airbrake::Promise]
29
+ def send(notice, promise)
28
30
  return will_not_deliver(notice) if @unsent.size >= @unsent.max
29
31
 
30
- @unsent << notice
31
- nil
32
+ @unsent << [notice, promise]
33
+ promise
32
34
  end
33
35
 
34
36
  ##
@@ -48,7 +50,7 @@ module Airbrake
48
50
  @config.logger.debug(msg + ' (Ctrl-C to abort)')
49
51
  end
50
52
 
51
- @config.workers.times { @unsent << :stop }
53
+ @config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
52
54
  @closed = true
53
55
  @workers.list.dup
54
56
  end
@@ -100,8 +102,9 @@ module Airbrake
100
102
 
101
103
  def spawn_worker
102
104
  Thread.new do
103
- while (notice = @unsent.pop) != :stop
104
- @sender.send(notice)
105
+ while (message = @unsent.pop)
106
+ break if message.first == :stop
107
+ @sender.send(*message)
105
108
  end
106
109
  end
107
110
  end
@@ -10,6 +10,9 @@ module Airbrake
10
10
  # rescue
11
11
  # Backtrace.parse($!, Logger.new(STDOUT))
12
12
  # end
13
+ #
14
+ # @api private
15
+ # @since v1.0.0
13
16
  module Backtrace
14
17
  module Patterns
15
18
  ##
@@ -32,6 +35,8 @@ module Airbrake
32
35
  (?<file>
33
36
  (?:uri:classloader:/.+(?=:)) # Matches '/META-INF/jruby.home/protocol.rb'
34
37
  |
38
+ (?:uri_3a_classloader_3a_.+(?=:)) # Matches 'uri_3a_classloader_3a_/gems/...'
39
+ |
35
40
  [^:]+ # Matches 'NewlineNode.java'
36
41
  )
37
42
  :?
@@ -151,8 +156,8 @@ module Airbrake
151
156
  return false unless defined?(ExecJS::RuntimeError)
152
157
  return true if exception.is_a?(ExecJS::RuntimeError)
153
158
 
154
- if Airbrake::RUBY_19 || Airbrake::RUBY_20
155
- # Ruby 1.9 doesn't support Exception#cause. We work around this by
159
+ if Airbrake::RUBY_20
160
+ # Ruby <2.1 doesn't support Exception#cause. We work around this by
156
161
  # parsing backtraces. It's slow, so we check only a few first frames.
157
162
  exception.backtrace[0..2].each do |frame|
158
163
  return true if frame =~ Patterns::EXECJS_SIMPLIFIED
@@ -2,6 +2,9 @@ module Airbrake
2
2
  ##
3
3
  # Represents the Airbrake config. A config contains all the options that you
4
4
  # can use to configure an Airbrake instance.
5
+ #
6
+ # @api private
7
+ # @since v1.0.0
5
8
  class Config
6
9
  ##
7
10
  # @return [Integer] the project identificator. This value *must* be set.
@@ -103,7 +106,7 @@ module Airbrake
103
106
  @endpoint ||=
104
107
  begin
105
108
  self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
106
- api = "/api/v3/projects/#{project_id}/notices?key=#{project_key}"
109
+ api = "api/v3/projects/#{project_id}/notices?key=#{project_key}"
107
110
  URI.join(host, api)
108
111
  end
109
112
  end
@@ -163,10 +166,5 @@ module Airbrake
163
166
  rescue NoMethodError
164
167
  raise Airbrake::Error, "unknown option '#{option}'"
165
168
  end
166
-
167
- def set_endpoint(id, key, host)
168
- host = ('https://' << host) if host !~ %r{\Ahttps?://}
169
- @endpoint = URI.join(host, "/api/v3/projects/#{id}/notices?key=#{key}")
170
- end
171
169
  end
172
170
  end
@@ -2,7 +2,10 @@ module Airbrake
2
2
  ##
3
3
  # Represents the mechanism for filtering notices. Defines a few default
4
4
  # filters.
5
+ #
5
6
  # @see Airbrake.add_filter
7
+ # @api private
8
+ # @since v1.0.0
6
9
  class FilterChain
7
10
  ##
8
11
  # Replaces paths to gems with a placeholder.
@@ -2,6 +2,9 @@ module Airbrake
2
2
  ##
3
3
  # A class that is capable of unwinding nested exceptions and representing them
4
4
  # as JSON-like hash.
5
+ #
6
+ # @api private
7
+ # @since v1.0.4
5
8
  class NestedException
6
9
  ##
7
10
  # @return [Integer] the maximum number of nested exceptions that a notice
@@ -2,6 +2,8 @@ module Airbrake
2
2
  ##
3
3
  # Represents a chunk of information that is meant to be either sent to
4
4
  # Airbrake or ignored completely.
5
+ #
6
+ # @since v1.0.0
5
7
  # rubocop:disable Metrics/ClassLength
6
8
  class Notice
7
9
  ##
@@ -54,6 +56,12 @@ module Airbrake
54
56
  # @return [String] the name of the host machine
55
57
  HOSTNAME = Socket.gethostname.freeze
56
58
 
59
+ ##
60
+ # @since v1.7.0
61
+ # @return [Hash{Symbol=>Object}] the hash with arbitrary objects to be used
62
+ # in filters
63
+ attr_reader :stash
64
+
57
65
  def initialize(config, exception, params = {})
58
66
  @config = config
59
67
 
@@ -64,6 +72,7 @@ module Airbrake
64
72
  session: {},
65
73
  params: params
66
74
  }
75
+ @stash = {}
67
76
 
68
77
  extract_custom_attributes(exception)
69
78
 
@@ -140,6 +149,17 @@ module Airbrake
140
149
  private
141
150
 
142
151
  def context(params)
152
+ # DEPRECATION: remove the following code in the next MINOR release.
153
+ if params.key?(:component) || params.key?(:action)
154
+ @config.logger.warn(
155
+ "#{LOG_LABEL} passing component/action keys in the params hash is " \
156
+ "deprecated and will be removed soon. Please update your code to use " \
157
+ "`Airbrake.build_notice` and set these keys like this:\n" \
158
+ " notice[:context][:component] = 'mycomponent'\n" \
159
+ " notice[:context][:action] = 'myaction'"
160
+ )
161
+ end
162
+
143
163
  ctx = {
144
164
  version: @config.app_version,
145
165
  # We ensure that root_directory is always a String, so it can always be
@@ -148,6 +168,7 @@ module Airbrake
148
168
  rootDirectory: @config.root_directory.to_s,
149
169
  environment: @config.environment,
150
170
 
171
+ # DEPRECATION: remove the following code in the next MINOR release.
151
172
  # Legacy Airbrake v4 behaviour.
152
173
  component: params.delete(:component),
153
174
  action: params.delete(:action),
@@ -5,7 +5,7 @@ module Airbrake
5
5
  #
6
6
  # @see Airbrake::Config The list of options
7
7
  # @api private
8
- # @since v5.0.0
8
+ # @since v1.0.0
9
9
  class Notifier
10
10
  ##
11
11
  # @return [String] the label to be prepended to the log output
@@ -49,14 +49,13 @@ module Airbrake
49
49
  ##
50
50
  # @macro see_public_api_method
51
51
  def notify(exception, params = {})
52
- send_notice(exception, params)
53
- nil
52
+ send_notice(exception, params, default_sender)
54
53
  end
55
54
 
56
55
  ##
57
56
  # @macro see_public_api_method
58
57
  def notify_sync(exception, params = {})
59
- send_notice(exception, params, @sync_sender)
58
+ send_notice(exception, params, @sync_sender).value
60
59
  end
61
60
 
62
61
  ##
@@ -95,7 +94,9 @@ module Airbrake
95
94
  host = @config.endpoint.to_s.split(@config.endpoint.path).first
96
95
  path = "/api/v4/projects/#{@config.project_id}/deploys?key=#{@config.project_key}"
97
96
 
98
- @sync_sender.send(deploy_params, URI.join(host, path))
97
+ promise = Airbrake::Promise.new
98
+ @sync_sender.send(deploy_params, promise, URI.join(host, path))
99
+ promise
99
100
  end
100
101
 
101
102
  private
@@ -114,14 +115,19 @@ module Airbrake
114
115
  e
115
116
  end
116
117
 
117
- def send_notice(exception, params, sender = default_sender)
118
- return if @config.ignored_environment?
118
+ def send_notice(exception, params, sender)
119
+ promise = Airbrake::Promise.new
120
+ if @config.ignored_environment?
121
+ return promise.reject("The '#{@config.environment}' environment is ignored")
122
+ end
119
123
 
120
124
  notice = build_notice(exception, params)
121
125
  @filter_chain.refine(notice)
122
- return if notice.ignored?
126
+ if notice.ignored?
127
+ return promise.reject("#{notice} was marked as ignored")
128
+ end
123
129
 
124
- sender.send(notice)
130
+ sender.send(notice, promise)
125
131
  end
126
132
 
127
133
  def default_sender
@@ -2,6 +2,9 @@ module Airbrake
2
2
  ##
3
3
  # This class is responsible for truncation of too big objects. Mainly, you
4
4
  # should use it for simple objects such as strings, hashes, & arrays.
5
+ #
6
+ # @api private
7
+ # @since v1.0.0
5
8
  class PayloadTruncator
6
9
  ##
7
10
  # @return [Hash] the options for +String#encode+
@@ -10,7 +13,7 @@ module Airbrake
10
13
  ##
11
14
  # @return [String] the temporary encoding to be used when fixing invalid
12
15
  # strings with +ENCODING_OPTIONS+
13
- TEMP_ENCODING = (RUBY_VERSION == '1.9.2' ? 'iso-8859-1' : 'utf-16')
16
+ TEMP_ENCODING = 'utf-16'.freeze
14
17
 
15
18
  ##
16
19
  # @param [Integer] max_size maximum size of hashes, arrays and strings
@@ -99,12 +102,6 @@ module Airbrake
99
102
  ##
100
103
  # Replaces invalid characters in string with arbitrary encoding.
101
104
  #
102
- # For Ruby 1.9.2 the method converts encoding of +str+ to +iso-8859-1+ to
103
- # avoid a bug when encoding options are no-op, when `#encode` is given the
104
- # same encoding as the receiver's encoding.
105
- #
106
- # For modern Rubies we use UTF-16 as a safe alternative.
107
- #
108
105
  # @param [String] str The string to replace characters
109
106
  # @return [String] a UTF-8 encoded string
110
107
  # @note This method mutates +str+ unless it's frozen,
@@ -0,0 +1,103 @@
1
+ module Airbrake
2
+ ##
3
+ # Represents a simplified promise object (similar to promises found in
4
+ # JavaScript), which allows chaining callbacks that are executed when the
5
+ # promise is either resolved or rejected.
6
+ #
7
+ # @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
8
+ # @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/promise.rb
9
+ # @since v1.7.0
10
+ class Promise
11
+ ##
12
+ # @return [Hash<String,String>] either successful response containing the
13
+ # `id` key or unsuccessful response containing the `error` key
14
+ attr_reader :value
15
+
16
+ def initialize
17
+ @on_resolved = []
18
+ @on_rejected = []
19
+ @value = {}
20
+ @mutex = Mutex.new
21
+ end
22
+
23
+ ##
24
+ # Attaches a callback to be executed when the promise is resolved.
25
+ #
26
+ # @example
27
+ # Airbrake::Promise.new.then { |response| puts response }
28
+ # #=> {"id"=>"00054415-8201-e9c6-65d6-fc4d231d2871",
29
+ # # "url"=>"http://localhost/locate/00054415-8201-e9c6-65d6-fc4d231d2871"}
30
+ #
31
+ # @yield [response]
32
+ # @yieldparam response [Hash<String,String>] Contains the `id` & `url` keys
33
+ # @return [self]
34
+ def then(&block)
35
+ @mutex.synchronize do
36
+ if @value.key?('id')
37
+ yield(@value)
38
+ return self
39
+ end
40
+
41
+ @on_resolved << block
42
+ end
43
+
44
+ self
45
+ end
46
+
47
+ ##
48
+ # Attaches a callback to be executed when the promise is rejected.
49
+ #
50
+ # @example
51
+ # Airbrake::Promise.new.rescue { |error| raise error }
52
+ #
53
+ # @yield [error] The error message from the API
54
+ # @yieldparam error [String]
55
+ # @return [self]
56
+ def rescue(&block)
57
+ @mutex.synchronize do
58
+ if @value.key?('error')
59
+ yield(@value['error'])
60
+ return self
61
+ end
62
+
63
+ @on_rejected << block
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ ##
70
+ # Resolves the promise.
71
+ #
72
+ # @example
73
+ # Airbrake::Promise.new.resolve('id' => '123')
74
+ #
75
+ # @param response [Hash<String,String>]
76
+ # @return [self]
77
+ def resolve(response)
78
+ @mutex.synchronize do
79
+ @value = response
80
+ @on_resolved.each { |callback| callback.call(response) }
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ ##
87
+ # Rejects the promise.
88
+ #
89
+ # @example
90
+ # Airbrake::Promise.new.reject('Something went wrong')
91
+ #
92
+ # @param error [String]
93
+ # @return [self]
94
+ def reject(error)
95
+ @mutex.synchronize do
96
+ @value['error'] = error
97
+ @on_rejected.each { |callback| callback.call(error) }
98
+ end
99
+
100
+ self
101
+ end
102
+ end
103
+ end
@@ -2,6 +2,9 @@ module Airbrake
2
2
  ##
3
3
  # Parses responses coming from the Airbrake API. Handles HTTP errors by
4
4
  # logging them.
5
+ #
6
+ # @api private
7
+ # @since v1.0.0
5
8
  module Response
6
9
  ##
7
10
  # @return [Integer] the limit of the response body
@@ -35,7 +38,7 @@ module Airbrake
35
38
  rescue => ex
36
39
  body_msg = truncated_body(body)
37
40
  logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}")
38
- { 'error' => ex }
41
+ { 'error' => ex.inspect }
39
42
  end
40
43
  end
41
44
 
@@ -3,6 +3,8 @@ module Airbrake
3
3
  # Responsible for sending notices to Airbrake synchronously. Supports proxies.
4
4
  #
5
5
  # @see AsyncSender
6
+ # @api private
7
+ # @since v1.0.0
6
8
  class SyncSender
7
9
  ##
8
10
  # @return [String] body for HTTP requests
@@ -20,15 +22,14 @@ module Airbrake
20
22
  # @param [Airbrake::Notice] notice
21
23
  # @param [Airbrake::Notice] endpoint
22
24
  # @return [Hash{String=>String}] the parsed HTTP response
23
- def send(notice, endpoint = @config.endpoint)
25
+ def send(notice, promise, endpoint = @config.endpoint)
24
26
  response = nil
25
27
  req = build_post_request(endpoint, notice)
26
28
 
27
29
  if req.body.nil?
28
- @config.logger.error(
29
- "#{LOG_LABEL} notice was not sent because of missing body"
30
- )
31
- return
30
+ reason = "#{LOG_LABEL} notice was not sent because of missing body"
31
+ @config.logger.error(reason)
32
+ return promise.reject(reason)
32
33
  end
33
34
 
34
35
  https = build_https(endpoint)
@@ -36,11 +37,14 @@ module Airbrake
36
37
  begin
37
38
  response = https.request(req)
38
39
  rescue => ex
39
- @config.logger.error("#{LOG_LABEL} HTTP error: #{ex}")
40
- return
40
+ reason = "#{LOG_LABEL} HTTP error: #{ex}"
41
+ @config.logger.error(reason)
42
+ return promise.reject(reason)
41
43
  end
42
44
 
43
- Response.parse(response, @config.logger)
45
+ parsed_resp = Response.parse(response, @config.logger)
46
+ return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
47
+ promise.resolve(parsed_resp)
44
48
  end
45
49
 
46
50
  private
@@ -4,5 +4,5 @@
4
4
  module Airbrake
5
5
  ##
6
6
  # @return [String] the library version
7
- AIRBRAKE_RUBY_VERSION = '1.6.0'.freeze
7
+ AIRBRAKE_RUBY_VERSION = '1.7.0'.freeze
8
8
  end
@@ -17,7 +17,7 @@ RSpec.describe Airbrake::AsyncSender do
17
17
  expect(sender).to have_workers
18
18
 
19
19
  notice = Airbrake::Notice.new(config, AirbrakeTestError.new)
20
- notices_count.times { sender.send(notice) }
20
+ notices_count.times { sender.send(notice, Airbrake::Promise.new) }
21
21
  sender.close
22
22
 
23
23
  log = stdout.string.split("\n")
@@ -49,7 +49,7 @@ RSpec.describe Airbrake::AsyncSender do
49
49
  context "when there are some unsent notices" do
50
50
  before do
51
51
  notice = Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
52
- 300.times { @sender.send(notice) }
52
+ 300.times { @sender.send(notice, Airbrake::Promise.new) }
53
53
  expect(@sender.instance_variable_get(:@unsent).size).not_to be_zero
54
54
  @sender.close
55
55
  end
@@ -80,14 +80,18 @@ RSpec.describe Airbrake::Backtrace do
80
80
  let(:backtrace) do
81
81
  # rubocop:disable Metrics/LineLength
82
82
  ['uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.net.protocol.rbuf_fill(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/net/protocol.rb:158)',
83
- 'bin.processors.image_uploader.block in make_streams(bin/processors/image_uploader.rb:21)']
83
+ 'bin.processors.image_uploader.block in make_streams(bin/processors/image_uploader.rb:21)',
84
+ 'uri_3a_classloader_3a_.gems.faye_minus_websocket_minus_0_dot_10_dot_5.lib.faye.websocket.api.invokeOther13:dispatch_event(uri_3a_classloader_3a_/gems/faye_minus_websocket_minus_0_dot_10_dot_5/lib/faye/websocket/uri:classloader:/gems/faye-websocket-0.10.5/lib/faye/websocket/api.rb:109)',
85
+ 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file(/tmp/jruby9022301782566983632extract/./META-INF/rails.rb:13)']
84
86
  # rubocop:enable Metrics/LineLength
85
87
  end
86
88
 
87
89
  let(:parsed_backtrace) do
88
90
  # rubocop:disable Metrics/LineLength
89
91
  [{ file: 'uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/net/protocol.rb', line: 158, function: 'uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.net.protocol.rbuf_fill' },
90
- { file: 'bin/processors/image_uploader.rb', line: 21, function: 'bin.processors.image_uploader.block in make_streams' }]
92
+ { file: 'bin/processors/image_uploader.rb', line: 21, function: 'bin.processors.image_uploader.block in make_streams' },
93
+ { file: 'uri_3a_classloader_3a_/gems/faye_minus_websocket_minus_0_dot_10_dot_5/lib/faye/websocket/uri:classloader:/gems/faye-websocket-0.10.5/lib/faye/websocket/api.rb', line: 109, function: 'uri_3a_classloader_3a_.gems.faye_minus_websocket_minus_0_dot_10_dot_5.lib.faye.websocket.api.invokeOther13:dispatch_event' },
94
+ { file: '/tmp/jruby9022301782566983632extract/./META-INF/rails.rb', line: 13, function: 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file' }]
91
95
  # rubocop:enable Metrics/LineLength
92
96
  end
93
97
 
@@ -242,12 +246,12 @@ RSpec.describe Airbrake::Backtrace do
242
246
  function: 'realtime' }]
243
247
  end
244
248
 
245
- context "when not on Ruby 1.9" do
249
+ context "when not on Ruby 2.0" do
246
250
  let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }
247
251
 
248
252
  it "returns a properly formatted array of hashes" do
249
253
  stub_const('ExecJS::RuntimeError', AirbrakeTestError)
250
- stub_const('Airbrake::RUBY_19', false)
254
+ stub_const('Airbrake::RUBY_20', false)
251
255
 
252
256
  expect(
253
257
  described_class.parse(ex, Logger.new('/dev/null'))
@@ -255,7 +259,7 @@ RSpec.describe Airbrake::Backtrace do
255
259
  end
256
260
  end
257
261
 
258
- context "when on Ruby 1.9" do
262
+ context "when on Ruby 2.0" do
259
263
  context "and when exception's class isn't ExecJS" do
260
264
  let(:ex) do
261
265
  ActionView::Template::Error.new.tap { |e| e.set_backtrace(bt) }
@@ -264,7 +268,7 @@ RSpec.describe Airbrake::Backtrace do
264
268
  it "returns a properly formatted array of hashes" do
265
269
  stub_const('ActionView::Template::Error', AirbrakeTestError)
266
270
  stub_const('ExecJS::RuntimeError', NameError)
267
- stub_const('Airbrake::RUBY_19', true)
271
+ stub_const('Airbrake::RUBY_20', true)
268
272
 
269
273
  expect(
270
274
  described_class.parse(ex, Logger.new('/dev/null'))
@@ -229,4 +229,29 @@ RSpec.describe Airbrake::Config do
229
229
  end
230
230
  end
231
231
  end
232
+
233
+ describe "#endpoint" do
234
+ context "when host is configured with a URL with a slug" do
235
+ before do
236
+ config.project_id = 1
237
+ config.project_key = '2'
238
+ end
239
+
240
+ context "and with a trailing slash" do
241
+ it "sets the endpoint with the slug" do
242
+ config.host = 'https://localhost/bingo/'
243
+ expect(config.endpoint.to_s).
244
+ to eq('https://localhost/bingo/api/v3/projects/1/notices?key=2')
245
+ end
246
+ end
247
+
248
+ context "and without a trailing slash" do
249
+ it "sets the endpoint without the slug" do
250
+ config.host = 'https://localhost/bingo'
251
+ expect(config.endpoint.to_s).
252
+ to eq('https://localhost/api/v3/projects/1/notices?key=2')
253
+ end
254
+ end
255
+ end
256
+ end
232
257
  end
@@ -237,4 +237,12 @@ RSpec.describe Airbrake::Notice do
237
237
  to raise_error(Airbrake::Error, 'Got Object value, wanted a Hash')
238
238
  end
239
239
  end
240
+
241
+ describe "#stash" do
242
+ it "returns a hash" do
243
+ obj = Object.new
244
+ notice.stash[:bingo_object] = obj
245
+ expect(notice.stash[:bingo_object]).to eql(obj)
246
+ end
247
+ end
240
248
  end
@@ -23,7 +23,10 @@ RSpec.describe Airbrake::Notifier do
23
23
  let(:ex) { AirbrakeTestError.new }
24
24
 
25
25
  before do
26
- stub_request(:post, endpoint).to_return(status: 201, body: '{}')
26
+ # rubocop:disable Metrics/LineLength
27
+ body = '{"id":"00054414-b147-6ffa-85d6-1524d83362a6","url":"http://localhost/locate/00054414-b147-6ffa-85d6-1524d83362a6"}'
28
+ # rubocop:enable Metrics/LineLength
29
+ stub_request(:post, endpoint).to_return(status: 201, body: body)
27
30
  @airbrake = described_class.new(airbrake_params)
28
31
  end
29
32
 
@@ -58,6 +61,15 @@ RSpec.describe Airbrake::Notifier do
58
61
  end
59
62
 
60
63
  describe "#notify_sync" do
64
+ it "returns a hash with error id & url" do
65
+ expect(@airbrake.notify_sync(ex)).to(
66
+ eq(
67
+ 'id' => '00054414-b147-6ffa-85d6-1524d83362a6',
68
+ 'url' => 'http://localhost/locate/00054414-b147-6ffa-85d6-1524d83362a6'
69
+ )
70
+ )
71
+ end
72
+
61
73
  describe "first argument" do
62
74
  context "when it is a Notice" do
63
75
  it "sends the argument" do
@@ -372,8 +384,8 @@ RSpec.describe Airbrake::Notifier do
372
384
  expect_a_request_with_body(/params":{"bingo":"bango"}/)
373
385
  end
374
386
 
375
- it "returns nil" do
376
- expect(@airbrake.notify(ex)).to be_nil
387
+ it "returns a promise" do
388
+ expect(@airbrake.notify(ex)).to be_an(Airbrake::Promise)
377
389
  sleep 1
378
390
  end
379
391
 
@@ -143,7 +143,7 @@ RSpec.describe Airbrake::Notifier do
143
143
  it "is being used if configured" do
144
144
  @airbrake.notify_sync(ex)
145
145
 
146
- proxied_request = requests.pop
146
+ proxied_request = requests.pop(true)
147
147
 
148
148
  expect(proxied_request.header['proxy-authorization'].first).
149
149
  to eq('Basic dXNlcjpwYXNzd29yZA==')
@@ -210,7 +210,8 @@ RSpec.describe Airbrake::Notifier do
210
210
  airbrake = described_class.new(airbrake_params.merge(params))
211
211
 
212
212
  expect(Airbrake::Notice).not_to receive(:new)
213
- expect(airbrake.notify_sync(ex)).to be_nil
213
+ expect(airbrake.notify_sync(ex)).
214
+ to eq('error' => "The 'development' environment is ignored")
214
215
  expect(a_request(:post, endpoint)).not_to have_been_made
215
216
  end
216
217
  end
@@ -409,12 +409,7 @@ RSpec.describe Airbrake::PayloadTruncator do
409
409
  @truncator.truncate_object(params)
410
410
 
411
411
  expect(params[:unicode].length).to eq(max_len - 1)
412
-
413
- if RUBY_VERSION == '1.9.2'
414
- expect(params[:unicode]).to match(/\A?{#{max_size - 1}}\[Truncated\]\z/)
415
- else
416
- expect(params[:unicode]).to match(/\A€{#{max_size - 1}}\[Truncated\]\z/)
417
- end
412
+ expect(params[:unicode]).to match(/\A€{#{max_size - 1}}\[Truncated\]\z/)
418
413
  end
419
414
  end
420
415
 
@@ -425,12 +420,7 @@ RSpec.describe Airbrake::PayloadTruncator do
425
420
  it "converts strings to valid UTF-8" do
426
421
  @truncator.truncate_object(params)
427
422
 
428
- if RUBY_VERSION == '1.9.2'
429
- expect(params[:unicode]).to eq('bad string??')
430
- else
431
- expect(params[:unicode]).to match(/\Abad string€[�\?]\z/)
432
- end
433
-
423
+ expect(params[:unicode]).to match(/\Abad string€[�\?]\z/)
434
424
  expect { params.to_json }.not_to raise_error
435
425
  end
436
426
 
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Airbrake::Promise do
4
+ subject { described_class.new }
5
+
6
+ describe ".then" do
7
+ let(:resolved_with) { [] }
8
+ let(:rejected_with) { [] }
9
+
10
+ context "when it is not resolved" do
11
+ it "returns self" do
12
+ expect(subject.then {}).to eq(subject)
13
+ end
14
+
15
+ it "doesn't call the resolve callbacks yet" do
16
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
17
+ expect(resolved_with).to be_empty
18
+ end
19
+ end
20
+
21
+ context "when it is resolved" do
22
+ shared_examples "then specs" do
23
+ it "returns self" do
24
+ expect(subject.then {}).to eq(subject)
25
+ end
26
+
27
+ it "yields the resolved value" do
28
+ yielded = nil
29
+ subject.then { |value| yielded = value }
30
+ expect(yielded).to eq('id' => '123')
31
+ end
32
+
33
+ it "calls the resolve callbacks" do
34
+ expect(resolved_with).to match_array([1, 2])
35
+ end
36
+
37
+ it "doesn't call the reject callbacks" do
38
+ expect(rejected_with).to be_empty
39
+ end
40
+ end
41
+
42
+ context "and there are some resolve and reject callbacks in place" do
43
+ before do
44
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
45
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
46
+ subject.resolve('id' => '123')
47
+ end
48
+
49
+ include_examples "then specs"
50
+
51
+ it "registers the resolve callbacks" do
52
+ subject.resolve('id' => '456')
53
+ expect(resolved_with).to match_array([1, 2, 1, 2])
54
+ end
55
+ end
56
+
57
+ context "and additional then callbacks are added" do
58
+ before do
59
+ subject.resolve('id' => '123')
60
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
61
+ end
62
+
63
+ include_examples "then specs"
64
+
65
+ it "doesn't register new resolve callbacks" do
66
+ subject.resolve('id' => '456')
67
+ expect(resolved_with).to match_array([1, 2])
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".rescue" do
74
+ let(:resolved_with) { [] }
75
+ let(:rejected_with) { [] }
76
+
77
+ context "when it is not rejected" do
78
+ it "returns self" do
79
+ expect(subject.then {}).to eq(subject)
80
+ end
81
+
82
+ it "doesn't call the reject callbacks yet" do
83
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
84
+ expect(rejected_with).to be_empty
85
+ end
86
+ end
87
+
88
+ context "when it is rejected" do
89
+ shared_examples "rescue specs" do
90
+ it "returns self" do
91
+ expect(subject.rescue {}).to eq(subject)
92
+ end
93
+
94
+ it "yields the rejected value" do
95
+ yielded = nil
96
+ subject.rescue { |value| yielded = value }
97
+ expect(yielded).to eq('bingo')
98
+ end
99
+
100
+ it "doesn't call the resolve callbacks" do
101
+ expect(resolved_with).to be_empty
102
+ end
103
+
104
+ it "calls the reject callbacks" do
105
+ expect(rejected_with).to match_array([1, 2])
106
+ end
107
+ end
108
+
109
+ context "and there are some resolve and reject callbacks in place" do
110
+ before do
111
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
112
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
113
+ subject.reject('bingo')
114
+ end
115
+
116
+ include_examples "rescue specs"
117
+
118
+ it "registers the reject callbacks" do
119
+ subject.reject('bingo again')
120
+ expect(rejected_with).to match_array([1, 2, 1, 2])
121
+ end
122
+ end
123
+
124
+ context "and additional reject callbacks are added" do
125
+ before do
126
+ subject.reject('bingo')
127
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
128
+ end
129
+
130
+ include_examples "rescue specs"
131
+
132
+ it "doesn't register new reject callbacks" do
133
+ subject.reject('bingo again')
134
+ expect(rejected_with).to match_array([1, 2])
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe ".resolve" do
141
+ it "returns self" do
142
+ expect(subject.resolve(1)).to eq(subject)
143
+ end
144
+
145
+ it "executes callbacks attached with .then" do
146
+ array = []
147
+ subject.then { |notice_id| array << notice_id }.rescue { array << 999 }
148
+
149
+ expect(array.size).to be_zero
150
+ subject.resolve(1)
151
+ expect(array).to match_array([1])
152
+ end
153
+ end
154
+
155
+ describe ".reject" do
156
+ it "returns self" do
157
+ expect(subject.reject(1)).to eq(subject)
158
+ end
159
+
160
+ it "executes callbacks attached with .rescue" do
161
+ array = []
162
+ subject.then { array << 1 }.rescue { |error| array << error }
163
+
164
+ expect(array.size).to be_zero
165
+ subject.reject(999)
166
+ expect(array).to match_array([999])
167
+ end
168
+ end
169
+ end
@@ -12,7 +12,9 @@ RSpec.describe Airbrake::SyncSender do
12
12
  end
13
13
 
14
14
  describe "#send" do
15
- it "catches exceptions raised when sending" do
15
+ let(:promise) { Airbrake::Promise.new }
16
+
17
+ it "catches exceptions raised while sending" do
16
18
  stdout = StringIO.new
17
19
  config = Airbrake::Config.new(logger: Logger.new(stdout))
18
20
  sender = described_class.new(config)
@@ -20,7 +22,8 @@ RSpec.describe Airbrake::SyncSender do
20
22
  https = double("foo")
21
23
  allow(sender).to receive(:build_https).and_return(https)
22
24
  allow(https).to receive(:request).and_raise(StandardError.new('foo'))
23
- expect(sender.send(notice)).to be_nil
25
+ expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
26
+ expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
24
27
  expect(stdout.string).to match(/ERROR -- : .+ HTTP error: foo/)
25
28
  end
26
29
 
@@ -43,7 +46,9 @@ RSpec.describe Airbrake::SyncSender do
43
46
  sender = described_class.new(config)
44
47
  notice = Airbrake::Notice.new(config, ex)
45
48
 
46
- expect(sender.send(notice)).to be_nil
49
+ expect(sender.send(notice, promise)).to be_an(Airbrake::Promise)
50
+ expect(promise.value).
51
+ to match('error' => '**Airbrake: notice was not sent because of missing body')
47
52
  expect(stdout.string).to match(/ERROR -- : .+ notice was not sent/)
48
53
  end
49
54
  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: 1.6.0
4
+ version: 1.7.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: 2016-10-18 00:00:00.000000000 Z
11
+ date: 2017-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2'
61
+ version: '2.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2'
68
+ version: '2.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: benchmark-ips
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.47'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.47'
83
97
  description: |
84
98
  Airbrake Ruby is a plain Ruby notifier for Airbrake (https://airbrake.io), the
85
99
  leading exception reporting service. Airbrake Ruby provides minimalist API that
@@ -109,6 +123,7 @@ files:
109
123
  - lib/airbrake-ruby/notice.rb
110
124
  - lib/airbrake-ruby/notifier.rb
111
125
  - lib/airbrake-ruby/payload_truncator.rb
126
+ - lib/airbrake-ruby/promise.rb
112
127
  - lib/airbrake-ruby/response.rb
113
128
  - lib/airbrake-ruby/sync_sender.rb
114
129
  - lib/airbrake-ruby/version.rb
@@ -125,6 +140,7 @@ files:
125
140
  - spec/notifier_spec/options_spec.rb
126
141
  - spec/notifier_spec/whitelist_keys_spec.rb
127
142
  - spec/payload_truncator_spec.rb
143
+ - spec/promise_spec.rb
128
144
  - spec/spec_helper.rb
129
145
  - spec/sync_sender_spec.rb
130
146
  homepage: https://airbrake.io
@@ -139,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
155
  requirements:
140
156
  - - ">="
141
157
  - !ruby/object:Gem::Version
142
- version: '0'
158
+ version: '2.0'
143
159
  required_rubygems_version: !ruby/object:Gem::Requirement
144
160
  requirements:
145
161
  - - ">="
@@ -147,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
163
  version: '0'
148
164
  requirements: []
149
165
  rubyforge_project:
150
- rubygems_version: 2.5.1
166
+ rubygems_version: 2.6.8
151
167
  signing_key:
152
168
  specification_version: 4
153
169
  summary: Ruby notifier for https://airbrake.io
@@ -165,5 +181,6 @@ test_files:
165
181
  - spec/notifier_spec/whitelist_keys_spec.rb
166
182
  - spec/notifier_spec.rb
167
183
  - spec/payload_truncator_spec.rb
184
+ - spec/promise_spec.rb
168
185
  - spec/spec_helper.rb
169
186
  - spec/sync_sender_spec.rb