failbot 2.5.2 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e17fb2d80a08382f07201582e708558ab9288c33c085950136d42c38ac9e47ff
4
- data.tar.gz: 664d9e980cb2901a1487518435a8c15bcd1e7fbce8d72eb16298e43db50d531c
3
+ metadata.gz: a2261cff70dfa6a7d6a35eec441d3440055ab89166e3441d61d10ccbd7fe635d
4
+ data.tar.gz: a163db78583fa4ceb80be66e5edb2477fec732718ff9057d331075163f4a35d2
5
5
  SHA512:
6
- metadata.gz: 5228d0b4914759a86d5605b8573f4c55de41716b2f9fa1248c6df87c169dacff3653a46da0ad4287866ecdb8a60aac5b0ed762202c0aaff89444e3b7cbbd75da
7
- data.tar.gz: 332e27bc52d28068b62b6eda1fe30925401788ab3225f68d0a09573e07182f2caa6e7437edf6627cd94cba9fa0be04c44fd6a873e594428ea8bf78a22e2707fa
6
+ metadata.gz: 915c6f252c20266490c1d345bacf92a246dc352fd409126c260954bf65f569a8a8c05566883c592e68de12af3516722c0469526ac3824f52ad670c1dd161eb3a
7
+ data.tar.gz: '08e40f7a93557a96a8e9b6857eb3302781f360c47b914f587330a62599b7c6d6a9b93984d58c40e2287ec7d1de4bdcd5ddc15e1ed6b25486a7d26f6b240d7678'
@@ -76,17 +76,17 @@ module Failbot
76
76
  warn "#{caller[0]} Failbot.report_errors? is deprecated and will be " \
77
77
  "removed in subsequent releases."
78
78
 
79
- if @report_errors.nil?
79
+ if @thread_local_report_errors.nil?
80
80
  config['report_errors']
81
81
  else
82
- @report_errors
82
+ @thread_local_report_errors.value
83
83
  end
84
84
  end
85
85
 
86
86
  def report_errors=(v)
87
87
  warn "#{caller[0]} Failbot.report_errors= is deprecated and will be " \
88
88
  "removed in subsequent releases."
89
- @report_errors = v
89
+ @thread_local_report_errors.value = v
90
90
  end
91
91
 
92
92
  # Load and initialize the exception reporting backend as specified by
@@ -14,7 +14,7 @@ module Failbot
14
14
  def self.call(e)
15
15
  message = e.message.to_s
16
16
  class_name = e.class.to_s
17
- stacktrace = begin
17
+ begin
18
18
  Failbot.backtrace_parser.call(e)
19
19
  rescue => ex
20
20
  message += "\nUnable to parse backtrace (#{ex.inspect})\nDon't put non-backtrace text in Exception#backtrace please!\nSo-called backtrace follows:\n#{e.backtrace.join("\n")}"
@@ -22,6 +22,7 @@ module Failbot
22
22
  EMPTY_ARRAY
23
23
  end
