failbot 2.4.3 → 2.5.5

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: c5d1f28105f9f590c7c9160004bfa4a15101da5a1372ee0e40e882c493dad5fd
4
- data.tar.gz: 5b09392f7d2243343a7982c6ae031f80d8501eb946d687490be6ad88e7321b1f
3
+ metadata.gz: ef2b30021fd84983545c9fa374d030d9c1269d3ef51b778cf84d55fe96fde087
4
+ data.tar.gz: 998dde878c99c47177d03ff693625e05f1522009d84bf4e5f1a12a0fb398ad0e
5
5
  SHA512:
6
- metadata.gz: 011af8c261c0826685972133268b52b85dcd358c402ee662575ef853b36f7c8ea6ff527673f2ec1210316d9cefc053aa8a20b119bfd55c09cf9666cbd6a0eeb8
7
- data.tar.gz: 58bc429f5b2e27ef75f570eb2d194d2e956dc935bc5ec471042a0029ae8e14a929ee486abe25d2bd9e52ee9f052eccec559b18f31bf3ee292441e51820880bf1
6
+ metadata.gz: 774ccebb4f0b04886309e32840d73f17b0278388a73aa6a15541870c791a832f68c767b140ed1d62527feff87ae880aedebf60e942946c103116103c32bda9be
7
+ data.tar.gz: 778c39eea60f8acdd0b53246401351e1373bf9386259fde67adfb86c0956baa8780fdda6afcf01f942ee6327aa8672390ec57d699e93ade429c8123b6f5ef3a6
@@ -3,6 +3,7 @@ require 'digest/md5'
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
  # Public: Set an instrumenter to be called when exceptions are reported.
30
32
  #
@@ -67,6 +69,11 @@ module Failbot
67
69
 
68
70
  EXCEPTION_DETAIL = 'exception_detail'
69
71
 
72
+ # We'll include this many nested Exception#cause objects in the needle
73
+ # context. We limit the number of objects to prevent excessive recursion and
74
+ # large needle contexts.
75
+ MAXIMUM_CAUSE_DEPTH = 2
76
+
70
77
  # Enumerates the available exception formats this gem supports. The original
71
78
  # format and the default is :haystack and the newer format is :structured
72
79
  EXCEPTION_FORMATS = {
@@ -135,15 +142,29 @@ module Failbot
135
142
  end
136
143
 
137
144
  populate_context_from_settings(settings)
145
+ @enable_timeout = false
146
+ if settings.key?("FAILBOT_TIMEOUT_MS")
147
+ @timeout_seconds = settings["FAILBOT_TIMEOUT_MS"].to_f / 1000
148
+ @enable_timeout = (@timeout_seconds > 0.0)
149
+ end
150
+
151
+ @connect_timeout_seconds = nil
152
+ if settings.key?("FAILBOT_CONNECT_TIMEOUT_MS")
153
+ @connect_timeout_seconds = settings["FAILBOT_CONNECT_TIMEOUT_MS"].to_f / 1000
154
+ # unset the value if it's not parsing to something valid
155
+ @connect_timeout_seconds = nil unless @connect_timeout_seconds > 0
156
+ end
138
157
 
139
158
  self.backend =
140
159
  case (name = settings["FAILBOT_BACKEND"])
141
160
  when "memory"
142
161
  Failbot::MemoryBackend.new
162
+ when "waiter"
163
+ Failbot::WaiterBackend.new
143
164
  when "file"
144
165
  Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
145
166
  when "http"
146
- Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]))
167
+ Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]), @connect_timeout_seconds)
147
168
  when 'json'
148
169
  Failbot::JSONBackend.new(settings["FAILBOT_BACKEND_JSON_HOST"], settings["FAILBOT_BACKEND_JSON_PORT"])
149
170
  when 'console'
@@ -329,17 +350,31 @@ module Failbot
329
350
  end
330
351
 
331
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
332
358
 
333
- backend.report(data)
334
- instrument("report.failbot", data)
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"
335
372
  rescue Object => i
336
- # don't fail for any reason
337
- logger.debug "FAILBOT: #{data.inspect}" rescue nil
338
- logger.debug e.message rescue nil
339
- logger.debug e.backtrace.join("\n") rescue nil
340
- logger.debug i.message rescue nil
341
- logger.debug i.backtrace.join("\n") rescue nil
373
+ log_failure("reporting", data, e, i)
374
+ instrumentation_data["exception_type"] = i.class.name
342
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
343
378
  self.already_reporting = false
344
379
  end
345
380
  end
@@ -480,11 +515,21 @@ module Failbot
480
515
  def populate_context_from_settings(settings)
481
516
  settings.each do |key, value|
482
517
  if /\AFAILBOT_CONTEXT_(.+)\z/ =~ key
483
- context[0][$1.downcase] = value
518
+ key = $1.downcase
519
+ context[0][key] = value unless context[0][key]
484
520
  end
485
521
  end
486
522
  end
487
523
 
524
+ 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
531
+ end
532
+
488
533
  extend self
489
534
 
490
535
  # If the library was lazy loaded due to failbot/exit_hook.rb and a delayed
@@ -30,11 +30,6 @@ module Failbot
30
30
  hash["class"]
31
31
  end
32
32
 
33
- # We'll include this many nested Exception#cause objects in the needle
34
- # context. We limit the number of objects to prevent excessive recursion and
35
- # large needle contexts.
36
- MAXIMUM_CAUSE_DEPTH = 2
37
-
38
33
  # Pretty-print Exception#cause (and nested causes) for inclusion in needle
39
34
  # context
40
35
  #
@@ -59,7 +54,7 @@ module Failbot
59
54
 
60
55
  result = causes.join("\n\nCAUSED BY:\n\n")
61
56
 
