failbot 2.5.5 → 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: ef2b30021fd84983545c9fa374d030d9c1269d3ef51b778cf84d55fe96fde087
4
- data.tar.gz: 998dde878c99c47177d03ff693625e05f1522009d84bf4e5f1a12a0fb398ad0e
3
+ metadata.gz: a2261cff70dfa6a7d6a35eec441d3440055ab89166e3441d61d10ccbd7fe635d
4
+ data.tar.gz: a163db78583fa4ceb80be66e5edb2477fec732718ff9057d331075163f4a35d2
5
5
  SHA512:
6
- metadata.gz: 774ccebb4f0b04886309e32840d73f17b0278388a73aa6a15541870c791a832f68c767b140ed1d62527feff87ae880aedebf60e942946c103116103c32bda9be
7
- data.tar.gz: 778c39eea60f8acdd0b53246401351e1373bf9386259fde67adfb86c0956baa8780fdda6afcf01f942ee6327aa8672390ec57d699e93ade429c8123b6f5ef3a6
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,10 +4,11 @@ require 'json'
4
4
 
5
5
  module Failbot
6
6
  class Haystack
7
- attr_accessor :connect_timeout
8
- def initialize(url, connect_timeout=nil)
7
+ attr_accessor :connect_timeout, :rw_timeout
8
+ def initialize(url, connect_timeout=nil, timeout_seconds=nil)
9
9
  @url = url
10
10
  @connect_timeout = connect_timeout
11
+ @rw_timeout = timeout_seconds - @connect_timeout.to_f if timeout_seconds
11
12
  end
12
13
 
13
14
  def user
@@ -26,7 +27,7 @@ module Failbot
26
27
 
27
28
  # Raise if the exception doesn't make it to Haystack, ensures the failure
28
29
  # is logged
29
- 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"
30
31
  end
31
32
 
32
33
  def self.send_data(data)
@@ -54,6 +55,8 @@ module Failbot
54
55
 
55
56
  # Set the connect timeout if it was provided
56
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
57
60
 
58
61
  # push it through
59
62
  http.request(request)
@@ -1,12 +1,11 @@
1
1
  module Failbot
2
2
  class HTTPBackend
3
- def initialize(url, connect_timeout = nil)
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
- @connect_timeout = connect_timeout
9
- @haystack = Failbot::Haystack.new(url, connect_timeout)
8
+ @haystack = Failbot::Haystack.new(url, connect_timeout, timeout_seconds)
10
9
  end
11
10
 
12
11
  def report(data)
@@ -28,5 +27,13 @@ module Failbot
28
27
  def connect_timeout=(timeout)
29
28
  @haystack.connect_timeout = timeout
30
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
31
38
  end
32
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)
@@ -0,0 +1,27 @@
1
+ module Failbot
2
+ # Public: A simplified implementation of [`::Concurrent::ThreadLocalVar`](https://github.com/ruby-concurrency/concurrent-ruby/blob/7dc6eb04142f008ffa79a59c125669c6fcbb85a8/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb)
3
+ #
4
+ # Why not just use `concurrent-ruby`? We wanted to minimize external dependencies to avoid conflicts with gems already installed with `github/github`.
5
+ #
6
+ class ThreadLocalVariable
7
+ def initialize(&block)
8
+ @default_block = block || proc {}
9
+ @key = "_thread_local_variable_#{object_id}".to_sym
10
+ end
11
+
12
+ def value
13
+ value_from_thread(Thread.current)
14
+ end
15
+
16
+ def value=(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)
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module Failbot
2
- VERSION = "2.5.5"
2
+ VERSION = "2.10.0"
3
3
  end
data/lib/failbot.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
- require 'digest/md5'
2
+ require 'digest/sha2'
3
3
  require 'logger'
4
4
  require 'socket'
5
5
  require "time"
@@ -28,6 +28,8 @@ module Failbot
28
28
  autoload :JSONBackend, 'failbot/json_backend'
29
29
  autoload :WaiterBackend, 'failbot/waiter_backend'
