airbrake-ruby 6.1.2 → 6.2.1

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
  SHA256:
3
- metadata.gz: bf054506d2927e93c5bb6d6050ff0b13dd6cef15c901243ad1e7f4871a4f8568
4
- data.tar.gz: b054ea5189464b1ebf822812cb7192893fbf84ef085b57fdfca9ead3d3361c6d
3
+ metadata.gz: 868a2afd78cb0fe3fa7c94fdfcef19cc34c3355ebe9665945f37326410deaa95
4
+ data.tar.gz: 6e21e26d4999a8bcb1044f96952718835d9c49b79167593be0d4f2529e7b8153
5
5
  SHA512:
6
- metadata.gz: 3469e65f834026e9c67c0200d2eca1786f8ea566b620f0ebc4175687a1d8b1ce41563460edc4a0be622e0d2dc93eade382fca1770bcb31b3f221dc188f5e4362
7
- data.tar.gz: ee6edd6c1a279ba33ca4e898198aff4147e07be36feb9aa165fd076668499a08cc41d8fc5158dff09ee9fc7d5732f0e5c87b4e3bc1ca57d39485651d0431082a
6
+ metadata.gz: 26d41719d656683977497104627ae82024868b75372db7886b18bef8bb654f6f5fe7617770b2d981063fb3dfa139e1011b65ad190ed93fa850118ca06bf0bb8f
7
+ data.tar.gz: 5d1fcffd4559b02a2e0a3c57561f6cec279e4561db5c28eb09dadfd4a174347ad7e8d62df9162248359f78b70fac89c363f9b673cc71d1d303faa28db2681c68
@@ -5,20 +5,20 @@ module Airbrake
5
5
  # @api private
6
6
  # @since v1.0.0
7
7
  class AsyncSender
8
- include Loggable
9
-
10
8
  def initialize(method = :post, name = 'async-sender')
11
9
  @config = Airbrake::Config.instance
12
- @method = method
10
+ @sync_sender = SyncSender.new(method)
13
11
  @name = name
14
12
  end
15
13
 
16
14
  # Asynchronously sends a notice to Airbrake.
17
15
  #
18
- # @param [Hash] payload Whatever needs to be sent
16
+ # @param [Airbrake::Notice] data Whatever needs to be sent
17
+ # @param [Airbrake::Promise] promise
18
+ # @param [URI] endpoint Where to send +data+
19
19
  # @return [Airbrake::Promise]
20
- def send(payload, promise, endpoint = @config.error_endpoint)
21
- unless thread_pool << [payload, promise, endpoint]
20
+ def send(data, promise, endpoint = @config.error_endpoint)
21
+ unless thread_pool << [data, promise, endpoint]
22
22
  return promise.reject(
23
23
  "AsyncSender has reached its capacity of #{@config.queue_size}",
24
24
  )
@@ -29,6 +29,7 @@ module Airbrake
29
29
 
30
30
  # @return [void]
31
31
  def close
32
+ @sync_sender.close
32
33
  thread_pool.close
33
34
  end
34
35
 
@@ -45,15 +46,12 @@ module Airbrake
45
46
  private
46
47
 
47
48
  def thread_pool
48
- @thread_pool ||= begin
49
- sender = SyncSender.new(@method)
50
- ThreadPool.new(
51
- name: @name,
52
- worker_size: @config.workers,
53
- queue_size: @config.queue_size,
54
- block: proc { |args| sender.send(*args) },
55
- )
56
- end
49
+ @thread_pool ||= ThreadPool.new(
50
+ name: @name,
51
+ worker_size: @config.workers,
52
+ queue_size: @config.queue_size,
53
+ block: proc { |args| @sync_sender.send(*args) },
54
+ )
57
55
  end
58
56
  end
59
57
  end
