failbot 2.4.2 → 2.5.4

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: 53e182bf0325cad972095bfc874140de2c28b0b771070719e7aff54fb02d8fae
4
- data.tar.gz: 8da6d5c26fcfe301e0df2edf096e2afa0e19f40e5941ae5b03e084d8435cfab0
3
+ metadata.gz: 1bae1044f6d88c6617750ffe63b606591880ebe5132d9714d2e4c9534e309962
4
+ data.tar.gz: b1d80185dae5e664c049ed38583d25086d3659f517826c38912974d93fed32a6
5
5
  SHA512:
6
- metadata.gz: 04c21e6c698b040686e522ebe442c04b03a31c4875e904395f4e312e07cc1741e31b59f7eca22dd5475fd32a8a867c0537306f5fbf36ef44870b5337f62ef31e
7
- data.tar.gz: e13a7e8a016abddc60e5f071cc4b46d73f3f4e42e575bd123999d68dafdfcd7b42ec1ce2e52dfa0c4a8032b5acfbfaaaf93d6dd43efaf201ab1ad30ad085bd78
6
+ metadata.gz: 8114032ca98920f4b902447d1b3c69b28ea6bfe14b52d8b1f431d0537295d5c565d9d8a17540afa72f00f439023bc711291542baa2044ada4cd6f547e9506c35
7
+ data.tar.gz: f0040811958da514da048dd38af5de2cfd3978d6f036dc78c19b6080991b02bf69cca5b79f684ecc3a8c5b3885b577ecf77116fca445439f7ecfa06fbe5a3452
@@ -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
 
@@ -11,6 +12,7 @@ require "failbot/compat"
11
12
  require "failbot/sensitive_data_scrubber"
12
13
  require "failbot/exception_format/haystack"
13
14
  require "failbot/exception_format/structured"
15
+ require "failbot/default_backtrace_parser"
14
16
 
15
17
  # Failbot asynchronously takes exceptions and reports them to the
16
18
  # exception logger du jour. Keeps the main app from failing or lagging if
@@ -24,6 +26,7 @@ module Failbot
24
26
  autoload :HTTPBackend, 'failbot/http_backend'
25
27
  autoload :MemoryBackend, 'failbot/memory_backend'
26
28
  autoload :JSONBackend, 'failbot/json_backend'
29
+ autoload :WaiterBackend, 'failbot/waiter_backend'
27
30
 
28
31
  # Public: Set an instrumenter to be called when exceptions are reported.
29
32
  #
@@ -66,6 +69,11 @@ module Failbot
66
69
 
67
70
  EXCEPTION_DETAIL = 'exception_detail'
68
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
+
69
77
  # Enumerates the available exception formats this gem supports. The original
70
78
  # format and the default is :haystack and the newer format is :structured
