failbot 2.4.2 → 2.5.4

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: 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: []