62
- if current.respond_to?(:cause) && current.cause
57
+ if current.cause
63
58
  result << "\n\nFurther #cause backtraces were omitted\n"
64
59
  end
65
60
 
@@ -72,7 +67,6 @@ module Failbot
72
67
  #
73
68
  # Returns a String.
74
69
  def self.pretty_print_one_cause(e)
75
- return unless e.respond_to?(:cause)
76
70
  cause = e.cause
77
71
  return unless cause
78
72
 
@@ -22,18 +22,47 @@ module Failbot
22
22
  EMPTY_ARRAY
23
23
  end
24
24
  {
25
- "exception_detail" => [ # TODO Once supported in failbotg, this should be an array of
26
- { # hashes generated from subsequent calls to Exception#cause.
27
- "type" => class_name,
28
- "value" => message,
29
- "stacktrace" => stacktrace
30
- }
31
- ],
25
+ "exception_detail" => exception_details(e),
32
26
  "ruby" => RUBY_DESCRIPTION,
33
27
  "created_at" => Time.now.utc.iso8601(6)
34
28
  }
35
29
  end
36
30
 
31
+ FURTHER_CAUSES_WERE_OMITTED = {
32
+ "type" => "Notice",
33
+ "value" => "further Exception#cause values were omitted",
34
+ "stacktrace" => EMPTY_ARRAY
35
+ }.freeze
36
+
37
+ def self.exception_details(e)
38
+ result = []
39
+ depth = 0
40
+
41
+ loop do
42
+ message = e.message.to_s
43
+ class_name = e.class.to_s
44
+ stacktrace = begin
45
+ Failbot.backtrace_parser.call(e)
46
+ rescue => ex
47
+ 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")}"
48
+ class_name += " (backtrace failed to parse)"
49
+ EMPTY_ARRAY
50
+ end
51
+ result.unshift({
52
+ "type" => class_name,
53
+ "value" => message,
54
+ "stacktrace" => stacktrace
55
+ })
56
+ depth += 1
57
+ break unless (e=e.cause)
58
+ if depth > MAXIMUM_CAUSE_DEPTH
59
+ result.unshift(FURTHER_CAUSES_WERE_OMITTED)
60
+ break
61
+ end
62
+ end
63
+ result
64
+ end
65
+
37
66
  # given a hash generated by this class, return the exception message.
38
67
  def self.exception_message_from_hash(hash)
39
68
  hash.dig("exception_detail", 0, "value")
@@ -4,8 +4,10 @@ require 'json'
4
4
 
5
5
  module Failbot
6
6
  class Haystack
7
- def initialize(url)
7
+ attr_accessor :connect_timeout
8
+ def initialize(url, connect_timeout=nil)
8
9
  @url = url
10
+ @connect_timeout = connect_timeout
9
11
  end
10
12
 
11
13
  def user
@@ -50,8 +52,15 @@ module Failbot
50
52
  # use SSL if applicable
51
53
  http.use_ssl = true if @url.scheme == "https"
52
54
 
55
+ # Set the connect timeout if it was provided
56
+ http.open_timeout = @connect_timeout if @connect_timeout
57
+
53
58
  # push it through
54
59
  http.request(request)
60
+ ensure
61
+ if defined?(http) && http.started?
62
+ http.finish
63
+ end
55
64
  end
56
65
  end
57
66
  end
@@ -1,11 +1,12 @@
1
1
  module Failbot
2
2
  class HTTPBackend
3
- def initialize(url)
3
+ def initialize(url, connect_timeout = 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
+ @connect_timeout = connect_timeout
9
+ @haystack = Failbot::Haystack.new(url, connect_timeout)
9
10
  end
10
11
 
11
12
  def report(data)
@@ -19,5 +20,13 @@ module Failbot
19
20
  def ping
20
21
  @haystack.ping
21
22
  end
23
+
24
+ def connect_timeout
25
+ @haystack.connect_timeout
26
+ end
27
+
28
+ def connect_timeout=(timeout)
29
+ @haystack.connect_timeout = timeout
30
+ end
22
31
  end
23
32
  end
@@ -12,11 +12,8 @@ module Failbot
12
12
  end
13
13
 
14
14
  def report(data)
15
- if @fail
16
- fail
17
- end
18
-
19
15
  @reports << data
16
+ fail if @fail
20
17
  end
21
18
 
22
19
  def ping
@@ -1,3 +1,3 @@
1
1
  module Failbot
2
- VERSION = "2.4.3"
2
+ VERSION = "2.5.5"
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
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: failbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.3
4
+ version: 2.5.5
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-03-24 00:00:00.000000000 Z
13
+ date: 2020-09-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
@@ -107,11 +107,12 @@ files:
107
107
  - lib/failbot/resque_failure_backend.rb
108
108
  - lib/failbot/sensitive_data_scrubber.rb
109
109
  - lib/failbot/version.rb
110
+ - lib/failbot/waiter_backend.rb
110
111
  homepage: http://github.com/github/failbot#readme
111
112
  licenses:
112
113
  - MIT
113
114
  metadata: {}
114
- post_install_message:
115
+ post_install_message:
115
116
  rdoc_options: []
116
117
  require_paths:
117
118
  - lib
@@ -119,15 +120,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
120
  requirements:
120
121
  - - ">="
121
122
  - !ruby/object:Gem::Version
122
- version: '0'
123
+ version: '2.4'
123
124
  required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  requirements:
125
126
  - - ">="
126
127
  - !ruby/object:Gem::Version
127
- version: 1.3.6
128
+ version: '0'
128
129
  requirements: []
129
130
  rubygems_version: 3.0.3
130
- signing_key:
131
+ signing_key:
131
132
  specification_version: 4
132
133
  summary: Deliver exceptions to Haystack
133
134
  test_files: []