airbrake-ruby 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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