71
79
  EXCEPTION_FORMATS = {
@@ -84,6 +92,27 @@ module Failbot
84
92
  # apps opt in to the newer version.
85
93
  self.exception_format = :haystack
86
94
 
95
+ # Set a callable that is responsible for parsing and formatting ruby
96
+ # backtraces. This is only necessary to set if your app deals with
97
+ # exceptions that are manipulated to contain something other than actual
98
+ # stackframe strings in the format produced by `caller`. The argument passed
99
+ # must respond to `call` with an arity of 1. The callable expects to be
100
+ # passed Exception instances as its argument.
101
+ def self.backtrace_parser=(callable)
102
+ unless callable.respond_to?(:call)
103
+ raise ArgumentError, "backtrace_parser= passed #{callable.inspect}, which is not callable"
104
+ end
105
+ if callable.method(:call).arity != 1
106
+ raise ArgumentError, "backtrace_parser= passed #{callable.inspect}, whose `#call` has arity =! 1"
107
+ end
108
+ @backtrace_parser = callable
109
+ end
110
+
111
+ attr_reader :backtrace_parser
112
+
113
+ # Default backtrace parser is provided:
114
+ self.backtrace_parser = ::Failbot::DefaultBacktraceParser
115
+
87
116
  # Helpers needed to parse hashes included in e.g. Failbot.reports.
88
117
  def self.exception_message_from_hash(hash)
89
118
  @exception_formatter.exception_message_from_hash(hash)
@@ -118,6 +147,8 @@ module Failbot
118
147
  case (name = settings["FAILBOT_BACKEND"])
119
148
  when "memory"
120
149
  Failbot::MemoryBackend.new
150
+ when "waiter"
151
+ Failbot::WaiterBackend.new
121
152
  when "file"
122
153
  Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
123
154
  when "http"
@@ -132,6 +163,11 @@ module Failbot
132
163
 
133
164
  @raise_errors = !settings["FAILBOT_RAISE"].to_s.empty?
134
165
  @report_errors = settings["FAILBOT_REPORT"] != "0"
166
+ @enable_timeout = false
167
+ if settings.key?("FAILBOT_TIMEOUT_MS")
168
+ @timeout_seconds = settings["FAILBOT_TIMEOUT_MS"].to_f / 1000
169
+ @enable_timeout = (@timeout_seconds != 0.0)
170
+ end
135
171
 
136
172
  # allows overriding the 'app' value to send to single haystack bucket.
137
173
  # used primarily on ghe.io.
@@ -307,17 +343,31 @@ module Failbot
307
343
  end
308
344
 
309
345
  data = scrub(sanitize(data))
346
+ rescue Object => i
347
+ log_failure("processing", data, e, i)
348
+ self.already_reporting = false
349
+ return
350
+ end
310
351
 
311
- backend.report(data)
312
- instrument("report.failbot", data)
352
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
353
+ instrumentation_data = {
354
+ "report_status" => "error",
355
+ }
356
+ begin
357
+ if @enable_timeout
358
+ Timeout.timeout(@timeout_seconds) do
359
+ backend.report(data)
360
+ end
361
+ else
362
+ backend.report(data)
363
+ end
364
+ instrumentation_data["report_status"] = "success"
313
365
  rescue Object => i
314
- # don't fail for any reason
315
- logger.debug "FAILBOT: #{data.inspect}" rescue nil
316
- logger.debug e.message rescue nil
317
- logger.debug e.backtrace.join("\n") rescue nil
318
- logger.debug i.message rescue nil
319
- logger.debug i.backtrace.join("\n") rescue nil
366
+ log_failure("reporting", data, e, i)
367
+ instrumentation_data["exception_type"] = i.class.name
320
368
  ensure
369
+ instrumentation_data["elapsed_ms"] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).to_i
370
+ instrument("report.failbot", data.merge(instrumentation_data)) rescue nil
321
371
  self.already_reporting = false
322
372
  end
323
373
  end
@@ -463,6 +513,15 @@ module Failbot
463
513
  end
464
514
  end
465
515
 
516
+ def log_failure(action, data, original_exception, exception)
517
+ # don't fail for any reason
518
+ logger.debug "FAILBOT EXCEPTION: action=#{action} exception=#{exception.class.name} original_type: #{original_exception.class.name} data=#{data.inspect}" rescue nil
519
+ logger.debug original_exception.message rescue nil
520
+ logger.debug original_exception.backtrace.join("\n") rescue nil
521
+ logger.debug exception.message rescue nil
522
+ logger.debug exception.backtrace.join("\n") rescue nil
523
+ end
524
+
466
525
  extend self
467
526
 
468
527
  # If the library was lazy loaded due to failbot/exit_hook.rb and a delayed
@@ -0,0 +1,23 @@
1
+ module Failbot
2
+ # This is the default backtrace parser for the Structured formatter.
3
+ module DefaultBacktraceParser
4
+ EMPTY_ARRAY = [].freeze
5
+
6
+ # Takes an Exception instance, returns an array of hashes with the keys
7
+ # that the Structured formatter expects.
8
+ def self.call(exception)
9
+ if exception.backtrace
10
+ Backtrace.parse(exception.backtrace).frames.reverse.map do |line|
11
+ {
12
+ "filename" => line.file_name,
13
+ "abs_path" => line.abs_path,
14
+ "lineno" => line.line_number,
15
+ "function" => line.method
16
+ }
17
+ end
18
+ else
19
+ EMPTY_ARRAY
20
+ end
21
+ end
22
+ end
23
+ end
@@ -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
 