30
30
 
31
+ autoload :ThreadLocalVariable, 'failbot/thread_local_variable'
32
+
31
33
  # Public: Set an instrumenter to be called when exceptions are reported.
32
34
  #
33
35
  # class CustomInstrumenter
@@ -46,9 +48,6 @@ module Failbot
46
48
  #
47
49
  attr_accessor :instrumenter
48
50
 
49
- # prevent recursive calls to Failbot.report!
50
- attr_accessor :already_reporting
51
-
52
51
  # Root directory of the project's source. Used to clean up stack traces if the exception format supports it
53
52
 
54
53
  def source_root=(str)
@@ -90,7 +89,7 @@ module Failbot
90
89
 
91
90
  # Default to the original exception format used by haystack unless
92
91
  # apps opt in to the newer version.
93
- self.exception_format = :haystack
92
+ self.exception_format = :structured
94
93
 
95
94
  # Set a callable that is responsible for parsing and formatting ruby
96
95
  # backtraces. This is only necessary to set if your app deals with
@@ -137,9 +136,16 @@ module Failbot
137
136
  return setup_deprecated(settings)
138
137
  end
139
138
 
140
- if default_context.respond_to?(:to_hash) && !default_context.to_hash.empty?
141
- context[0] = default_context.to_hash
139
+ initial_context = if default_context.respond_to?(:to_hash) && !default_context.to_hash.empty?
140
+ default_context.to_hash
141
+ else
142
+ { 'server' => hostname }
143
+ end
144
+
145
+ @thread_local_context = ::Failbot::ThreadLocalVariable.new do
146
+ [initial_context]
142
147
  end
148
+ @thread_local_already_reporting = ::Failbot::ThreadLocalVariable.new { false }
143
149
 
144
150
  populate_context_from_settings(settings)
145
151
  @enable_timeout = false
@@ -164,7 +170,7 @@ module Failbot
164
170
  when "file"
165
171
  Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
166
172
  when "http"
167
- Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]), @connect_timeout_seconds)
173
+ Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]), @connect_timeout_seconds, @timeout_seconds)
168
174
  when 'json'
169
175
  Failbot::JSONBackend.new(settings["FAILBOT_BACKEND_JSON_HOST"], settings["FAILBOT_BACKEND_JSON_PORT"])
170
176
  when 'console'
@@ -174,7 +180,9 @@ module Failbot
174
180
  end
175
181
 
176
182
  @raise_errors = !settings["FAILBOT_RAISE"].to_s.empty?
177
- @report_errors = settings["FAILBOT_REPORT"] != "0"
183
+ @thread_local_report_errors = ::Failbot::ThreadLocalVariable.new do
184
+ settings["FAILBOT_REPORT"] != "0"
185
+ end
178
186
 
179
187
  # allows overriding the 'app' value to send to single haystack bucket.
180
188
  # used primarily on ghe.io.
@@ -184,6 +192,10 @@ module Failbot
184
192
  if settings["FAILBOT_EXCEPTION_FORMAT"]
185
193
  self.exception_format = settings["FAILBOT_EXCEPTION_FORMAT"].to_sym
186
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
187
199
  end
188
200
 
189
201
  # Bring in deprecated methods
@@ -195,7 +207,7 @@ module Failbot
195
207
  # hashes are condensed down into one and included in the next report. Don't
196
208
  # mess with this structure directly - use the #push and #pop methods.
197
209
  def context
198
- @context ||= [{'server' => hostname}]
210
+ @thread_local_context.value
199
211
  end
200
212
 
201
213
  # Add info to be sent in the next failbot report, should one occur.
@@ -206,6 +218,11 @@ module Failbot
206
218
  #
207
219
  # Returns the value returned by the block when given; otherwise, returns nil.
208
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
209
226
  context.push(info)
210
227
  yield if block_given?
211
228
  ensure
@@ -219,7 +236,7 @@ module Failbot
219
236
 
220
237
  # Reset the context stack to a pristine state.