24
24
  {
25
+ "platform" => "ruby",
25
26
  "exception_detail" => exception_details(e),
26
27
  "ruby" => RUBY_DESCRIPTION,
27
28
  "created_at" => Time.now.utc.iso8601(6)
@@ -4,8 +4,11 @@ require 'json'
4
4
 
5
5
  module Failbot
6
6
  class Haystack
7
- def initialize(url)
7
+ attr_accessor :connect_timeout, :rw_timeout
8
+ def initialize(url, connect_timeout=nil, timeout_seconds=nil)
8
9
  @url = url
10
+ @connect_timeout = connect_timeout
11
+ @rw_timeout = timeout_seconds - @connect_timeout.to_f if timeout_seconds
9
12
  end
10
13
 
11
14
  def user
@@ -24,7 +27,7 @@ module Failbot
24
27
 
25
28
  # Raise if the exception doesn't make it to Haystack, ensures the failure
26
29
  # is logged
27
- raise StandardError, "couldn't send exception to Haystack" unless response.code == "201"
30
+ raise StandardError, "couldn't send exception to Haystack: #{response.code} #{response.message}" unless response.code == "201"
28
31
  end
29
32
 
30
33
  def self.send_data(data)
@@ -50,8 +53,17 @@ module Failbot
50
53
  # use SSL if applicable
51
54
  http.use_ssl = true if @url.scheme == "https"
52
55
 
56
+ # Set the connect timeout if it was provided
57
+ http.open_timeout = @connect_timeout if @connect_timeout
58
+ http.read_timeout = @rw_timeout if @rw_timeout
59
+ http.write_timeout = @rw_timeout if @rw_timeout
60
+
53
61
  # push it through
54
62
  http.request(request)
63
+ ensure
64
+ if defined?(http) && http.started?
65
+ http.finish
66
+ end
55
67
  end
56
68
  end
57
69
  end
@@ -1,11 +1,11 @@
1
1
  module Failbot
2
2
  class HTTPBackend
3
- def initialize(url)
3
+ def initialize(url, connect_timeout = nil, timeout_seconds = nil)
4
4
  if url.to_s.empty?
5
5
  raise ArgumentError, "FAILBOT_HAYSTACK_URL setting required."
6
6
  end
7
7
 
8
- @haystack = Failbot::Haystack.new(url)
8
+ @haystack = Failbot::Haystack.new(url, connect_timeout, timeout_seconds)
9
9
  end
10
10
 
11
11
  def report(data)
@@ -19,5 +19,21 @@ module Failbot
19
19
  def ping
20
20
  @haystack.ping
21
21
  end
22
+
23
+ def connect_timeout
24
+ @haystack.connect_timeout
25
+ end
26
+
27
+ def connect_timeout=(timeout)
28
+ @haystack.connect_timeout = timeout
29
+ end
30
+
31
+ def rw_timeout
32
+ @haystack.rw_timeout
33
+ end
34
+
35
+ def rw_timeout=(timeout)
36
+ @haystack.rw_timeout = timeout
37
+ end
22
38
  end
23
39
  end
@@ -17,7 +17,7 @@ module Failbot
17
17
  def report(data)
18
18
  payload = data.to_json
19
19
 
20
- response = socket do |s|
20
+ socket do |s|
21
21
  s.send(payload, 0)
22
22
  nil
23
23
  end
@@ -10,16 +10,17 @@ module Failbot
10
10
 
11
11
  def call(env)
12
12
  Failbot.reset!
13
- Failbot.push @other.merge(self.class.context(env)) do
14
- begin
15
- start = Time.now
16
- @app.call(env)
17
- rescue Object => boom
18
- elapsed = Time.now - start
19
- Failbot.report(boom, {:time => elapsed.to_s})
20
- raise
21
- end
13
+ Failbot.push @other.merge(self.class.context(env))
14
+ begin
15
+ start = Time.now
16
+ @app.call(env)
17
+ rescue Object => boom
18
+ elapsed = Time.now - start
19
+ Failbot.report(boom, {:time => elapsed.to_s})
20
+ raise
22
21
  end
22
+ ensure
23
+ Failbot.reset!
23
24
  end
24
25
 
25
26
  def self.context(env)
@@ -6,19 +6,22 @@ module Failbot
6
6
  class ThreadLocalVariable
7
7
  def initialize(&block)
8
8
  @default_block = block || proc {}
9
- @key = "_thread_local_variable##{object_id}"
9
+ @key = "_thread_local_variable_#{object_id}".to_sym
10
10
  end
11
11
 
12
12
  def value
13
- if Thread.current.key?(@key)
14
- Thread.current[@key]
15
- else
16
- Thread.current[@key] = @default_block.call
17
- end
13
+ value_from_thread(Thread.current)
18
14
  end
19
15
 
20
16
  def value=(val)
21
- Thread.current[@key] = val
17
+ Thread.current.thread_variable_set(@key, val)
18
+ end
19
+
20
+ def value_from_thread(thread)
21
+ if !thread.thread_variables.include?(@key)
22
+ thread.thread_variable_set(@key, @default_block.call)
23
+ end
24
+ thread.thread_variable_get(@key)
22
25
  end
23
26
  end
24
27
  end
@@ -1,3 +1,3 @@
1
1
  module Failbot
2
- VERSION = "2.5.2"
2
+ VERSION = "2.10.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ module Failbot
2
+ class WaiterBackend
3
+ # This backend waits a configured amount of time before returning. This is
4
+ # intended be used to test timeouts. Delay is the number of seconds to wait.
5
+
6
+ attr_reader :reports
7
+ def initialize(delay = 5)
8
+ @delay = delay
9
+ @reports = []
10
+ end
11
+
12
+ def report(data)
13
+ @reports << data
14
+ sleep(@delay)
15
+ end
16
+
17
+ def ping
18
+ # nop
19
+ end
20
+ end
21
+ end
data/lib/failbot.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  require 'yaml'
2
- require 'digest/md5'
2
+ require 'digest/sha2'
3
3
  require 'logger'
4
4
  require 'socket'
5
5
  require "time"
6
+ require "timeout"
6
7
  require "date"
7
8
  require "uri"
8
9
 
@@ -25,6 +26,7 @@ module Failbot
25
26
  autoload :HTTPBackend, 'failbot/http_backend'
26
27
  autoload :MemoryBackend, 'failbot/memory_backend'
27
28
  autoload :JSONBackend, 'failbot/json_backend'
29
+ autoload :WaiterBackend, 'failbot/waiter_backend'
28
30
 
29
31
  autoload :ThreadLocalVariable, 'failbot/thread_local_variable'
30
32
 
@@ -87,7 +89,7 @@ module Failbot
87
89
 
88
90
  # Default to the original exception format used by haystack unless
89
91
  # apps opt in to the newer version.
90
- self.exception_format = :haystack
92
+ self.exception_format = :structured
91
93
 
92
94
  # Set a callable that is responsible for parsing and formatting ruby
93
95
  # backtraces. This is only necessary to set if your app deals with
@@ -146,15 +148,29 @@ module Failbot
146
148
  @thread_local_already_reporting = ::Failbot::ThreadLocalVariable.new { false }
147
149
 
148
150
  populate_context_from_settings(settings)
151
+ @enable_timeout = false
152
+ if settings.key?("FAILBOT_TIMEOUT_MS")
153
+ @timeout_seconds = settings["FAILBOT_TIMEOUT_MS"].to_f / 1000
154
+ @enable_timeout = (@timeout_seconds > 0.0)
155
+ end
156
+
157
+ @connect_timeout_seconds = nil
158
+ if settings.key?("FAILBOT_CONNECT_TIMEOUT_MS")
159
+ @connect_timeout_seconds = settings["FAILBOT_CONNECT_TIMEOUT_MS"].to_f / 1000
160
+ # unset the value if it's not parsing to something valid
161
+ @connect_timeout_seconds = nil unless @connect_timeout_seconds > 0
162
+ end
149
163
 
150
164
  self.backend =
151
165
  case (name = settings["FAILBOT_BACKEND"])
152
166
  when "memory"
153
167
  Failbot::MemoryBackend.new
168
+ when "waiter"
169
+ Failbot::WaiterBackend.new
154
170
  when "file"
155
171
  Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
156
172
  when "http"
157
- Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]))
173
+ Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]), @connect_timeout_seconds, @timeout_seconds)
158
174
  when 'json'
