honeybadger 2.7.2 → 3.0.0.beta1
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 +4 -4
- data/CHANGELOG.md +58 -3
- data/README.md +8 -15
- data/lib/honeybadger.rb +9 -232
- data/lib/honeybadger/agent.rb +292 -134
- data/lib/honeybadger/backend.rb +6 -6
- data/lib/honeybadger/backend/base.rb +11 -0
- data/lib/honeybadger/backend/server.rb +2 -14
- data/lib/honeybadger/cli.rb +0 -2
- data/lib/honeybadger/cli/deploy.rb +42 -0
- data/lib/honeybadger/cli/exec.rb +138 -0
- data/lib/honeybadger/cli/heroku.rb +1 -22
- data/lib/honeybadger/cli/install.rb +74 -0
- data/lib/honeybadger/cli/main.rb +138 -153
- data/lib/honeybadger/cli/notify.rb +66 -0
- data/lib/honeybadger/cli/test.rb +266 -0
- data/lib/honeybadger/config.rb +178 -162
- data/lib/honeybadger/config/defaults.rb +5 -5
- data/lib/honeybadger/config/env.rb +8 -6
- data/lib/honeybadger/config/ruby.rb +100 -0
- data/lib/honeybadger/config/yaml.rb +18 -19
- data/lib/honeybadger/const.rb +3 -16
- data/lib/honeybadger/context_manager.rb +50 -0
- data/lib/honeybadger/init/rails.rb +9 -21
- data/lib/honeybadger/init/rake.rb +2 -0
- data/lib/honeybadger/init/ruby.rb +9 -0
- data/lib/honeybadger/init/sinatra.rb +13 -6
- data/lib/honeybadger/notice.rb +29 -14
- data/lib/honeybadger/plugins/delayed_job/plugin.rb +4 -5
- data/lib/honeybadger/plugins/passenger.rb +1 -2
- data/lib/honeybadger/plugins/rails.rb +0 -28
- data/lib/honeybadger/plugins/resque.rb +2 -5
- data/lib/honeybadger/plugins/shoryuken.rb +2 -2
- data/lib/honeybadger/plugins/sidekiq.rb +2 -2
- data/lib/honeybadger/plugins/sucker_punch.rb +1 -0
- data/lib/honeybadger/plugins/thor.rb +2 -2
- data/lib/honeybadger/plugins/warden.rb +1 -0
- data/lib/honeybadger/rack/error_notifier.rb +11 -9
- data/lib/honeybadger/rack/user_feedback.rb +6 -4
- data/lib/honeybadger/rack/user_informer.rb +6 -4
- data/lib/honeybadger/ruby.rb +2 -0
- data/lib/honeybadger/singleton.rb +26 -0
- data/lib/honeybadger/util/http.rb +12 -0
- data/lib/honeybadger/util/request_hash.rb +71 -0
- data/lib/honeybadger/util/sanitizer.rb +101 -64
- data/lib/honeybadger/version.rb +1 -1
- data/lib/honeybadger/worker.rb +246 -0
- metadata +17 -13
- data/lib/honeybadger/agent/batch.rb +0 -50
- data/lib/honeybadger/agent/null_worker.rb +0 -26
- data/lib/honeybadger/agent/worker.rb +0 -243
- data/lib/honeybadger/cli/helpers.rb +0 -160
- data/lib/honeybadger/config/callbacks.rb +0 -70
- data/lib/honeybadger/plugins/unicorn.rb +0 -27
- data/lib/honeybadger/rack/metrics_reporter.rb +0 -16
- data/lib/honeybadger/rack/request_hash.rb +0 -55
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'honeybadger/cli/main'
|
4
|
+
require 'honeybadger/util/http'
|
5
|
+
require 'honeybadger/util/stats'
|
6
|
+
|
7
|
+
module Honeybadger
|
8
|
+
module CLI
|
9
|
+
class Notify
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize(options, args, config)
|
13
|
+
@options = options
|
14
|
+
@args = args
|
15
|
+
@config = config
|
16
|
+
@shell = ::Thor::Base.shell.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
payload = {
|
21
|
+
api_key: config.get(:api_key),
|
22
|
+
notifier: NOTIFIER,
|
23
|
+
error: {
|
24
|
+
class: options['class'],
|
25
|
+
message: options['message']
|
26
|
+
},
|
27
|
+
request: {},
|
28
|
+
server: {
|
29
|
+
project_root: Dir.pwd,
|
30
|
+
environment_name: config.get(:env),
|
31
|
+
time: Time.now,
|
32
|
+
stats: Util::Stats.all
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
payload[:error][:fingerprint] = Digest::SHA1.hexdigest(options['fingerprint']) if option?('fingerprint')
|
37
|
+
payload[:error][:tags] = options['tags'].to_s.strip.split(',').map(&:strip) if option?('tags')
|
38
|
+
|
39
|
+
payload[:request][:component] = options['component'] if option?('component')
|
40
|
+
payload[:request][:action] = options['action'] if option?('action')
|
41
|
+
payload[:request][:url] = options['url'] if option?('url')
|
42
|
+
|
43
|
+
payload.delete(:request) if payload[:request].empty?
|
44
|
+
|
45
|
+
http = Util::HTTP.new(config)
|
46
|
+
result = http.post('/v1/notices', payload)
|
47
|
+
if result.code == '201'
|
48
|
+
say("Error notification complete.", :green)
|
49
|
+
else
|
50
|
+
say("Invalid response from server: #{result.code}", :red)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :options, :args, :config
|
58
|
+
|
59
|
+
def_delegator :@shell, :say
|
60
|
+
|
61
|
+
def option?(key)
|
62
|
+
options.has_key?(key) && options[key] != key
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'honeybadger/cli/main'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Honeybadger
|
7
|
+
module CLI
|
8
|
+
class Test
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
TEST_EXCEPTION = begin
|
12
|
+
exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
|
13
|
+
Object.const_get(exception_name)
|
14
|
+
rescue
|
15
|
+
Object.const_set(exception_name, Class.new(Exception))
|
16
|
+
end.new('Testing honeybadger via "honeybadger test". If you can see this, it works.')
|
17
|
+
|
18
|
+
class TestBackend
|
19
|
+
def initialize(backend)
|
20
|
+
@backend = backend
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.callings
|
24
|
+
@callings ||= Hash.new {|h,k| h[k] = [] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def notify(feature, payload)
|
28
|
+
response = @backend.notify(feature, payload)
|
29
|
+
self.class.callings[feature] << [payload, response]
|
30
|
+
response
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(options)
|
35
|
+
@options = options
|
36
|
+
@shell = ::Thor::Base.shell.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
require 'honeybadger/ruby'
|
41
|
+
|
42
|
+
begin
|
43
|
+
require File.join(Dir.pwd, 'config', 'application.rb')
|
44
|
+
raise LoadError unless defined?(::Rails)
|
45
|
+
require 'honeybadger/init/rails'
|
46
|
+
Rails.application.initialize!
|
47
|
+
say("Detected Rails #{Rails::VERSION::STRING}")
|
48
|
+
rescue LoadError
|
49
|
+
require 'honeybadger/init/ruby'
|
50
|
+
end
|
51
|
+
|
52
|
+
if Honeybadger.config.get(:api_key).to_s =~ BLANK
|
53
|
+
say("Unable to send test: Honeybadger API key is missing.", :red)
|
54
|
+
exit(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
Honeybadger.config.set(:report_data, !options[:dry_run])
|
58
|
+
test_backend = TestBackend.new(Honeybadger.config.backend)
|
59
|
+
Honeybadger.config.backend = test_backend
|
60
|
+
|
61
|
+
at_exit do
|
62
|
+
Honeybadger.flush
|
63
|
+
|
64
|
+
if calling = TestBackend.callings[:notices].find {|c| c[0].exception.eql?(TEST_EXCEPTION) }
|
65
|
+
notice, response = *calling
|
66
|
+
|
67
|
+
if response.code != 201
|
68
|
+
host = Honeybadger.config.get(:'connection.host')
|
69
|
+
say(<<-MSG, :red)
|
70
|
+
!! --- Honeybadger test failed ------------------------------------------------ !!
|
71
|
+
|
72
|
+
The error notifier is installed, but we encountered an error:
|
73
|
+
|
74
|
+
#{response.code.kind_of?(Integer) ? "(#{response.code}) " : nil}#{response.error_message}
|
75
|
+
|
76
|
+
To fix this issue, please try the following:
|
77
|
+
|
78
|
+
- Make sure the gem is configured properly.
|
79
|
+
- Retry executing this command a few times.
|
80
|
+
- Make sure you can connect to #{host} (`ping #{host}`).
|
81
|
+
- Email support@honeybadger.io for help. Include as much debug info as you
|
82
|
+
can for a faster resolution!
|
83
|
+
|
84
|
+
!! --- End -------------------------------------------------------------------- !!
|
85
|
+
MSG
|
86
|
+
exit(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
say(generate_success_message(response), :green)
|
90
|
+
|
91
|
+
exit(0)
|
92
|
+
end
|
93
|
+
|
94
|
+
say(<<-MSG, :red)
|
95
|
+
!! --- Honeybadger test failed ------------------------------------------------ !!
|
96
|
+
|
97
|
+
Error: The test exception was not reported; the application may not be
|
98
|
+
configured properly.
|
99
|
+
|
100
|
+
This is usually caused by one of the following issues:
|
101
|
+
|
102
|
+
- There was a problem loading your application. Check your logs to see if a
|
103
|
+
different exception is being raised.
|
104
|
+
- The exception is being rescued before it reaches our Rack middleware. If
|
105
|
+
you're using `rescue` or `rescue_from` you may need to notify Honeybadger
|
106
|
+
manually: `Honeybadger.notify(exception)`.
|
107
|
+
- The honeybadger gem is misconfigured. Check the settings in your
|
108
|
+
honeybadger.yml file.
|
109
|
+
MSG
|
110
|
+
|
111
|
+
notices = TestBackend.callings[:notices].map(&:first)
|
112
|
+
unless notices.empty?
|
113
|
+
say("\nThe following errors were reported:", :red)
|
114
|
+
notices.each {|n| say("\n - #{n.error_class}: #{n.error_message}", :red) }
|
115
|
+
end
|
116
|
+
|
117
|
+
say("\nSee https://git.io/vXCYp for more troubleshooting help.\n\n", :red)
|
118
|
+
say("!! --- End -------------------------------------------------------------------- !!", :red)
|
119
|
+
|
120
|
+
exit(1)
|
121
|
+
end
|
122
|
+
|
123
|
+
run_test
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
attr_reader :options
|
129
|
+
|
130
|
+
def_delegator :@shell, :say
|
131
|
+
|
132
|
+
def run_test
|
133
|
+
if defined?(::Rails.application)
|
134
|
+
run_rails_test
|
135
|
+
else
|
136
|
+
run_standalone_test
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_exception_class
|
141
|
+
exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
|
142
|
+
Object.const_get(exception_name)
|
143
|
+
rescue
|
144
|
+
Object.const_set(exception_name, Class.new(Exception))
|
145
|
+
end
|
146
|
+
|
147
|
+
def run_standalone_test
|
148
|
+
Honeybadger.notify(TEST_EXCEPTION)
|
149
|
+
end
|
150
|
+
|
151
|
+
def run_rails_test
|
152
|
+
# Suppress error logging in Rails' exception handling middleware. Rails 3.0
|
153
|
+
# uses ActionDispatch::ShowExceptions to rescue/show exceptions, but does
|
154
|
+
# not log anything but application trace. Rails 3.2 now falls back to
|
155
|
+
# logging the framework trace (moved to ActionDispatch::DebugExceptions),
|
156
|
+
# which caused cluttered output while running the test task.
|
157
|
+
defined?(::ActionDispatch::DebugExceptions) and
|
158
|
+
::ActionDispatch::DebugExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end }
|
159
|
+
defined?(::ActionDispatch::ShowExceptions) and
|
160
|
+
::ActionDispatch::ShowExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end }
|
161
|
+
|
162
|
+
# Detect and disable the better_errors gem
|
163
|
+
if defined?(::BetterErrors::Middleware)
|
164
|
+
say('Better Errors detected: temporarily disabling middleware.', :yellow)
|
165
|
+
::BetterErrors::Middleware.class_eval { def call(env) @app.call(env); end }
|
166
|
+
end
|
167
|
+
|
168
|
+
begin
|
169
|
+
require './app/controllers/application_controller'
|
170
|
+
rescue LoadError
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
unless defined?(::ApplicationController)
|
175
|
+
say('Error: No ApplicationController found.', :red)
|
176
|
+
return false
|
177
|
+
end
|
178
|
+
|
179
|
+
eval(<<-CONTROLLER)
|
180
|
+
class Honeybadger::TestController < ApplicationController
|
181
|
+
# This is to bypass any filters that may prevent access to the action.
|
182
|
+
if respond_to?(:prepend_before_action)
|
183
|
+
prepend_before_action :test_honeybadger
|
184
|
+
else
|
185
|
+
prepend_before_filter :test_honeybadger
|
186
|
+
end
|
187
|
+
def test_honeybadger
|
188
|
+
puts "Raising '#{Honeybadger::CLI::Test::TEST_EXCEPTION.class.name}' to simulate application failure."
|
189
|
+
raise Honeybadger::CLI::Test::TEST_EXCEPTION
|
190
|
+
end
|
191
|
+
# Ensure we actually have an action to go to.
|
192
|
+
def verify; end
|
193
|
+
end
|
194
|
+
CONTROLLER
|
195
|
+
|
196
|
+
::Rails.application.routes.tap do |r|
|
197
|
+
# RouteSet#disable_clear_and_finalize prevents existing routes from
|
198
|
+
# being cleared. We'll set it back to the original value when we're
|
199
|
+
# done so not to mess with Rails state.
|
200
|
+
d = r.disable_clear_and_finalize
|
201
|
+
begin
|
202
|
+
r.disable_clear_and_finalize = true
|
203
|
+
r.clear!
|
204
|
+
r.draw do
|
205
|
+
match 'verify' => 'honeybadger/test#verify', :as => 'verify', :via => :get
|
206
|
+
end
|
207
|
+
::Rails.application.routes_reloader.paths.each{ |path| load(path) }
|
208
|
+
::ActiveSupport.on_load(:action_controller) { r.finalize! }
|
209
|
+
ensure
|
210
|
+
r.disable_clear_and_finalize = d
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
ssl = defined?(::Rails.configuration.force_ssl) && ::Rails.configuration.force_ssl
|
215
|
+
env = ::Rack::MockRequest.env_for("http#{ ssl ? 's' : nil }://www.example.com/verify", 'REMOTE_ADDR' => '127.0.0.1')
|
216
|
+
|
217
|
+
::Rails.application.call(env)
|
218
|
+
end
|
219
|
+
|
220
|
+
def generate_success_message(response)
|
221
|
+
notice_id = JSON.parse(response.body)['id']
|
222
|
+
notice_url = "https://app.honeybadger.io/notice/#{notice_id}"
|
223
|
+
|
224
|
+
unless options[:install]
|
225
|
+
return "⚡ Success: #{notice_url}"
|
226
|
+
end
|
227
|
+
|
228
|
+
<<-MSG
|
229
|
+
⚡ --- Honeybadger is installed! -----------------------------------------------
|
230
|
+
|
231
|
+
Good news: You're one deploy away from seeing all of your exceptions in
|
232
|
+
Honeybadger. For now, we've generated a test exception for you:
|
233
|
+
|
234
|
+
#{notice_url}
|
235
|
+
|
236
|
+
Optional steps:
|
237
|
+
|
238
|
+
- Show a feedback form on your error page:
|
239
|
+
http://docs.honeybadger.io/gem-feedback
|
240
|
+
- Show a UUID or link to Honeybadger on your error page:
|
241
|
+
http://docs.honeybadger.io/gem-informer
|
242
|
+
- Track deployments (if you're using Capistrano, we already did this):
|
243
|
+
http://docs.honeybadger.io/gem-deploys
|
244
|
+
|
245
|
+
If you ever need help:
|
246
|
+
|
247
|
+
- Read the gem troubleshooting guide: https://git.io/vXCYp
|
248
|
+
- Check out our documentation: http://docs.honeybadger.io/
|
249
|
+
- Email the founders: support@honeybadger.io
|
250
|
+
|
251
|
+
Most people don't realize that Honeybadger is a small, bootstrapped company. We
|
252
|
+
really couldn't do this without you. Thank you for allowing us to do what we
|
253
|
+
love: making developers awesome.
|
254
|
+
|
255
|
+
Happy 'badgering!
|
256
|
+
|
257
|
+
Sincerely,
|
258
|
+
Ben, Josh and Starr
|
259
|
+
https://www.honeybadger.io/about/
|
260
|
+
|
261
|
+
⚡ --- End --------------------------------------------------------------------
|
262
|
+
MSG
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
data/lib/honeybadger/config.rb
CHANGED
@@ -10,7 +10,6 @@ require 'honeybadger/backend'
|
|
10
10
|
require 'honeybadger/config/defaults'
|
11
11
|
require 'honeybadger/util/http'
|
12
12
|
require 'honeybadger/logging'
|
13
|
-
require 'honeybadger/rack/request_hash'
|
14
13
|
|
15
14
|
module Honeybadger
|
16
15
|
class Config
|
@@ -20,9 +19,11 @@ module Honeybadger
|
|
20
19
|
|
21
20
|
class ConfigError < StandardError; end
|
22
21
|
|
23
|
-
|
22
|
+
# Config subclasses have circular dependencies, so they must be loaded
|
23
|
+
# after constants are defined.
|
24
24
|
autoload :Env, 'honeybadger/config/env'
|
25
25
|
autoload :Yaml, 'honeybadger/config/yaml'
|
26
|
+
autoload :Ruby, 'honeybadger/config/ruby'
|
26
27
|
|
27
28
|
KEY_REPLACEMENT = Regexp.new('[^a-z\d_]', Regexp::IGNORECASE).freeze
|
28
29
|
|
@@ -30,70 +31,100 @@ module Honeybadger
|
|
30
31
|
|
31
32
|
NOT_BLANK = Regexp.new('\S').freeze
|
32
33
|
|
33
|
-
FEATURES = [:notices, :local_variables].freeze
|
34
|
-
|
35
|
-
MERGE_DEFAULT = [:'exceptions.ignore'].freeze
|
36
|
-
|
37
|
-
OVERRIDE = {
|
38
|
-
:'exceptions.ignore' => :'exceptions.ignore_only'
|
39
|
-
}.freeze
|
40
|
-
|
41
|
-
DEFAULT_REQUEST_HASH = {}.freeze
|
42
|
-
|
43
34
|
def initialize(opts = {})
|
44
|
-
|
45
|
-
|
46
|
-
@
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
35
|
+
@ruby = opts.freeze
|
36
|
+
@env = {}.freeze
|
37
|
+
@yaml = {}.freeze
|
38
|
+
@framework = {}.freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :ruby, :env, :yaml, :framework
|
42
|
+
|
43
|
+
def init!(opts = {}, env = ENV)
|
44
|
+
self.framework = opts.freeze
|
45
|
+
self.env = Env.new(env).freeze
|
46
|
+
load_config_from_disk {|yaml| self.yaml = yaml.freeze }
|
47
|
+
init_logging!
|
48
|
+
init_backend!
|
49
|
+
logger.info(sprintf('Initializing Honeybadger Error Tracker for Ruby. Ship it! version=%s framework=%s', Honeybadger::VERSION, detected_framework))
|
50
|
+
logger.warn('Entering development mode: data will not be reported.') if dev? && backend.kind_of?(Backend::Null)
|
51
|
+
if valid? && !ping
|
52
|
+
logger.warn('Failed to connect to Honeybadger service -- please verify that api.honeybadger.io is reachable (connection will be retried).')
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
53
56
|
|
54
|
-
|
55
|
-
|
57
|
+
def configure
|
58
|
+
ruby_config = Ruby.new(self)
|
59
|
+
yield(ruby_config)
|
60
|
+
self.ruby = ruby.merge(ruby_config).freeze
|
61
|
+
@logging = @backend = nil
|
62
|
+
self
|
63
|
+
end
|
56
64
|
|
57
|
-
|
65
|
+
def backtrace_filter
|
66
|
+
self[:backtrace_filter] = Proc.new if block_given?
|
67
|
+
self[:backtrace_filter]
|
58
68
|
end
|
59
69
|
|
60
|
-
|
70
|
+
def exception_filter
|
71
|
+
self[:exception_filter] = Proc.new if block_given?
|
72
|
+
self[:exception_filter]
|
73
|
+
end
|
61
74
|
|
62
|
-
|
75
|
+
def exception_fingerprint
|
76
|
+
self[:exception_fingerprint] = Proc.new if block_given?
|
77
|
+
self[:exception_fingerprint]
|
78
|
+
end
|
63
79
|
|
64
80
|
def get(key)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
else
|
71
|
-
DEFAULTS[key]
|
81
|
+
[:@ruby, :@env, :@yaml, :@framework].each do |var|
|
82
|
+
source = instance_variable_get(var)
|
83
|
+
if source.has_key?(key)
|
84
|
+
return source[key]
|
85
|
+
end
|
72
86
|
end
|
87
|
+
|
88
|
+
DEFAULTS[key]
|
73
89
|
end
|
74
90
|
alias [] :get
|
75
91
|
|
76
92
|
def set(key, value)
|
77
|
-
|
93
|
+
self.ruby = ruby.merge(key => value).freeze
|
94
|
+
@logging = @backend = nil
|
78
95
|
end
|
79
96
|
alias []= :set
|
80
97
|
|
81
98
|
def to_hash(defaults = false)
|
82
|
-
hash =
|
99
|
+
hash = [:@ruby, :@env, :@yaml, :@framework].reverse.reduce({}) do |a,e|
|
100
|
+
a.merge!(instance_variable_get(e))
|
101
|
+
end
|
102
|
+
|
103
|
+
hash = DEFAULTS.merge(hash) if defaults
|
104
|
+
|
83
105
|
undotify_keys(hash.select {|k,v| DEFAULTS.has_key?(k) })
|
84
106
|
end
|
85
|
-
alias
|
107
|
+
alias to_h to_hash
|
86
108
|
|
87
|
-
|
88
|
-
!!features[feature.to_sym]
|
89
|
-
end
|
109
|
+
# Internal Helpers
|
90
110
|
|
91
111
|
def logger
|
92
|
-
@logger
|
112
|
+
init_logging! unless @logger
|
113
|
+
@logger
|
93
114
|
end
|
94
115
|
|
95
116
|
def backend
|
96
|
-
|
117
|
+
init_backend! unless @backend
|
118
|
+
@backend
|
119
|
+
end
|
120
|
+
|
121
|
+
def backend=(backend)
|
122
|
+
set(:backend, backend)
|
123
|
+
@backend = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def disabled?
|
127
|
+
!!self[:disabled]
|
97
128
|
end
|
98
129
|
|
99
130
|
def dev?
|
@@ -106,14 +137,6 @@ module Honeybadger
|
|
106
137
|
!self[:env] || !dev?
|
107
138
|
end
|
108
139
|
|
109
|
-
def default_backend
|
110
|
-
if public?
|
111
|
-
:server
|
112
|
-
else
|
113
|
-
:null
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
140
|
def valid?
|
118
141
|
self[:api_key].to_s =~ /\S/
|
119
142
|
end
|
@@ -127,28 +150,12 @@ module Honeybadger
|
|
127
150
|
!!self[:'logging.debug']
|
128
151
|
end
|
129
152
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
def log_path
|
135
|
-
if self[:'logging.path'] && self[:'logging.path'] != 'STDOUT'
|
136
|
-
locate_absolute_path(self[:'logging.path'], self[:root])
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Internal: Path to honeybadger.yml configuration file; this should be the
|
141
|
-
# root directory if no path was specified.
|
142
|
-
#
|
143
|
-
# Returns the Pathname configuration path.
|
144
|
-
def config_path
|
145
|
-
config_paths.first
|
146
|
-
end
|
153
|
+
def ignored_classes
|
154
|
+
ignore_only = get(:'exceptions.ignore_only')
|
155
|
+
return ignore_only if ignore_only
|
156
|
+
return DEFAULTS[:'exceptions.ignore'] unless ignore = get(:'exceptions.ignore')
|
147
157
|
|
148
|
-
|
149
|
-
Array(ENV['HONEYBADGER_CONFIG_PATH'] || get(:'config.path')).map do |c|
|
150
|
-
locate_absolute_path(c, self[:root])
|
151
|
-
end
|
158
|
+
DEFAULTS[:'exceptions.ignore'] | Array(ignore)
|
152
159
|
end
|
153
160
|
|
154
161
|
def ca_bundle_path
|
@@ -183,32 +190,12 @@ module Honeybadger
|
|
183
190
|
end
|
184
191
|
end
|
185
192
|
|
186
|
-
def request
|
187
|
-
Thread.current[:__honeybadger_request]
|
188
|
-
end
|
189
|
-
|
190
|
-
def with_request(request, &block)
|
191
|
-
Thread.current[:__honeybadger_request] = request
|
192
|
-
yield
|
193
|
-
ensure
|
194
|
-
Thread.current[:__honeybadger_request] = nil
|
195
|
-
end
|
196
|
-
|
197
193
|
def max_queue_size
|
198
194
|
self[:max_queue_size]
|
199
195
|
end
|
200
196
|
|
201
|
-
def request_hash
|
202
|
-
return DEFAULT_REQUEST_HASH unless request
|
203
|
-
Rack::RequestHash.new(request)
|
204
|
-
end
|
205
|
-
|
206
197
|
def params_filters
|
207
|
-
self[:'request.filter_keys']
|
208
|
-
end
|
209
|
-
|
210
|
-
def rails_params_filters
|
211
|
-
request && request.env['action_dispatch.parameter_filter'] or []
|
198
|
+
Array(self[:'request.filter_keys'])
|
212
199
|
end
|
213
200
|
|
214
201
|
def excluded_request_keys
|
@@ -220,23 +207,6 @@ module Honeybadger
|
|
220
207
|
end
|
221
208
|
end
|
222
209
|
|
223
|
-
def write
|
224
|
-
path = config_path
|
225
|
-
|
226
|
-
if path.exist?
|
227
|
-
raise ConfigError, "The configuration file #{path} already exists."
|
228
|
-
elsif !path.dirname.writable?
|
229
|
-
raise ConfigError, "The configuration path #{path.dirname} is not writable."
|
230
|
-
end
|
231
|
-
|
232
|
-
File.open(path, 'w+') do |file|
|
233
|
-
file.write(<<-CONFIG)
|
234
|
-
---
|
235
|
-
api_key: '#{self[:api_key]}'
|
236
|
-
CONFIG
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
210
|
def log_level(key = :'logging.level')
|
241
211
|
case self[key].to_s
|
242
212
|
when /\A(0|debug)\z/i then Logger::DEBUG
|
@@ -249,21 +219,33 @@ api_key: '#{self[:api_key]}'
|
|
249
219
|
end
|
250
220
|
|
251
221
|
def load_plugin?(name)
|
252
|
-
return false if includes_token?(self[:'
|
222
|
+
return false if includes_token?(self[:'skipped_plugins'], name)
|
253
223
|
return true unless self[:plugins].kind_of?(Array)
|
254
224
|
includes_token?(self[:plugins], name)
|
255
225
|
end
|
256
226
|
|
227
|
+
# Internal: Match the project root.
|
228
|
+
#
|
229
|
+
# Returns Regexp matching the project root in a file string.
|
230
|
+
def root_regexp
|
231
|
+
return @root_regexp if @root_regexp
|
232
|
+
return nil if @no_root
|
233
|
+
|
234
|
+
root = get(:root).to_s
|
235
|
+
@no_root = true and return nil unless root =~ NOT_BLANK
|
236
|
+
|
237
|
+
@root_regexp = Regexp.new("^#{ Regexp.escape(root) }")
|
238
|
+
end
|
239
|
+
|
257
240
|
def ping
|
258
241
|
if result = send_ping
|
259
|
-
@features = symbolize_keys(result['features']) if result['features']
|
260
242
|
return true
|
261
243
|
end
|
262
244
|
|
263
245
|
false
|
264
246
|
end
|
265
247
|
|
266
|
-
def
|
248
|
+
def detected_framework
|
267
249
|
if self[:framework] =~ NOT_BLANK
|
268
250
|
self[:framework].to_sym
|
269
251
|
elsif defined?(::Rails::VERSION) && ::Rails::VERSION::STRING > '3.0'
|
@@ -278,7 +260,7 @@ api_key: '#{self[:api_key]}'
|
|
278
260
|
end
|
279
261
|
|
280
262
|
def framework_name
|
281
|
-
case
|
263
|
+
case detected_framework
|
282
264
|
when :rails then "Rails #{::Rails::VERSION::STRING}"
|
283
265
|
when :sinatra then "Sinatra #{::Sinatra::VERSION}"
|
284
266
|
when :rack then "Rack #{::Rack.release}"
|
@@ -287,20 +269,92 @@ api_key: '#{self[:api_key]}'
|
|
287
269
|
end
|
288
270
|
end
|
289
271
|
|
290
|
-
|
272
|
+
private
|
273
|
+
|
274
|
+
# Internal: Optional path to honeybadger.log log file.
|
291
275
|
#
|
292
|
-
# Returns
|
293
|
-
def
|
294
|
-
return
|
295
|
-
return
|
276
|
+
# Returns the Pathname log path if a log path was specified.
|
277
|
+
def log_path
|
278
|
+
return if log_stdout?
|
279
|
+
return if !self[:'logging.path']
|
280
|
+
locate_absolute_path(self[:'logging.path'], self[:root])
|
281
|
+
end
|
296
282
|
|
297
|
-
|
298
|
-
|
283
|
+
# Internal: Path to honeybadger.yml configuration file; this should be the
|
284
|
+
# root directory if no path was specified.
|
285
|
+
#
|
286
|
+
# Returns the Pathname configuration path.
|
287
|
+
def config_path
|
288
|
+
config_paths.first
|
289
|
+
end
|
299
290
|
|
300
|
-
|
291
|
+
def config_paths
|
292
|
+
Array(ENV['HONEYBADGER_CONFIG_PATH'] || get(:'config.path')).map do |c|
|
293
|
+
locate_absolute_path(c, self[:root])
|
294
|
+
end
|
301
295
|
end
|
302
296
|
|
303
|
-
|
297
|
+
def default_backend
|
298
|
+
return Backend::Server.new(self) if public?
|
299
|
+
Backend::Null.new(self)
|
300
|
+
end
|
301
|
+
|
302
|
+
def init_backend!
|
303
|
+
if self[:backend].is_a?(String) || self[:backend].is_a?(Symbol)
|
304
|
+
@backend = Backend.for(self[:backend].to_sym).new(self)
|
305
|
+
return
|
306
|
+
end
|
307
|
+
|
308
|
+
if ruby[:backend].respond_to?(:notify)
|
309
|
+
@backend = ruby[:backend]
|
310
|
+
return
|
311
|
+
end
|
312
|
+
|
313
|
+
if ruby[:backend]
|
314
|
+
logger.warn(sprintf('Unknown backend: %p; default will be used. Backend must respond to #notify', self[:backend]))
|
315
|
+
end
|
316
|
+
|
317
|
+
@backend = default_backend
|
318
|
+
end
|
319
|
+
|
320
|
+
def build_stdout_logger
|
321
|
+
logger = Logger.new($stdout)
|
322
|
+
logger.formatter = lambda do |severity, datetime, progname, msg|
|
323
|
+
"#{msg}\n"
|
324
|
+
end
|
325
|
+
logger.level = log_level
|
326
|
+
Logging::FormattedLogger.new(logger)
|
327
|
+
end
|
328
|
+
|
329
|
+
def build_file_logger(path)
|
330
|
+
Logger.new(path).tap do |logger|
|
331
|
+
logger.level = log_level
|
332
|
+
logger.formatter = Logger::Formatter.new
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def log_stdout?
|
337
|
+
self[:'logging.path'] && self[:'logging.path'].to_s.downcase == 'stdout'
|
338
|
+
end
|
339
|
+
|
340
|
+
def build_logger
|
341
|
+
return ruby[:logger] if ruby[:logger]
|
342
|
+
|
343
|
+
return build_stdout_logger if log_stdout?
|
344
|
+
|
345
|
+
if path = log_path
|
346
|
+
FileUtils.mkdir_p(path.dirname) unless path.dirname.writable?
|
347
|
+
return build_file_logger(path)
|
348
|
+
end
|
349
|
+
|
350
|
+
return framework[:logger] if framework[:logger]
|
351
|
+
|
352
|
+
Logger.new('/dev/null')
|
353
|
+
end
|
354
|
+
|
355
|
+
def init_logging!
|
356
|
+
@logger = Logging::ConfigLogger.new(self, build_logger)
|
357
|
+
end
|
304
358
|
|
305
359
|
# Internal: Does collection include the String value or Symbol value?
|
306
360
|
#
|
@@ -349,25 +403,6 @@ api_key: '#{self[:api_key]}'
|
|
349
403
|
end
|
350
404
|
end
|
351
405
|
|
352
|
-
def build_logger(default = nil)
|
353
|
-
if path = log_path
|
354
|
-
FileUtils.mkdir_p(path.dirname) unless path.dirname.writable?
|
355
|
-
Logger.new(path).tap do |logger|
|
356
|
-
logger.level = log_level
|
357
|
-
logger.formatter = Logger::Formatter.new
|
358
|
-
end
|
359
|
-
elsif self[:'logging.path'] != 'STDOUT' && default
|
360
|
-
default
|
361
|
-
else
|
362
|
-
logger = Logger.new($stdout)
|
363
|
-
logger.level = log_level
|
364
|
-
logger.formatter = lambda do |severity, datetime, progname, msg|
|
365
|
-
"#{msg}\n"
|
366
|
-
end
|
367
|
-
Logging::FormattedLogger.new(logger)
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
406
|
def load_config_from_disk
|
372
407
|
if (path = config_paths.find(&:exist?)) && path.file?
|
373
408
|
Yaml.new(path, self[:env]).tap do |yml|
|
@@ -375,11 +410,11 @@ api_key: '#{self[:api_key]}'
|
|
375
410
|
end
|
376
411
|
end
|
377
412
|
rescue ConfigError => e
|
378
|
-
error("
|
413
|
+
error("Error loading config from disk: #{e}")
|
379
414
|
nil
|
380
415
|
rescue StandardError => e
|
381
416
|
error {
|
382
|
-
msg = "
|
417
|
+
msg = "Error loading config from disk. class=%s message=%s\n\t%s"
|
383
418
|
sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
|
384
419
|
}
|
385
420
|
nil
|
@@ -392,29 +427,10 @@ api_key: '#{self[:api_key]}'
|
|
392
427
|
new_hash[$1] ||= {}
|
393
428
|
new_hash[$1] = undotify_keys(new_hash[$1].merge({$2 => v}))
|
394
429
|
else
|
395
|
-
new_hash[k] = v
|
430
|
+
new_hash[k.to_s] = v
|
396
431
|
end
|
397
432
|
end
|
398
433
|
end
|
399
434
|
end
|
400
|
-
|
401
|
-
def symbolize_keys(hash)
|
402
|
-
Hash[hash.map {|k,v| [k.to_sym, v] }]
|
403
|
-
end
|
404
|
-
|
405
|
-
# Internal: Merges supplied config options with defaults.
|
406
|
-
#
|
407
|
-
# config - The Hash config options to merge.
|
408
|
-
#
|
409
|
-
# Returns the updated Hash config with merged values.
|
410
|
-
def merge_defaults!(config)
|
411
|
-
MERGE_DEFAULT.each do |option|
|
412
|
-
if config[option].kind_of?(Array)
|
413
|
-
config[option] = (DEFAULTS[option] | config[option])
|
414
|
-
end
|
415
|
-
end
|
416
|
-
|
417
|
-
config
|
418
|
-
end
|
419
435
|
end
|
420
436
|
end
|