failbot 2.5.2 → 2.10.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
  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