failbot 0.9.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.
- data/bin/failbot +5 -0
- data/lib/failbot.rb +237 -0
- data/lib/failbot/bert_backend.rb +17 -0
- data/lib/failbot/compat.rb +157 -0
- data/lib/failbot/exit_hook.rb +16 -0
- data/lib/failbot/failbot.yml +82 -0
- data/lib/failbot/file_backend.rb +27 -0
- data/lib/failbot/handler.rb +28 -0
- data/lib/failbot/haystack.rb +42 -0
- data/lib/failbot/heroku_backend.rb +15 -0
- data/lib/failbot/http_backend.rb +19 -0
- data/lib/failbot/memory_backend.rb +22 -0
- data/lib/failbot/middleware.rb +34 -0
- data/lib/failbot/resque_failure_backend.rb +28 -0
- data/lib/failbot/server.rb +53 -0
- data/lib/failbot/version.rb +3 -0
- metadata +112 -0
data/bin/failbot
ADDED
data/lib/failbot.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'logger'
|
4
|
+
require 'socket'
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
require 'failbot/version'
|
8
|
+
require "failbot/compat"
|
9
|
+
|
10
|
+
# Failbot asynchronously takes exceptions and reports them to the
|
11
|
+
# exception logger du jour. Keeps the main app from failing or lagging if
|
12
|
+
# the exception logger service is down or slow.
|
13
|
+
module Failbot
|
14
|
+
# The failbot relay server.
|
15
|
+
autoload :Server, 'failbot/server'
|
16
|
+
|
17
|
+
# Interface for posting exception data to haystack.
|
18
|
+
autoload :Haystack, 'failbot/haystack'
|
19
|
+
|
20
|
+
autoload :BERTBackend, 'failbot/bert_backend'
|
21
|
+
autoload :FileBackend, 'failbot/file_backend'
|
22
|
+
autoload :HerokuBackend, 'failbot/heroku_backend'
|
23
|
+
autoload :HTTPBackend, 'failbot/http_backend'
|
24
|
+
autoload :MemoryBackend, 'failbot/memory_backend'
|
25
|
+
|
26
|
+
# Public: Setup the backend for reporting exceptions.
|
27
|
+
def setup(settings={}, default_context={})
|
28
|
+
deprecated_settings = %w[
|
29
|
+
backend host port haystack workers service_logs
|
30
|
+
raise_errors
|
31
|
+
]
|
32
|
+
|
33
|
+
if settings.empty? ||
|
34
|
+
settings.keys.any? { |key| deprecated_settings.include?(key) }
|
35
|
+
warn "%s Deprecated Failbot.setup usage. See %s for details." % [
|
36
|
+
caller[0], "https://github.com/github/failbot"
|
37
|
+
]
|
38
|
+
return setup_deprecated(settings)
|
39
|
+
end
|
40
|
+
|
41
|
+
if default_context.respond_to?(:to_hash) && !default_context.to_hash.empty?
|
42
|
+
context[0] = default_context.to_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
self.backend =
|
46
|
+
case (name = settings["FAILBOT_BACKEND"])
|
47
|
+
when "bert"
|
48
|
+
Failbot::BERTBackend.new(URI(settings["FAILBOT_BACKEND_BERT_URL"]))
|
49
|
+
when "memory"
|
50
|
+
Failbot::MemoryBackend.new
|
51
|
+
when "file"
|
52
|
+
Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
|
53
|
+
when "http"
|
54
|
+
Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]))
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Unknown backend: #{name.inspect}"
|
57
|
+
end
|
58
|
+
|
59
|
+
@raise_errors = !settings["FAILBOT_RAISE"].to_s.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Bring in deprecated methods
|
63
|
+
extend Failbot::Compat
|
64
|
+
|
65
|
+
# Stack of context information to include in the next failbot report. These
|
66
|
+
# hashes are condensed down into one and included in the next report. Don't
|
67
|
+
# mess with this structure directly - use the #push and #pop methods.
|
68
|
+
def context
|
69
|
+
@context ||= [{'server' => hostname}]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add info to be sent in the next failbot report, should one occur.
|
73
|
+
#
|
74
|
+
# info - Hash of name => value pairs to include in the exception report.
|
75
|
+
# block - When given, the info is removed from the current context after the
|
76
|
+
# block is executed.
|
77
|
+
#
|
78
|
+
# Returns the value returned by the block when given; otherwise, returns nil.
|
79
|
+
def push(info={})
|
80
|
+
context.push(info)
|
81
|
+
yield if block_given?
|
82
|
+
ensure
|
83
|
+
pop if block_given?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Remove the last info hash from the context stack.
|
87
|
+
def pop
|
88
|
+
context.pop if context.size > 1
|
89
|
+
end
|
90
|
+
|
91
|
+
# Reset the context stack to a pristine state.
|
92
|
+
def reset!
|
93
|
+
@context = [context[0]]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Sends an exception to the exception tracking service along
|
97
|
+
# with a hash of custom attributes to be included with the report. When the
|
98
|
+
# raise_errors option is set, this method raises the exception instead of
|
99
|
+
# reporting to the exception tracking service.
|
100
|
+
#
|
101
|
+
# e - The Exception object. Must respond to #message and #backtrace.
|
102
|
+
# other - Hash of additional attributes to include with the report.
|
103
|
+
#
|
104
|
+
# Examples
|
105
|
+
#
|
106
|
+
# begin
|
107
|
+
# my_code
|
108
|
+
# rescue => e
|
109
|
+
# Failbot.report(e, :user => current_user)
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# Returns nothing.
|
113
|
+
def report(e, other = {})
|
114
|
+
if @raise_errors
|
115
|
+
squash_context(exception_info(e), other) # surface problems squashing
|
116
|
+
raise e
|
117
|
+
else
|
118
|
+
report!(e, other)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def report!(e, other = {})
|
123
|
+
data = squash_context(exception_info(e), other)
|
124
|
+
backend.report(data)
|
125
|
+
rescue Object => i
|
126
|
+
# don't fail for any reason
|
127
|
+
logger.debug "FAILBOT: #{data.inspect}" rescue nil
|
128
|
+
logger.debug e.message rescue nil
|
129
|
+
logger.debug e.backtrace.join("\n") rescue nil
|
130
|
+
logger.debug i.message rescue nil
|
131
|
+
logger.debug i.backtrace.join("\n") rescue nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Public: exceptions that were reported. Only available when using the
|
135
|
+
# memory and file backends.
|
136
|
+
#
|
137
|
+
# Returns an Array of exceptions data Hash.
|
138
|
+
def reports
|
139
|
+
backend.reports
|
140
|
+
end
|
141
|
+
|
142
|
+
# Combines all context hashes into a single hash converting non-standard
|
143
|
+
# data types in values to strings, then combines the result with a custom
|
144
|
+
# info hash provided in the other argument.
|
145
|
+
#
|
146
|
+
# other - Optional array of hashes to also squash in on top of the context
|
147
|
+
# stack hashes.
|
148
|
+
#
|
149
|
+
# Returns a Hash with all keys and values.
|
150
|
+
def squash_context(*other)
|
151
|
+
merged = {}
|
152
|
+
(context + other).each do |hash|
|
153
|
+
hash.each do |key, value|
|
154
|
+
value = (value.call rescue nil) if value.kind_of?(Proc)
|
155
|
+
merged[key.to_s] =
|
156
|
+
case value
|
157
|
+
when String, Numeric, true, false
|
158
|
+
value.to_s
|
159
|
+
else
|
160
|
+
value.inspect
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
merged
|
165
|
+
end
|
166
|
+
|
167
|
+
# Extract exception info into a simple Hash.
|
168
|
+
#
|
169
|
+
# e - The exception object to turn into a Hash.
|
170
|
+
#
|
171
|
+
# Returns a Hash including a 'class', 'message', 'backtrace', and 'rollup'
|
172
|
+
# keys. The rollup value is a MD5 hash of the exception class, file, and line
|
173
|
+
# number and is used to group exceptions.
|
174
|
+
def exception_info(e)
|
175
|
+
backtrace = Array(e.backtrace)[0, 500]
|
176
|
+
|
177
|
+
res = {
|
178
|
+
'class' => e.class.to_s,
|
179
|
+
'message' => e.message,
|
180
|
+
'backtrace' => backtrace.join("\n"),
|
181
|
+
'ruby' => RUBY_DESCRIPTION,
|
182
|
+
'rollup' => Digest::MD5.hexdigest("#{e.class}#{backtrace[0]}")
|
183
|
+
}
|
184
|
+
|
185
|
+
if original = (e.respond_to?(:original_exception) && e.original_exception)
|
186
|
+
remote_backtrace = []
|
187
|
+
remote_backtrace << original.message
|
188
|
+
if original.backtrace
|
189
|
+
remote_backtrace.concat(Array(original.backtrace)[0,500])
|
190
|
+
end
|
191
|
+
res['remote_backtrace'] = remote_backtrace.join("\n")
|
192
|
+
end
|
193
|
+
|
194
|
+
res
|
195
|
+
end
|
196
|
+
|
197
|
+
# Installs an at_exit hook to report exceptions that raise all the way out of
|
198
|
+
# the stack and halt the interpreter. This is useful for catching boot time
|
199
|
+
# errors as well and even signal kills.
|
200
|
+
#
|
201
|
+
# To use, call this method very early during the program's boot to cover as
|
202
|
+
# much code as possible:
|
203
|
+
#
|
204
|
+
# require 'failbot'
|
205
|
+
# Failbot.install_unhandled_exception_hook!
|
206
|
+
#
|
207
|
+
# Returns true when the hook was installed, nil when the hook had previously
|
208
|
+
# been installed by another component.
|
209
|
+
def install_unhandled_exception_hook!
|
210
|
+
# only install the hook once, even when called from multiple locations
|
211
|
+
return if @unhandled_exception_hook_installed
|
212
|
+
|
213
|
+
# the $! is set when the interpreter is exiting due to an exception
|
214
|
+
at_exit do
|
215
|
+
boom = $!
|
216
|
+
if boom && !@raise_errors && !boom.is_a?(SystemExit)
|
217
|
+
report(boom, 'argv' => ([$0]+ARGV).join(" "), 'halting' => true)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
@unhandled_exception_hook_installed = true
|
222
|
+
end
|
223
|
+
|
224
|
+
def logger
|
225
|
+
@logger ||= Logger.new($stderr)
|
226
|
+
end
|
227
|
+
|
228
|
+
def logger=(logger)
|
229
|
+
@logger = logger
|
230
|
+
end
|
231
|
+
|
232
|
+
def hostname
|
233
|
+
@hostname ||= Socket.gethostname
|
234
|
+
end
|
235
|
+
|
236
|
+
extend self
|
237
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bertrpc'
|
2
|
+
|
3
|
+
module Failbot
|
4
|
+
class BERTBackend
|
5
|
+
def initialize(url)
|
6
|
+
@service = BERTRPC::Service.new(url.host, url.port)
|
7
|
+
end
|
8
|
+
|
9
|
+
def report(data)
|
10
|
+
@service.cast.failbot.report(data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def reports
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Failbot
|
2
|
+
module Compat
|
3
|
+
# DEPRECATED Reset the backend and optionally override the environment
|
4
|
+
# configuration.
|
5
|
+
#
|
6
|
+
# config - The optional configuration Hash.
|
7
|
+
#
|
8
|
+
# Returns nothing.
|
9
|
+
def setup_deprecated(_config={})
|
10
|
+
config.merge!(_config)
|
11
|
+
@backend = nil
|
12
|
+
@raise_errors = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# The current "environment". This dictates which section will be read
|
16
|
+
# from the failbot.yml config file.
|
17
|
+
def environment
|
18
|
+
warn "#{caller[0]} Failbot.environment is deprecated and will be " \
|
19
|
+
"removed in subsequent releases."
|
20
|
+
@environment ||= ENV['FAILBOT_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Hash of configuration data from lib/failbot/failbot.yml.
|
24
|
+
def config
|
25
|
+
warn "#{caller[0]} Failbot.config is deprecated and will be " \
|
26
|
+
"removed in subsequent releases."
|
27
|
+
@config ||= YAML.load_file(config_file)[environment]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Location of failbot.yml config file.
|
31
|
+
def config_file
|
32
|
+
warn "#{caller[0]} Failbot.config_file is deprecated and will be " \
|
33
|
+
"removed in subsequent releases."
|
34
|
+
File.expand_path('../../failbot/failbot.yml', __FILE__)
|
35
|
+
end
|
36
|
+
|
37
|
+
# The name of the backend that should be used to post exceptions to the
|
38
|
+
# exceptions-collection service. The fellowing backends are available:
|
39
|
+
#
|
40
|
+
# memory - Dummy backend that simply save exceptions in memory. Typically
|
41
|
+
# used in testing environments.
|
42
|
+
#
|
43
|
+
# bert - The BERT-RPC backend relays exceptions using the bert library.
|
44
|
+
# See <https://github.com/mojombo/bert> for details.
|
45
|
+
#
|
46
|
+
# heroku - In-process posting for outside of vpn apps
|
47
|
+
#
|
48
|
+
# file - Append JSON-encoded exceptions to a file.
|
49
|
+
#
|
50
|
+
# Returns the String backend name. See also `Failbot.backend`.
|
51
|
+
def backend_name
|
52
|
+
warn "#{caller[0]} Failbot.backend_name is deprecated and will be " \
|
53
|
+
"removed in subsequent releases."
|
54
|
+
config['backend']
|
55
|
+
end
|
56
|
+
|
57
|
+
# Determines whether exceptions are raised instead of being reported to
|
58
|
+
# the exception tracking service. This is typically enabled in development
|
59
|
+
# and test environments. When set true, no exception information is reported
|
60
|
+
# and the exception is raised instead. When false (default in production
|
61
|
+
# environments), the exception is reported to the exception tracking service
|
62
|
+
# but not raised.
|
63
|
+
def raise_errors?
|
64
|
+
warn "#{caller[0]} Failbot.raise_errors? is deprecated and will be " \
|
65
|
+
"removed in subsequent releases."
|
66
|
+
|
67
|
+
if @raise_errors.nil?
|
68
|
+
config['raise_errors']
|
69
|
+
else
|
70
|
+
@raise_errors
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def raise_errors=(v)
|
75
|
+
warn "#{caller[0]} Failbot.raise_errors= is deprecated and will be " \
|
76
|
+
"removed in subsequent releases."
|
77
|
+
@raise_errors = v
|
78
|
+
end
|
79
|
+
|
80
|
+
# Load and initialize the exception reporting backend as specified by
|
81
|
+
# the 'backend' configuration option.
|
82
|
+
#
|
83
|
+
# Raises ArgumentError for invalid backends.
|
84
|
+
def backend
|
85
|
+
@backend ||= backend!
|
86
|
+
end
|
87
|
+
attr_writer :backend
|
88
|
+
|
89
|
+
def backend!
|
90
|
+
warn "#{caller[0]} Failbot.backend! is deprecated and will be " \
|
91
|
+
"removed in subsequent releases."
|
92
|
+
case backend_name
|
93
|
+
when 'bert'
|
94
|
+
url = URI("bertrpc://#{config["host"]}:#{config["port"]}")
|
95
|
+
Failbot::BERTBackend.new(url)
|
96
|
+
when 'memory'
|
97
|
+
Failbot::MemoryBackend.new
|
98
|
+
when 'file'
|
99
|
+
Failbot::FileBackend.new(config['file_path'])
|
100
|
+
when 'heroku', 'http'
|
101
|
+
if backend_name == "heroku"
|
102
|
+
warn "The Failbot \"heroku\" backend is deprecated. Use \"http\" " \
|
103
|
+
"instead. #{caller[1]}"
|
104
|
+
end
|
105
|
+
Failbot::HerokuBackend.new(config['haystack'])
|
106
|
+
else
|
107
|
+
raise ArgumentError, "Unknown backend: #{backend_name.inspect}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# The URL where exceptions should be posted. Each exception is converted into
|
112
|
+
# JSON and posted to this URL.
|
113
|
+
def haystack
|
114
|
+
warn "#{caller[0]} Failbot.haystack is deprecated and will be " \
|
115
|
+
"removed in subsequent releases."
|
116
|
+
config['haystack']
|
117
|
+
end
|
118
|
+
|
119
|
+
# Send the exception data to the relay service using
|
120
|
+
# a non-waiting cast call.
|
121
|
+
#
|
122
|
+
# data - Hash of string key => string value pairs.
|
123
|
+
#
|
124
|
+
# Returns nothing.
|
125
|
+
def cast(data)
|
126
|
+
warn "#{caller[0]} Failbot.cast is deprecated and will be " \
|
127
|
+
"removed in subsequent releases."
|
128
|
+
backend.report(data)
|
129
|
+
end
|
130
|
+
|
131
|
+
def service
|
132
|
+
warn "#{caller[0]} Failbot.service is deprecated and will be " \
|
133
|
+
"removed in subsequent releases."
|
134
|
+
@service ||= BERTRPC::Service.new(config['host'], config['port'])
|
135
|
+
end
|
136
|
+
|
137
|
+
alias svc service
|
138
|
+
|
139
|
+
def fail
|
140
|
+
warn "#{caller[0]} Failbot.fail is deprecated and will be " \
|
141
|
+
"removed in subsequent releases."
|
142
|
+
raise "failure failure!"
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_options
|
146
|
+
warn "#{caller[0]} Failbot.default_options is deprecated and will be " \
|
147
|
+
"removed in subsequent releases."
|
148
|
+
context[0]
|
149
|
+
end
|
150
|
+
|
151
|
+
def default_options=(hash)
|
152
|
+
warn "#{caller[0]} Failbot.default_options= is deprecated. Please use " \
|
153
|
+
"Failbot.setup(ENV, context={}) instead."
|
154
|
+
context[0] = hash
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file exists so that the unhandled exception hook may easily be injected
|
2
|
+
# into programs that don't register it themselves. To use, set RUBYOPT or pass
|
3
|
+
# an -r argument to ruby:
|
4
|
+
#
|
5
|
+
# With RUBYOPT:
|
6
|
+
#
|
7
|
+
# RUBYOPT=rfailbot/exit_hook some-program.rb
|
8
|
+
#
|
9
|
+
# With ruby -r:
|
10
|
+
#
|
11
|
+
# ruby -rfailbot/exit_hook some-program.rb
|
12
|
+
#
|
13
|
+
# Note: it may be necessary to also require rubygems w/ RUBYOPT or -r before
|
14
|
+
# requiring failbot/exit_hook.
|
15
|
+
require 'failbot'
|
16
|
+
Failbot.install_unhandled_exception_hook!
|
@@ -0,0 +1,82 @@
|
|
1
|
+
development:
|
2
|
+
backend: bert
|
3
|
+
host: localhost
|
4
|
+
port: 6666
|
5
|
+
haystack: http://localhost:9393/async
|
6
|
+
workers: 1
|
7
|
+
service_log: /tmp/failbot.log
|
8
|
+
raise_errors: true
|
9
|
+
|
10
|
+
test:
|
11
|
+
backend: memory
|
12
|
+
host: localhost
|
13
|
+
port: 6666
|
14
|
+
haystack: http://localhost:9393/async
|
15
|
+
workers: 0
|
16
|
+
raise_errors: true
|
17
|
+
|
18
|
+
production:
|
19
|
+
backend: bert
|
20
|
+
host: localhost
|
21
|
+
port: 6666
|
22
|
+
haystack: https://haystack.githubapp.com/_needles
|
23
|
+
workers: 2
|
24
|
+
service_log: /data/github/current/log/failbot.log
|
25
|
+
raise_errors: false
|
26
|
+
|
27
|
+
staging:
|
28
|
+
backend: bert
|
29
|
+
host: localhost
|
30
|
+
port: 6666
|
31
|
+
haystack: https://haystack.githubapp.com/_needles
|
32
|
+
workers: 1
|
33
|
+
service_log: /data/github/current/log/failbot.log
|
34
|
+
raise_errors: false
|
35
|
+
|
36
|
+
standalone:
|
37
|
+
backend: bert
|
38
|
+
host: localhost
|
39
|
+
port: 6666
|
40
|
+
haystack: https://haystack.githubapp.com/_needles
|
41
|
+
service_log: /tmp/failbot.log
|
42
|
+
workers: 1
|
43
|
+
raise_errors: false
|
44
|
+
|
45
|
+
standalone-staging:
|
46
|
+
backend: bert
|
47
|
+
host: localhost
|
48
|
+
port: 6666
|
49
|
+
haystack: https://haystack-staging.githubapp.com/async
|
50
|
+
service_log: /tmp/failbot.log
|
51
|
+
workers: 1
|
52
|
+
raise_errors: false
|
53
|
+
|
54
|
+
standalone-ng:
|
55
|
+
backend: bert
|
56
|
+
host: localhost
|
57
|
+
port: 6666
|
58
|
+
haystack: https://haystack-ng.githubapp.com/async
|
59
|
+
service_log: /tmp/failbot.log
|
60
|
+
workers: 1
|
61
|
+
raise_errors: false
|
62
|
+
|
63
|
+
haystack-ng:
|
64
|
+
backend: heroku
|
65
|
+
haystack: https://haystack-ng.githubapp.com/async
|
66
|
+
service_log: /tmp/failbot.log
|
67
|
+
workers: 1
|
68
|
+
raise_errors: false
|
69
|
+
|
70
|
+
heroku:
|
71
|
+
backend: heroku
|
72
|
+
haystack: https://hubble.githubapp.com/async
|
73
|
+
service_log: /tmp/failbot.log
|
74
|
+
workers: 1
|
75
|
+
raise_errors: false
|
76
|
+
|
77
|
+
heroku-staging:
|
78
|
+
backend: heroku
|
79
|
+
haystack: https://hubble-staging.githubapp.com/async
|
80
|
+
service_log: /tmp/failbot.log
|
81
|
+
workers: 1
|
82
|
+
raise_errors: false
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
|
3
|
+
module Failbot
|
4
|
+
class FileBackend
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
|
8
|
+
if path.to_s.empty?
|
9
|
+
raise ArgumentError, "FAILBOT_BACKEND_FILE_PATH setting required."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def report(data)
|
14
|
+
File.open(@path, 'a') do |file|
|
15
|
+
file.puts(Yajl.dump(data))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def reports
|
20
|
+
reports = []
|
21
|
+
File.foreach(@path) do |line|
|
22
|
+
reports << Yajl.load(line)
|
23
|
+
end
|
24
|
+
reports
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'time'
|
3
|
+
require 'failbot'
|
4
|
+
|
5
|
+
# Reopen stderr/stdout on the logfile if enabled.
|
6
|
+
if log_file = Failbot::config['service_log']
|
7
|
+
$stderr.reopen(log_file, 'ab')
|
8
|
+
$stdout.reopen($stderr)
|
9
|
+
[$stderr, $stdout].each { |fd| fd.sync = true }
|
10
|
+
end
|
11
|
+
warn "failbot handler #$$ starting in #{Failbot::environment} at #{Time.now.iso8601}"
|
12
|
+
|
13
|
+
require 'ernie'
|
14
|
+
require 'failbot/haystack'
|
15
|
+
|
16
|
+
# The Ernie handler implementation responsible for delivering exception reports
|
17
|
+
# to the exception backend. The report method is made available as a BERT-RPC
|
18
|
+
# service.
|
19
|
+
module Failbot::Handler
|
20
|
+
def report(data)
|
21
|
+
Failbot::Haystack.send_data(data)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Ernie.expose(:failbot, Failbot::Handler)
|
27
|
+
|
28
|
+
# vim:ft=ruby
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'yajl'
|
4
|
+
|
5
|
+
module Failbot
|
6
|
+
class Haystack
|
7
|
+
def initialize(url)
|
8
|
+
@url = url
|
9
|
+
end
|
10
|
+
|
11
|
+
def user
|
12
|
+
@url.user || "failbot"
|
13
|
+
end
|
14
|
+
|
15
|
+
def password
|
16
|
+
@url.password
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_data(data)
|
20
|
+
# make a post
|
21
|
+
post = Net::HTTP::Post.new(@url.path)
|
22
|
+
post.set_form_data('json' => Yajl.dump(data))
|
23
|
+
|
24
|
+
if user && password
|
25
|
+
post.basic_auth(user, password)
|
26
|
+
end
|
27
|
+
|
28
|
+
# make request
|
29
|
+
req = Net::HTTP.new(@url.host, @url.port)
|
30
|
+
|
31
|
+
# use SSL if applicable
|
32
|
+
req.use_ssl = true if @url.scheme == "https"
|
33
|
+
|
34
|
+
# push it through
|
35
|
+
req.start { |http| http.request(post) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.send_data(data)
|
39
|
+
new(Failbot.haystack).send_data(data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Failbot
|
2
|
+
class HTTPBackend
|
3
|
+
def initialize(url)
|
4
|
+
if url.to_s.empty?
|
5
|
+
raise ArgumentError, "FAILBOT_HAYSTACK_URL setting required."
|
6
|
+
end
|
7
|
+
|
8
|
+
@haystack = Failbot::Haystack.new(url)
|
9
|
+
end
|
10
|
+
|
11
|
+
def report(data)
|
12
|
+
@haystack.send_data(data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reports
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Failbot
|
2
|
+
class MemoryBackend
|
3
|
+
def initialize
|
4
|
+
@reports = []
|
5
|
+
@fail = false
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :reports
|
9
|
+
|
10
|
+
def fail!
|
11
|
+
@fail = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def report(data)
|
15
|
+
if @fail
|
16
|
+
fail
|
17
|
+
end
|
18
|
+
|
19
|
+
@reports << data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Failbot
|
2
|
+
# Rack middleware that rescues exceptions raised from the downstream app and
|
3
|
+
# reports to failbot. The exception is reraised after being sent to haystack
|
4
|
+
# so upstream middleware can still display an error page or whathaveyou.
|
5
|
+
class Rescuer
|
6
|
+
def initialize(app, other)
|
7
|
+
@app = app
|
8
|
+
@other = other
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
start = Time.now
|
13
|
+
@app.call(env)
|
14
|
+
rescue Object => boom
|
15
|
+
elapsed = Time.now - start
|
16
|
+
self.class.report(boom, env, @other.merge(:time => elapsed.to_s))
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.report(boom, env, other={})
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
Failbot.context.push(other.merge({
|
23
|
+
:method => request.request_method,
|
24
|
+
:user_agent => env['HTTP_USER_AGENT'],
|
25
|
+
:params => (request.params.inspect rescue nil),
|
26
|
+
:session => (request.session.inspect rescue nil),
|
27
|
+
:referrer => request.referrer,
|
28
|
+
:remote_ip => request.ip,
|
29
|
+
:url => request.url
|
30
|
+
}))
|
31
|
+
Failbot.report(boom)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'resque'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Failure
|
5
|
+
class Failbot < Base
|
6
|
+
def self.url
|
7
|
+
if ENV['RAILS_ENV'] == 'staging'
|
8
|
+
"http://haystack.stg.github.com/types/exception"
|
9
|
+
else
|
10
|
+
"http://haystack.rs.github.com/types/exception"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.count
|
15
|
+
# Fake it for now.
|
16
|
+
Stat[:failed]
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
::Failbot.report(exception,
|
21
|
+
:worker => worker.to_s,
|
22
|
+
:queue => queue.to_s,
|
23
|
+
:job => payload['class'].to_s,
|
24
|
+
:args => payload['args'].inspect)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'failbot'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module Failbot
|
5
|
+
# Interface for starting the exception relay server. This class constructs a
|
6
|
+
# temporary config file for ernie and starts the erlang server process.
|
7
|
+
class Server
|
8
|
+
def initialize(port=nil, tmpdir=nil)
|
9
|
+
@port = (port || 6666).to_i
|
10
|
+
@tmp = tmpdir || Dir.tmpdir
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :port
|
14
|
+
attr_reader :tmp
|
15
|
+
|
16
|
+
def workers
|
17
|
+
Failbot.config['workers'] || 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def handler_file
|
21
|
+
File.expand_path('../handler.rb', __FILE__)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pid_file
|
25
|
+
"#{@tmp}/failbot.#{port}.pid"
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure
|
29
|
+
write_config
|
30
|
+
config_file
|
31
|
+
end
|
32
|
+
|
33
|
+
def config_file
|
34
|
+
"#{@tmp}/ernie.failbot.#{Failbot.environment}.cfg"
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_config
|
38
|
+
File.open(config_file, 'wb') do |io|
|
39
|
+
io.write((<<-ERLANG).gsub(/^ {10}/, ''))
|
40
|
+
[{module, failbot},
|
41
|
+
{type, external},
|
42
|
+
{command, "ruby #{handler_file}"},
|
43
|
+
{count, #{workers}}].
|
44
|
+
ERLANG
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def exec!
|
49
|
+
configure
|
50
|
+
exec "ernie", "-p", port.to_s, "-P", pid_file, "-d", "-c", config_file
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: failbot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ! '@rtomayko'
|
9
|
+
- ! '@atmos'
|
10
|
+
- ! '@sr'
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-03-14 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: yajl-ruby
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '1.1'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '1.1'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: bertrpc
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.3.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.3.0
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.8.7
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.8.7
|
64
|
+
description: ! '...'
|
65
|
+
email:
|
66
|
+
- github+failbot@lists.github.com
|
67
|
+
executables:
|
68
|
+
- failbot
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- lib/failbot.rb
|
73
|
+
- lib/failbot/bert_backend.rb
|
74
|
+
- lib/failbot/compat.rb
|
75
|
+
- lib/failbot/exit_hook.rb
|
76
|
+
- lib/failbot/failbot.yml
|
77
|
+
- lib/failbot/file_backend.rb
|
78
|
+
- lib/failbot/handler.rb
|
79
|
+
- lib/failbot/haystack.rb
|
80
|
+
- lib/failbot/heroku_backend.rb
|
81
|
+
- lib/failbot/http_backend.rb
|
82
|
+
- lib/failbot/memory_backend.rb
|
83
|
+
- lib/failbot/middleware.rb
|
84
|
+
- lib/failbot/resque_failure_backend.rb
|
85
|
+
- lib/failbot/server.rb
|
86
|
+
- lib/failbot/version.rb
|
87
|
+
- bin/failbot
|
88
|
+
homepage: http://github.com/github/failbot#readme
|
89
|
+
licenses: []
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 1.3.6
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.23
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Deliver exceptions to Haystack
|
112
|
+
test_files: []
|