@@ -0,0 +1,123 @@
1
+ module Airbrake
2
+ # Backlog accepts notices and APM events and synchronously sends them in the
3
+ # background at regular intervals. The backlog is a queue of data that failed
4
+ # to be sent due to some error. In a nutshell, it's a retry mechanism.
5
+ #
6
+ # @api private
7
+ # @since v6.2.0
8
+ class Backlog
9
+ include Loggable
10
+
11
+ # @return [Integer] how many records to keep in the backlog
12
+ BACKLOG_SIZE = 100
13
+
14
+ # @return [Integer] flush period in seconds
15
+ TWO_MINUTES = 60 * 2
16
+
17
+ def initialize(sync_sender, flush_period = TWO_MINUTES)
18
+ @sync_sender = sync_sender
19
+ @flush_period = flush_period
20
+ @queue = SizedQueue.new(BACKLOG_SIZE).extend(MonitorMixin)
21
+ @has_backlog_data = @queue.new_cond
22
+ @schedule_flush = nil
23
+
24
+ @seen = Set.new
25
+ end
26
+
27
+ # Appends data to the backlog. Once appended, the flush schedule will
28
+ # start. Chainable.
29
+ #
30
+ # @example
31
+ # backlog << [{ 'data' => 1 }, 'https://airbrake.io/api']
32
+ #
33
+ # @param [Array<#to_json, String>] data An array of two elements, where the
34
+ # first element is the data we are sending and the second element is the
35
+ # URL that we are sending to
36
+ # @return [self]
37
+ def <<(data)
38
+ @queue.synchronize do
39
+ return self if @seen.include?(data)
40
+
41
+ @seen << data
42
+
43
+ begin
44
+ @queue.push(data, true)
45
+ rescue ThreadError
46
+ logger.error("#{LOG_LABEL} Airbrake::Backlog full")
47
+ return self
48
+ end
49
+
50
+ @has_backlog_data.signal
51
+ schedule_flush
52
+
53
+ self
54
+ end
55
+ end
56
+
57
+ # Closes all the resources that this sender has allocated.
58
+ #
59
+ # @return [void]
60
+ # @since v6.2.0
61
+ def close
62
+ @queue.synchronize do
63
+ if @schedule_flush
64
+ @schedule_flush.kill
65
+ logger.debug("#{LOG_LABEL} Airbrake::Backlog closed")
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def schedule_flush
73
+ @schedule_flush ||= Thread.new do
74
+ loop do
75
+ @queue.synchronize do
76
+ wait
77
+ next if @queue.empty?
78
+
79
+ flush
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def wait
86
+ @has_backlog_data.wait(@flush_period) while time_elapsed < @flush_period
87
+ @last_flush = nil
88
+ end
89
+
90
+ def time_elapsed
91
+ MonotonicTime.time_in_s - last_flush
92
+ end
93
+
94
+ def last_flush
95
+ @last_flush ||= MonotonicTime.time_in_s
96
+ end
97
+
98
+ def flush
99
+ unless @queue.empty?
100
+ logger.debug(
101
+ "#{LOG_LABEL} Airbrake::Backlog flushing #{@queue.size} messages",
102
+ )
103
+ end
104
+
105
+ failed = 0
106
+
107
+ until @queue.empty?
108
+ data, endpoint = @queue.pop
109
+ promise = Airbrake::Promise.new
110
+ @sync_sender.send(data, promise, endpoint)
111
+ failed += 1 if promise.rejected?
112
+ end
113
+
114
+ if failed > 0
115
+ logger.debug(
116
+ "#{LOG_LABEL} Airbrake::Backlog #{failed} messages were not flushed",
117
+ )
118
+ end
119
+
120
+ @seen.clear
121
+ end
122
+ end
123
+ end
@@ -44,12 +44,8 @@ module Airbrake
44
44
  # @return [String] the host, which provides the API endpoint to which
45
45
  # exceptions should be sent
46
46
  # @api public
47
- attr_accessor :host
48
-
49
- # @since v5.0.0
50
- alias error_host host
51
47
  # @since v5.0.0
52
- alias error_host= host=
48
+ attr_accessor :error_host
53
49
 
54
50
  # @return [String] the host, which provides the API endpoint to which
55
51
  # APM data should be sent
@@ -136,6 +132,12 @@ module Airbrake
136
132
  # @since v5.2.0
137
133
  attr_accessor :remote_config
138
134
 
135
+ # @return [Boolean] true if the library should keep a backlog of failed
136
+ # notices or APM events and retry them after an interval, false otherwise
137
+ # @api public
138
+ # @since v6.2.0
139
+ attr_accessor :backlog
140
+
139
141
  class << self
140
142
  # @return [Config]
141
143
  attr_writer :instance
@@ -157,8 +159,7 @@ module Airbrake
157
159
  self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
