failbot 2.5.5 → 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: 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: []