@@ -13,25 +13,56 @@ module Failbot
13
13
  # Format an exception.
14
14
  def self.call(e)
15
15
  message = e.message.to_s
16
+ class_name = e.class.to_s
16
17
  stacktrace = begin
17
- formatted_backtrace(e) || EMPTY_ARRAY
18
- rescue Backtrace::ParseError
19
- message += "\nUnable to parse backtrace! Don't put non-backtrace text in Exception#backtrace please!\n#{e.backtrace}"
18
+ Failbot.backtrace_parser.call(e)
19
+ rescue => ex
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")}"
21
+ class_name += " (backtrace failed to parse)"
20
22
  EMPTY_ARRAY
21
23
  end
22
24
  {
23
- "exception_detail" => [ # TODO Once supported in failbotg, this should be an array of
24
- { # hashes generated from subsequent calls to Exception#cause.
25
- "type" => e.class.to_s,
26
- "value" => message,
27
- "stacktrace" => stacktrace
28
- }
29
- ],
25
+ "exception_detail" => exception_details(e),
30
26
  "ruby" => RUBY_DESCRIPTION,
31
27
  "created_at" => Time.now.utc.iso8601(6)
32
28
  }
33
29
  end
34
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
+
35
66
  # given a hash generated by this class, return the exception message.
36
67
  def self.exception_message_from_hash(hash)
37
68
  hash.dig("exception_detail", 0, "value")
@@ -41,19 +72,6 @@ module Failbot
41
72
  def self.exception_classname_from_hash(hash)
42
73
  hash.dig("exception_detail", 0, "type")
43
74
  end
44
-
45
- def self.formatted_backtrace(e)
46
- if e.backtrace
47
- Backtrace.parse(e.backtrace).frames.reverse.map do |line|
48
- {
49
- "filename" => line.file_name,
50
- "abs_path" => line.abs_path,
51
- "lineno" => line.line_number,
52
- "function" => line.method
53
- }
54
- end
55
- end
56
- end
57
75
  end
58
76
  end
59
77
  end
@@ -52,6 +52,10 @@ module Failbot
52
52
 
53
53
  # push it through
54
54
  http.request(request)
55
+ ensure
56
+ if defined?(http) && http.started?
57
+ http.finish
58
+ end
55
59
  end
56
60
  end
57
61
  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.2"
2
+ VERSION = "2.5.4"
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.2
4
+ version: 2.5.4
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-18 00:00:00.000000000 Z
13
+ date: 2020-08-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
@@ -93,6 +93,7 @@ files:
93
93
  - lib/failbot/backtrace.rb
94
94
  - lib/failbot/compat.rb
95
95
  - lib/failbot/console_backend.rb
96
+ - lib/failbot/default_backtrace_parser.rb
96
97
  - lib/failbot/exception_format/haystack.rb
97
98
  - lib/failbot/exception_format/structured.rb
98
99
  - lib/failbot/exit_hook.rb
@@ -106,11 +107,12 @@ files:
106
107
  - lib/failbot/resque_failure_backend.rb
107
108
  - lib/failbot/sensitive_data_scrubber.rb
108
109
  - lib/failbot/version.rb
110
+ - lib/failbot/waiter_backend.rb
109
111
  homepage: http://github.com/github/failbot#readme
110
112
  licenses:
111
113
  - MIT
112
114
  metadata: {}
113
- post_install_message:
115
+ post_install_message:
114
116
  rdoc_options: []
115
117
  require_paths:
116
118
  - lib
@@ -118,15 +120,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
120
  requirements:
119
121
  - - ">="
120
122
  - !ruby/object:Gem::Version
121
- version: '0'
123
+ version: '2.4'
122
124
  required_rubygems_version: !ruby/object:Gem::Requirement
123
125
  requirements:
124
126
  - - ">="
125
127
  - !ruby/object:Gem::Version
126
- version: 1.3.6
128
+ version: '0'
127
129
  requirements: []
128
130
  rubygems_version: 3.0.3
129
- signing_key:
131
+ signing_key:
130
132
  specification_version: 4
131
133
  summary: Deliver exceptions to Haystack
132
134
  test_files: []