159
175
  Failbot::JSONBackend.new(settings["FAILBOT_BACKEND_JSON_HOST"], settings["FAILBOT_BACKEND_JSON_PORT"])
160
176
  when 'console'
@@ -164,7 +180,9 @@ module Failbot
164
180
  end
165
181
 
166
182
  @raise_errors = !settings["FAILBOT_RAISE"].to_s.empty?
167
- @report_errors = settings["FAILBOT_REPORT"] != "0"
183
+ @thread_local_report_errors = ::Failbot::ThreadLocalVariable.new do
184
+ settings["FAILBOT_REPORT"] != "0"
185
+ end
168
186
 
169
187
  # allows overriding the 'app' value to send to single haystack bucket.
170
188
  # used primarily on ghe.io.
@@ -174,6 +192,10 @@ module Failbot
174
192
  if settings["FAILBOT_EXCEPTION_FORMAT"]
175
193
  self.exception_format = settings["FAILBOT_EXCEPTION_FORMAT"].to_sym
176
194
  end
195
+
196
+ @ignored_error_classes = settings.fetch("FAILBOT_IGNORED_ERROR_CLASSES", "").split(",").map do |class_name|
197
+ Module.const_get(class_name.strip)
198
+ end
177
199
  end
178
200
 
179
201
  # Bring in deprecated methods