221
238
  def reset!
222
- @context = [context[0]]
239
+ @thread_local_context.value = [context[0]].dup
223
240
  end
224
241
 
225
242
  # Loops through the stack of contexts and deletes the given key if it exists.
@@ -280,13 +297,13 @@ module Failbot
280
297
  # Default rollup for an exception. Exceptions with the same rollup are
281
298
  # grouped together in Haystack. The rollup is an MD5 hash of the exception
282
299
  # class and the raising file, line, and method.
283
- DEFAULT_ROLLUP = lambda do |exception, context|
300
+ DEFAULT_ROLLUP = lambda do |exception, context, thread|
284
301
  backtrace_line = Array(exception.backtrace).first || ""
285
302
  # We want all exceptions from foo.html.erb:123 to be grouped together, no
286
303
  # matter what wacky generated ERB method name is attached to it.
287
304
  cleaned_line = backtrace_line.sub(/_erb__[_\d]+'\z/, "_erb'")
288
305
 
289
- Digest::MD5.hexdigest("#{exception.class}#{cleaned_line}")
306
+ Digest::SHA256.hexdigest("#{exception.class}#{cleaned_line}")
290
307
  end
291
308
 
292
309
  private_constant :DEFAULT_ROLLUP
@@ -315,6 +332,8 @@ module Failbot
315
332
  #
316
333
  # Returns nothing.
317
334
  def report(e, other = {})
335
+ return if ignore_error?(e)
336
+
318
337
  if @raise_errors
319
338
  squash_contexts(context, exception_info(e), other) # surface problems squashing
320
339
  raise e
@@ -324,59 +343,22 @@ module Failbot
324
343
  end
325
344
 
326
345
  def report!(e, other = {})
327
- return unless @report_errors
346
+ report_with_context!(Thread.current, context, e, other)
347
+ end
328
348
 
329
- if already_reporting
330
- logger.warn "FAILBOT: asked to report while reporting!" rescue nil
331
- logger.warn e.message rescue nil
332
- logger.warn e.backtrace.join("\n") rescue nil
333
- 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)
334
355
  end
335
- self.already_reporting = true
336
-
337
- begin
338
- data = squash_contexts(context, exception_info(e), other)
339
-
340
- if !data.has_key?("rollup")
341
- data = data.merge("rollup" => @rollup.call(e, data))
342
- end
343
-
344
- if @before_report
345
- data = squash_contexts(data, @before_report.call(e, data))
346
- end
347
-
348
- if @app_override
349
- data = data.merge("app" => @app_override)
350
- end
356
+ end
351
357
 
352
- data = scrub(sanitize(data))
353
- rescue Object => i
354
- log_failure("processing", data, e, i)
355
- self.already_reporting = false
356
- return
357
- end
358
+ def report_from_thread!(thread, e, other = {})
359
+ return if ignore_error?(e)
358
360
 
359
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
360
- instrumentation_data = {
361
- "report_status" => "error",
362
- }
363
- begin
364
- if @enable_timeout
365
- Timeout.timeout(@timeout_seconds) do
366
- backend.report(data)
367
- end
368
- else
369
- backend.report(data)
370
- end
371
- instrumentation_data["report_status"] = "success"
372
- rescue Object => i
373
- log_failure("reporting", data, e, i)
374
- instrumentation_data["exception_type"] = i.class.name
375
- ensure
376
- instrumentation_data["elapsed_ms"] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).to_i
377
- instrument("report.failbot", data.merge(instrumentation_data)) rescue nil
378
- self.already_reporting = false
379
- end
361
+ report_with_context!(thread, @thread_local_context.value_from_thread(thread), e, other)
380
362
  end
381
363
 
382
364
  # Public: Disable exception reporting. This is equivalent to calling
@@ -389,14 +371,14 @@ module Failbot
389
371
  # block - an optional block to perform while reporting is disabled. If a block
390
372
  # is passed, reporting will be re-enabled after the block is called.
391
373
  def disable(&block)
