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 +4 -4
- data/lib/failbot.rb +67 -8
- data/lib/failbot/default_backtrace_parser.rb +23 -0
- data/lib/failbot/exception_format/haystack.rb +1 -7
- data/lib/failbot/exception_format/structured.rb +41 -23
- data/lib/failbot/haystack.rb +4 -0
- data/lib/failbot/memory_backend.rb +1 -4
- data/lib/failbot/version.rb +1 -1
- data/lib/failbot/waiter_backend.rb +21 -0
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bae1044f6d88c6617750ffe63b606591880ebe5132d9714d2e4c9534e309962
|
4
|
+
data.tar.gz: b1d80185dae5e664c049ed38583d25086d3659f517826c38912974d93fed32a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8114032ca98920f4b902447d1b3c69b28ea6bfe14b52d8b1f431d0537295d5c565d9d8a17540afa72f00f439023bc711291542baa2044ada4cd6f547e9506c35
|
7
|
+
data.tar.gz: f0040811958da514da048dd38af5de2cfd3978d6f036dc78c19b6080991b02bf69cca5b79f684ecc3a8c5b3885b577ecf77116fca445439f7ecfa06fbe5a3452
|
data/lib/failbot.rb
CHANGED
@@ -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
|
-
|
312
|
-
|
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
|
-
|
315
|
-
|
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.
|
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
|
-
|
18
|
-
rescue
|
19
|
-
message += "\nUnable to parse 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" =>
|
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
|
data/lib/failbot/haystack.rb
CHANGED
data/lib/failbot/version.rb
CHANGED
@@ -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
|
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-
|
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: '
|
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:
|
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: []
|