@@ -196,6 +218,11 @@ module Failbot
196
218
  #
197
219
  # Returns the value returned by the block when given; otherwise, returns nil.
198
220
  def push(info={})
221
+ info.each do |key, value|
222
+ if value.kind_of?(Proc)
223
+ raise ArgumentError, "Proc usage has been removed from Failbot"
224
+ end
225
+ end
199
226
  context.push(info)
200
227
  yield if block_given?
201
228
  ensure
@@ -270,13 +297,13 @@ module Failbot
270
297
  # Default rollup for an exception. Exceptions with the same rollup are
271
298
  # grouped together in Haystack. The rollup is an MD5 hash of the exception
272
299
  # class and the raising file, line, and method.
273
- DEFAULT_ROLLUP = lambda do |exception, context|
300
+ DEFAULT_ROLLUP = lambda do |exception, context, thread|
274
301
  backtrace_line = Array(exception.backtrace).first || ""
275
302
  # We want all exceptions from foo.html.erb:123 to be grouped together, no
276
303
  # matter what wacky generated ERB method name is attached to it.
277
304
  cleaned_line = backtrace_line.sub(/_erb__[_\d]+'\z/, "_erb'")
278
305
 
279
- Digest::MD5.hexdigest("#{exception.class}#{cleaned_line}")
306
+ Digest::SHA256.hexdigest("#{exception.class}#{cleaned_line}")
280
307
  end
281
308
 
282
309
  private_constant :DEFAULT_ROLLUP
@@ -305,6 +332,8 @@ module Failbot
305
332
  #
306
333
  # Returns nothing.
307
334
  def report(e, other = {})
335
+ return if ignore_error?(e)
336
+
308
337
  if @raise_errors
309
338
  squash_contexts(context, exception_info(e), other) # surface problems squashing
310
339
  raise e
@@ -314,53 +343,22 @@ module Failbot
314
343
  end
315
344
 
316
345
  def report!(e, other = {})
317
- return unless @report_errors
346
+ report_with_context!(Thread.current, context, e, other)
347
+ end
318
348
 
319
- if already_reporting
320
- logger.warn "FAILBOT: asked to report while reporting!" rescue nil
321
- logger.warn e.message rescue nil
322
- logger.warn e.backtrace.join("\n") rescue nil
323
- return
349
+ def report_from_thread(thread, e, other = {})
350
+ if @raise_errors
351
+ squash_contexts(@thread_local_context.value_from_thread(thread), exception_info(e), other) # surface problems squashing
352
+ raise e
353
+ else
354
+ report_from_thread!(thread, e, other)
324
355
  end
325
- self.already_reporting = true
326
-
327
- begin
328
- data = squash_contexts(context, exception_info(e), other)
329
-
330
- if !data.has_key?("rollup")
331
- data = data.merge("rollup" => @rollup.call(e, data))
332
- end
333
-
334
- if @before_report
335
- data = squash_contexts(data, @before_report.call(e, data))
336
- end
337
-
338
- if @app_override
339
- data = data.merge("app" => @app_override)
340
- end
356
+ end
341
357
 
342
- data = scrub(sanitize(data))
343
- rescue Object => i
344
- log_failure("processing", data, e, i)
345
- self.already_reporting = false
346
- return
347
- end
358
+ def report_from_thread!(thread, e, other = {})
359
+ return if ignore_error?(e)
348
360
 
349
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
350
- instrumentation_data = {
351
- "report_status" => "error",
352
- }
353
- begin
354
- backend.report(data)
355
- instrumentation_data["report_status"] = "success"
356
- rescue Object => i
357
- log_failure("reporting", data, e, i)
358
- instrumentation_data["exception_type"] = i.class.name
359
- ensure
360
- instrumentation_data["elapsed_ms"] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).to_i
361
- instrument("report.failbot", data.merge(instrumentation_data)) rescue nil
362
- self.already_reporting = false
363
- end
361
+ report_with_context!(thread, @thread_local_context.value_from_thread(thread), e, other)
364
362
  end
365
363
 
366
364
  # Public: Disable exception reporting. This is equivalent to calling
@@ -373,14 +371,14 @@ module Failbot
373
371
  # block - an optional block to perform while reporting is disabled. If a block