392
- original_report_errors = @report_errors
393
- @report_errors = false
374
+ original_report_errors = @thread_local_report_errors.value
375
+ @thread_local_report_errors.value = false
394
376
 
395
377
  if block
396
378
  begin
397
379
  block.call
398
380
  ensure
399
- @report_errors = original_report_errors
381
+ @thread_local_report_errors.value = original_report_errors
400
382
  end
401
383
  end
402
384
  end
@@ -405,7 +387,7 @@ module Failbot
405
387
  # this can be called if it is explicitly disabled by calling `Failbot.disable`
406
388
  # or setting `FAILBOT_REPORTING => "0"` in `Failbot.setup`.
407
389
  def enable
408
- @report_errors = true
390
+ @thread_local_report_errors.value = true
409
391
  end
410
392
 
411
393
  # Public: exceptions that were reported. Only available when using the
@@ -429,7 +411,6 @@ module Failbot
429
411
 
430
412
  contexts_to_squash.flatten.each do |hash|
431
413
  hash.each do |key, value|
432
- value = (value.call rescue nil) if value.kind_of?(Proc)
433
414
  squashed[key.to_s] = value
434
415
  end
435
416
  end
@@ -451,6 +432,8 @@ module Failbot
451
432
  value
452
433
  when String, true, false
453
434
  value.to_s
435
+ when Proc
436
+ "proc usage is deprecated"
454
437
  when Array
455
438
  if key == EXCEPTION_DETAIL
456
439
  # special-casing for the exception_detail key, which is allowed to
@@ -492,7 +475,16 @@ module Failbot
492
475
  end
493
476
 
494
477
  def logger
495
- @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
+ })
496
488
  end
497
489
 
498
490
  def logger=(logger)
@@ -503,8 +495,73 @@ module Failbot
503
495
  @hostname ||= Socket.gethostname
504
496
  end
505
497
 
498
+ def already_reporting=(bool)
499
+ @thread_local_already_reporting.value = bool
500
+ end
501
+
502
+ def already_reporting
503
+ @thread_local_already_reporting.value
504
+ end
505
+
506
506
  private
507
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
+
508
565
  # Internal: Publish an event to the instrumenter
509
566
  def instrument(name, payload = {})
510
567
  Failbot.instrumenter.instrument(name, payload) if Failbot.instrumenter
@@ -522,12 +579,44 @@ module Failbot
522
579
  end
523
580
 
524
581
  def log_failure(action, data, original_exception, exception)
525
- # don't fail for any reason
526
- logger.debug "FAILBOT EXCEPTION: action=#{action} exception=#{exception.class.name} original_type: #{original_exception.class.name} data=#{data.inspect}" rescue nil
527
- logger.debug original_exception.message rescue nil
528
- logger.debug original_exception.backtrace.join("\n") rescue nil
529
- logger.debug exception.message rescue nil
530
- 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]
531
620
  end
532
621
 
533
622
  extend self
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: failbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.5
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@rtomayko"
8
8
  - "@atmos"
9
9
  - "@sr"
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-09-03 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
@@ -106,13 +120,14 @@ files:
106
120
  - lib/failbot/middleware.rb
107
121
  - lib/failbot/resque_failure_backend.rb
108
122
  - lib/failbot/sensitive_data_scrubber.rb
123
+ - lib/failbot/thread_local_variable.rb
109
124
  - lib/failbot/version.rb
110
125
  - lib/failbot/waiter_backend.rb
111
126
  homepage: http://github.com/github/failbot#readme
112
127
  licenses:
113
128
  - MIT
114
129
  metadata: {}
115
- post_install_message:
130
+ post_install_message:
116
131
  rdoc_options: []
117
132
  require_paths:
118
133
  - lib
@@ -127,8 +142,8 @@ 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
131
- signing_key:
145
+ rubygems_version: 3.1.6
146
+ signing_key:
132
147
  specification_version: 4
133
148
  summary: Deliver exceptions to Haystack
134
149
  test_files: []