158
160
  self.project_id = user_config[:project_id]
159
161
  self.project_key = user_config[:project_key]
160
- self.error_host = 'https://api.airbrake.io'
161
- self.apm_host = 'https://api.airbrake.io'
162
+ self.error_host = self.apm_host = 'https://api.airbrake.io'
162
163
  self.remote_config_host = 'https://notifier-configs.airbrake.io'
163
164
 
164
165
  self.ignore_environments = []
@@ -180,6 +181,7 @@ module Airbrake
180
181
  self.job_stats = true
181
182
  self.error_notifications = true
182
183
  self.remote_config = true
184
+ self.backlog = true
183
185
 
184
186
  merge(user_config)
185
187
  end
@@ -262,6 +264,19 @@ module Airbrake
262
264
  end
263
265
  end
264
266
 
267
+ HOST_DEPRECATION_MSG = "**Airbrake: the 'host' option is deprecated. Use " \
268
+ "'error_host' instead".freeze
269
+
270
+ def host
271
+ logger.warn(HOST_DEPRECATION_MSG)
272
+ @error_host
273
+ end
274
+
275
+ def host=(value)
276
+ logger.warn(HOST_DEPRECATION_MSG)
277
+ @error_host = value
278
+ end
279
+
265
280
  private
266
281
 
267
282
  def set_option(option, value)
@@ -68,6 +68,7 @@ module Airbrake
68
68
 
69
69
  # @see Airbrake.close
70
70
  def close
71
+ @sync_sender.close
71
72
  @async_sender.close
72
73
  end
73
74
 
@@ -50,6 +50,7 @@ module Airbrake
50
50
  def close
51
51
  @payload.synchronize do
52
52
  @schedule_flush.kill if @schedule_flush
53
+ @sync_sender.close
53
54
  @async_sender.close
54
55
  end
55
56
  end
@@ -8,9 +8,56 @@ module Airbrake
8
8
  # @return [Integer] the limit of the response body
9
9
  TRUNCATE_LIMIT = 100
10
10
 
11
+ # @return [Integer] HTTP code returned when the server cannot or will not
12
+ # process the request due to something that is perceived to be a client
13
+ # error
14
+ # @since v6.2.0
15
+ BAD_REQUEST = 400
16
+
17
+ # @return [Integer] HTTP code returned when client request has not been
18
+ # completed because it lacks valid authentication credentials for the
19
+ # requested resource
20
+ # @since v6.2.0
21
+ UNAUTHORIZED = 401
22
+
23
+ # @return [Integer] HTTP code returned when the server understands the
24
+ # request but refuses to authorize it
25
+ # @since v6.2.0
26
+ FORBIDDEN = 403
27
+
28
+ # @return [Integer] HTTP code returned when the server would like to shut
29
+ # down this unused connection
30
+ # @since v6.2.0
31
+ REQUEST_TIMEOUT = 408
32
+
33
+ # @return [Integer] HTTP code returned when there's a request conflict with
34
+ # the current state of the target resource
35
+ # @since v6.2.0
36
+ CONFLICT = 409
37
+
38
+ # @return [Integer]
39
+ # @since v6.2.0
40
+ ENHANCE_YOUR_CALM = 420
41
+
11
42
  # @return [Integer] HTTP code returned when an IP sends over 10k/min notices
12
43
  TOO_MANY_REQUESTS = 429
13
44
 
45
+ # @return [Integer] HTTP code returned when the server encountered an
46
+ # unexpected condition that prevented it from fulfilling the request
47
+ # @since v6.2.0
48
+ INTERNAL_SERVER_ERROR = 500
49
+
50
+ # @return [Integer] HTTP code returened when the server, while acting as a
51
+ # gateway or proxy, received an invalid response from the upstream server
52
+ # @since v6.2.0
53
+ BAD_GATEWAY = 502
54
+
55
+ # @return [Integer] HTTP code returened when the server, while acting as a
56
+ # gateway or proxy, did not get a response in time from the upstream
57
+ # server that it needed in order to complete the request
58
+ # @since v6.2.0
59
+ GATEWAY_TIMEOUT = 504
60
+
14
61
  class << self
15
62
  include Loggable
16
63
  end
@@ -33,24 +80,28 @@ module Airbrake
33
80
  parsed_body = JSON.parse(body)