374
372
  # is passed, reporting will be re-enabled after the block is called.
375
373
  def disable(&block)
376
- original_report_errors = @report_errors
377
- @report_errors = false
374
+ original_report_errors = @thread_local_report_errors.value
375
+ @thread_local_report_errors.value = false
378
376
 
379
377
  if block
380
378
  begin
381
379
  block.call
382
380
  ensure
383
- @report_errors = original_report_errors
381
+ @thread_local_report_errors.value = original_report_errors
384
382
  end
385
383
  end
386
384
  end
@@ -389,7 +387,7 @@ module Failbot
389
387
  # this can be called if it is explicitly disabled by calling `Failbot.disable`
390
388
  # or setting `FAILBOT_REPORTING => "0"` in `Failbot.setup`.
391
389
  def enable
392
- @report_errors = true
390
+ @thread_local_report_errors.value = true
393
391
  end
394
392
 
395
393
  # Public: exceptions that were reported. Only available when using the
@@ -413,7 +411,6 @@ module Failbot
413
411
 
414
412
  contexts_to_squash.flatten.each do |hash|
415
413
  hash.each do |key, value|
416
- value = (value.call rescue nil) if value.kind_of?(Proc)
417
414
  squashed[key.to_s] = value
418
415
  end
419
416
  end
@@ -435,6 +432,8 @@ module Failbot
435
432
  value
436
433
  when String, true, false
437
434
  value.to_s
435
+ when Proc
436
+ "proc usage is deprecated"
438
437
  when Array
439
438
  if key == EXCEPTION_DETAIL
440
439
  # special-casing for the exception_detail key, which is allowed to
@@ -476,7 +475,16 @@ module Failbot
476
475
  end
477
476
 
478
477
  def logger
479
- @logger ||= Logger.new($stderr)
478
+ @logger ||= Logger.new($stderr, formatter: proc { |severity, datetime, progname, msg|
479
+ log = case msg
480
+ when Hash
481
+ msg.map { |k,v| "#{k}=#{v.inspect}" }.join(" ")
482
+ else
483
+ %Q|msg="#{msg.inspect}"|
484
+ end
485
+ log_line = %Q|ts="#{datetime.utc.iso8601}" level=#{severity} logger=Failbot #{log}\n|
486
+ log_line.lstrip
487
+ })
480
488
  end
481
489
 
482
490
  def logger=(logger)
@@ -497,6 +505,63 @@ module Failbot
497
505
 
498
506
  private
499
507
 
508
+ def report_with_context!(thread, provided_context, e, other = {})
509
+ return unless @thread_local_report_errors.value
510
+ return if ignore_error?(e)
511
+
512
+ if already_reporting
513
+ logger.warn "FAILBOT: asked to report while reporting!" rescue nil
514
+ logger.warn e.message rescue nil
515
+ logger.warn e.backtrace.join("\n") rescue nil
516
+ return
517
+ end
518
+ self.already_reporting = true
519
+
520
+ begin
521
+ data = squash_contexts(provided_context, exception_info(e), other)
522
+
523
+ if !data.has_key?("rollup")
524
+ data = data.merge("rollup" => @rollup.call(e, data, thread))
525
+ end
526
+
527
+ if defined?(@before_report) && @before_report
528
+ data = squash_contexts(data, @before_report.call(e, data, thread))
529
+ end
530
+
531
+ if @app_override
532
+ data = data.merge("app" => @app_override)
533
+ end
534
+
535
+ data = scrub(sanitize(data))
536
+ rescue Object => i
537
+ log_failure("processing", data, e, i)
538
+ self.already_reporting = false
539
+ return
540
+ end
541
+
542
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
543
+ instrumentation_data = {
544
+ "report_status" => "error",
545
+ }
546
+ begin
547
+ if @enable_timeout
548
+ Timeout.timeout(@timeout_seconds) do
549
+ backend.report(data)
550
+ end
551
+ else
552
+ backend.report(data)
553
+ end
554
+ instrumentation_data["report_status"] = "success"
555
+ rescue Object => i
556
+ log_failure("reporting", data, e, i)
557
+ instrumentation_data["exception_type"] = i.class.name
558
+ ensure
559
+ instrumentation_data["elapsed_ms"] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).to_i
560
+ instrument("report.failbot", data.merge(instrumentation_data)) rescue nil
561
+ self.already_reporting = false
562
+ end
563
+ end
564
+
500
565
  # Internal: Publish an event to the instrumenter