34
81
  logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}")
35
82
  parsed_body
36
- when 400, 401, 403, 420
83
+ when BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, ENHANCE_YOUR_CALM
37
84
  parsed_body = JSON.parse(body)
38
85
  logger.error("#{LOG_LABEL} #{parsed_body['message']}")
39
- parsed_body
86
+ parsed_body.merge('code' => code, 'error' => parsed_body['message'])
40
87
  when TOO_MANY_REQUESTS
41
88
  parsed_body = JSON.parse(body)
42
89
  msg = "#{LOG_LABEL} #{parsed_body['message']}"
43
90
  logger.error(msg)
44
- { 'error' => msg, 'rate_limit_reset' => rate_limit_reset(response) }
91
+ {
92
+ 'code' => code,
93
+ 'error' => msg,
94
+ 'rate_limit_reset' => rate_limit_reset(response),
95
+ }
45
96
  else
46
97
  body_msg = truncated_body(body)
47
98
  logger.error("#{LOG_LABEL} unexpected code (#{code}). Body: #{body_msg}")
48
- { 'error' => body_msg }
99
+ { 'code' => code, 'error' => body_msg }
49
100
  end
50
101
  rescue StandardError => ex
51
102
  body_msg = truncated_body(body)
52
103
  logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}")
53
- { 'error' => ex.inspect }
104
+ { 'code' => code, 'error' => ex.inspect }
54
105
  end
55
106
  end
56
107
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
@@ -9,6 +9,20 @@ module Airbrake
9
9
  # @return [String] body for HTTP requests
10
10
  CONTENT_TYPE = 'application/json'.freeze
11
11
 
12
+ # @return [Array<Integer>] response codes that are good to be backlogged
13
+ # @since v6.2.0
14
+ BACKLOGGABLE_STATUS_CODES = [
15
+ Response::BAD_REQUEST,
16
+ Response::FORBIDDEN,
17
+ Response::ENHANCE_YOUR_CALM,
18
+ Response::REQUEST_TIMEOUT,
19
+ Response::CONFLICT,
20
+ Response::TOO_MANY_REQUESTS,
21
+ Response::INTERNAL_SERVER_ERROR,
22
+ Response::BAD_GATEWAY,
23
+ Response::GATEWAY_TIMEOUT,
24
+ ].freeze
25
+
12
26
  include Loggable
13
27
 
14
28
  # @param [Symbol] method HTTP method to use to send payload
@@ -16,6 +30,7 @@ module Airbrake
16
30
  @config = Airbrake::Config.instance
17
31
  @method = method
18
32
  @rate_limit_reset = Time.now
33
+ @backlog = Backlog.new(self) if @config.backlog
19
34
  end
20
35
 
21
36
  # Sends a POST or PUT request to the given +endpoint+ with the +data+ payload.
@@ -26,15 +41,11 @@ module Airbrake
26
41
  def send(data, promise, endpoint = @config.error_endpoint)
27
42
  return promise if rate_limited_ip?(promise)
28
43
 
29
- response = nil
30
44
  req = build_request(endpoint, data)
31
-
32
45
  return promise if missing_body?(req, promise)
33
46
 
34
- https = build_https(endpoint)
35
-
36
47
  begin
37
- response = https.request(req)
48
+ response = build_https(endpoint).request(req)
38
49
  rescue StandardError => ex
39
50
  reason = "#{LOG_LABEL} HTTP error: #{ex}"
40
51
  logger.error(reason)
@@ -42,15 +53,22 @@ module Airbrake
42
53
  end
43
54
 
44
55
  parsed_resp = Response.parse(response)
45
- if parsed_resp.key?('rate_limit_reset')
46
- @rate_limit_reset = parsed_resp['rate_limit_reset']
47
- end
56
+ handle_rate_limit(parsed_resp)
57
+ @backlog << [data, endpoint] if add_to_backlog?(parsed_resp)
48
58
 
49
59
  return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
50
60
 
51
61
  promise.resolve(parsed_resp)
52
62
  end
53
63
 
64
+ # Closes all the resources that this sender has allocated.
65
+ #
66
+ # @return [void]
67
+ # @since v6.2.0
68
+ def close
69
+ @backlog.close
70
+ end
71
+
54
72
  private
55
73
 