501
566
  def instrument(name, payload = {})
502
567
  Failbot.instrumenter.instrument(name, payload) if Failbot.instrumenter
@@ -507,18 +572,51 @@ module Failbot
507
572
  def populate_context_from_settings(settings)
508
573
  settings.each do |key, value|
509
574
  if /\AFAILBOT_CONTEXT_(.+)\z/ =~ key
510
- context[0][$1.downcase] = value
575
+ key = $1.downcase
576
+ context[0][key] = value unless context[0][key]
511
577
  end
512
578
  end
513
579
  end
514
580
 
515
581
  def log_failure(action, data, original_exception, exception)
516
- # don't fail for any reason
517
- logger.debug "FAILBOT EXCEPTION: action=#{action} exception=#{exception.class.name} original_type: #{original_exception.class.name} data=#{data.inspect}" rescue nil
518
- logger.debug original_exception.message rescue nil
519
- logger.debug original_exception.backtrace.join("\n") rescue nil
520
- logger.debug exception.message rescue nil
521
- logger.debug exception.backtrace.join("\n") rescue nil
582
+ begin
583
+ record = {
584
+ "msg" => "exception",
585
+ "action" => action,
586
+ "data" => data,
587
+ }
588
+
589
+ record.merge!(to_semconv(exception))
590
+ logger.debug record
591
+
592
+ record = {
593
+ "msg" => "report-failed",
594
+ "action" => action,
595
+ "data" => data,
596
+ }
597
+ record.merge!(to_semconv(original_exception))
598
+ logger.debug record
599
+ rescue => e
600
+ raise e
601
+ end
602
+ end
603
+
604
+ def to_semconv(exception)
605
+ {
606
+ "exception.type" => exception.class.to_s,
607
+ "exception.message" => exception.message.encode("UTF-8", invalid: :replace, undef: :replace, replace: '�'),
608
+ "exception.backtrace" => exception.full_message(highlight: false, order: :top).encode('UTF-8', invalid: :replace, undef: :replace, replace: '�'),
609
+ }
610
+ end
611
+
612
+ def ignore_error?(error)
613
+ @cache ||= Hash.new do |hash, error_class|
614
+ hash[error_class] = @ignored_error_classes.any? do |ignored_error_class|
615
+ error_class.ancestors.include?(ignored_error_class)
616
+ end
617
+ end
618
+
619
+ @cache[error.class]
522
620
  end
523
621
 
524
622
  extend self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: failbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.2
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@rtomayko"
@@ -10,34 +10,34 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-04-07 00:00:00.000000000 Z
13
+ date: 2022-02-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - "~>"
19
+ - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '10.0'
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - "~>"
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '10.0'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: rack
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - "~>"
33
+ - - ">="
34
34
  - !ruby/object:Gem::Version
35
35
  version: 1.6.4
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - "~>"
40
+ - - ">="
41
41
  - !ruby/object:Gem::Version
42
42
  version: 1.6.4
43
43
  - !ruby/object:Gem::Dependency
@@ -82,6 +82,20 @@ dependencies:
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0.6'
85
+ - !ruby/object:Gem::Dependency
86
+ name: webmock
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '3.0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '3.0'
85
99
  description: "..."
86
100
  email:
87
101
  - github+failbot@lists.github.com
@@ -108,6 +122,7 @@ files:
108
122
  - lib/failbot/sensitive_data_scrubber.rb
109
123
  - lib/failbot/thread_local_variable.rb
110
124
  - lib/failbot/version.rb
125
+ - lib/failbot/waiter_backend.rb
111
126
  homepage: http://github.com/github/failbot#readme
112
127
  licenses:
113
128
  - MIT
@@ -127,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
142
  - !ruby/object:Gem::Version
128
143
  version: '0'
129
144
  requirements: []
130
- rubygems_version: 3.0.3
145
+ rubygems_version: 3.1.6
131
146
  signing_key:
132
147
  specification_version: 4
133
148
  summary: Deliver exceptions to Haystack