56
74
  def build_https(uri)
@@ -86,6 +104,19 @@ module Airbrake
86
104
  req
87
105
  end
88
106
 
107
+ def handle_rate_limit(parsed_resp)
108
+ return unless parsed_resp.key?('rate_limit_reset')
109
+
110
+ @rate_limit_reset = parsed_resp['rate_limit_reset']
111
+ end
112
+
113
+ def add_to_backlog?(parsed_resp)
114
+ return unless @backlog
115
+ return unless parsed_resp.key?('code')
116
+
117
+ BACKLOGGABLE_STATUS_CODES.include?(parsed_resp['code'])
118
+ end
119
+
89
120
  def proxy_params
90
121
  return unless @config.proxy.key?(:host)
91
122
 
@@ -79,7 +79,7 @@ module Airbrake
79
79
  end
80
80
  output += mean_arr
81
81
  # Variable length encoding of numbers
82
- c_arr = @centroids.each_value.each_with_object([]) do |c, arr|
82
+ c_arr = @centroids.each_value.with_object([]) do |c, arr|
83
83
  k = 0
84
84
  n = c.n
85
85
  while n < 0 || n > 0x7f
@@ -122,7 +122,6 @@ module Airbrake
122
122
 
123
123
  def spawn_workers
124
124
  @worker_size.times { @workers.add(spawn_worker) }
125
- @workers.enclose
126
125
  end
127
126
 
128
127
  private
@@ -3,7 +3,7 @@
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
5
  # @api public
6
- AIRBRAKE_RUBY_VERSION = '6.1.2'.freeze
6
+ AIRBRAKE_RUBY_VERSION = '6.2.1'.freeze
7
7
 
8
8
  # @return [Hash{Symbol=>String}] the information about the notifier library
9
9
  # @since v5.0.0
data/lib/airbrake-ruby.rb CHANGED
@@ -18,9 +18,9 @@ require 'airbrake-ruby/remote_settings/settings_data'
18
18
  require 'airbrake-ruby/remote_settings'
19
19
  require 'airbrake-ruby/promise'
20
20
  require 'airbrake-ruby/thread_pool'
21
+ require 'airbrake-ruby/response'
21
22
  require 'airbrake-ruby/sync_sender'
22
23
  require 'airbrake-ruby/async_sender'
23
- require 'airbrake-ruby/response'
24
24
  require 'airbrake-ruby/nested_exception'
25
25
  require 'airbrake-ruby/ignorable'
26
26
  require 'airbrake-ruby/inspectable'
@@ -59,6 +59,7 @@ require 'airbrake-ruby/monotonic_time'
59
59
  require 'airbrake-ruby/timed_trace'
60
60
  require 'airbrake-ruby/queue'
61
61
  require 'airbrake-ruby/context'
62
+ require 'airbrake-ruby/backlog'
62
63
 
63
64
  # Airbrake is a thin wrapper around instances of the notifier classes (such as
64
65
  # notice, performance & deploy notifiers). It creates a way to access them via a
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: 6.1.2
4
+ version: 6.2.1
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: 2022-09-01 00:00:00.000000000 Z
11
+ date: 2023-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree3
@@ -16,14 +16,112 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.5'
19
+ version: '0.6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.5'
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-its
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: benchmark-ips
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
27
125
  description: |
28
126
  Airbrake Ruby is a plain Ruby notifier for Airbrake (https://airbrake.io), the
29
127
  leading exception reporting service. Airbrake Ruby provides minimalist API that
@@ -40,6 +138,7 @@ extra_rdoc_files: []
40
138
  files:
41
139
  - lib/airbrake-ruby.rb
42
140
  - lib/airbrake-ruby/async_sender.rb
141
+ - lib/airbrake-ruby/backlog.rb
43
142
  - lib/airbrake-ruby/backtrace.rb
44
143
  - lib/airbrake-ruby/benchmark.rb
45
144
  - lib/airbrake-ruby/code_hunk.rb
@@ -113,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
212
  - !ruby/object:Gem::Version
114
213
  version: '0'
115
214
  requirements: []
116
- rubygems_version: 3.3.3
215
+ rubygems_version: 3.4.6
117
216
  signing_key:
118
217
  specification_version: 4
119
218
  summary: Ruby notifier for https://airbrake.io