errorapp_notifier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +25 -0
- data/README.md +60 -0
- data/Rakefile +8 -0
- data/errorapp_notifier.gemspec +29 -0
- data/lib/errorapp_notifier/action_controller_methods.rb +35 -0
- data/lib/errorapp_notifier/application_environment_data.rb +77 -0
- data/lib/errorapp_notifier/config.rb +67 -0
- data/lib/errorapp_notifier/controller_failure_data.rb +96 -0
- data/lib/errorapp_notifier/exception_data.rb +19 -0
- data/lib/errorapp_notifier/failure_data.rb +87 -0
- data/lib/errorapp_notifier/monkeypatches.rb +11 -0
- data/lib/errorapp_notifier/notifier.rb +80 -0
- data/lib/errorapp_notifier/notifiers/rack_rails.rb +32 -0
- data/lib/errorapp_notifier/notifiers/rails.rb +34 -0
- data/lib/errorapp_notifier/notifiers/tester.rb +16 -0
- data/lib/errorapp_notifier/notify.rb +59 -0
- data/lib/errorapp_notifier/rack_failure_data.rb +29 -0
- data/lib/errorapp_notifier/railtie.rb +48 -0
- data/lib/errorapp_notifier/sanitizer.rb +56 -0
- data/lib/errorapp_notifier/tasks/errorapp_notifier.rake +6 -0
- data/lib/errorapp_notifier/version.rb +3 -0
- data/lib/errorapp_notifier.rb +74 -0
- data/lib/generators/errorapp_notifier/errorapp_notifier_generator.rb +54 -0
- data/lib/generators/errorapp_notifier/templates/errorapp_notifier.rb +2 -0
- data/spec/errorapp_notifier/config_spec.rb +76 -0
- data/spec/errorapp_notifier/controller_failure_data_spec.rb +277 -0
- data/spec/errorapp_notifier/failure_data_spec.rb +32 -0
- data/spec/errorapp_notifier/notifier_spec.rb +31 -0
- data/spec/errorapp_notifier/notify_spec.rb +128 -0
- data/spec/errorapp_notifier/rack_failure_data_spec.rb +87 -0
- data/spec/errorapp_notifier/sanitizer_spec.rb +57 -0
- data/spec/helper.rb +12 -0
- data/spec/spec_helper.rb +16 -0
- metadata +188 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module ErrorappNotifier
|
2
|
+
module Integration
|
3
|
+
class OmgTestException < StandardError;
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.test
|
7
|
+
begin
|
8
|
+
raise OmgTestException.new, 'Test exception'
|
9
|
+
rescue Exception => e
|
10
|
+
ErrorappNotifier::Notifier.notify_error(
|
11
|
+
ErrorappNotifier::FailureData.new(e, "Test Exception")
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ErrorappNotifier
|
2
|
+
class Notify
|
3
|
+
class << self
|
4
|
+
def notify_with_controller(exception, controller = nil, request = nil)
|
5
|
+
notify_exception(exception) do
|
6
|
+
if !ignore?(exception, request)
|
7
|
+
ControllerFailureData.new(exception, controller, request)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def notify_with_rack(exception, environment, request)
|
13
|
+
notify_exception(exception) do
|
14
|
+
RackFailureData.new(exception, environment, request)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def notify(exception, name = nil)
|
19
|
+
notify_exception(exception) do
|
20
|
+
FailureData.new(exception, name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ignore?(exception, request)
|
25
|
+
ignore_class?(exception) || ignore_user_agent?(request)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ignore_class?(exception)
|
29
|
+
config.ignore_exceptions.flatten.any? do |exception_class|
|
30
|
+
exception_class === exception.class.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ignore_user_agent?(request)
|
35
|
+
config.ignore_user_agents.flatten.any? do |user_agent|
|
36
|
+
user_agent === request.user_agent.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def notify_exception(exception, &block)
|
43
|
+
if config.should_send_to_api?
|
44
|
+
notify!(yield)
|
45
|
+
else
|
46
|
+
raise exception
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def notify!(data)
|
51
|
+
Notifier.notify_error(data)
|
52
|
+
end
|
53
|
+
|
54
|
+
def config
|
55
|
+
ErrorappNotifier.configuration
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ErrorappNotifier
|
2
|
+
class RackFailureData < FailureData
|
3
|
+
def initialize(exception, environment, request)
|
4
|
+
super(exception)
|
5
|
+
@environment = environment
|
6
|
+
@request = request
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def framework
|
12
|
+
'rack'
|
13
|
+
end
|
14
|
+
|
15
|
+
def extra_stuff
|
16
|
+
return {} if @request.nil?
|
17
|
+
{
|
18
|
+
:request => {
|
19
|
+
:url => "#{@request.url}",
|
20
|
+
:parameters => @request.params,
|
21
|
+
:request_method => @request.request_method.to_s,
|
22
|
+
:remote_ip => @request.ip,
|
23
|
+
:headers => extract_http_headers(@environment),
|
24
|
+
:session => Sanitizer.sanitize_session(@request)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'errorapp_notifier'
|
3
|
+
require 'errorapp_notifier/notifiers/rack_rails'
|
4
|
+
|
5
|
+
module ErrorappNotifier
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
rake_tasks do
|
8
|
+
load "errorapp_notifier/tasks/errorapp_notifier.rake"
|
9
|
+
end
|
10
|
+
|
11
|
+
config.after_initialize do
|
12
|
+
ErrorappNotifier.configure do |config|
|
13
|
+
config.logger ||= ::Rails.logger
|
14
|
+
config.environment_name ||= ::Rails.env
|
15
|
+
config.project_root ||= ::Rails.root
|
16
|
+
config.params_filters += Rails.configuration.filter_parameters.map do |filter|
|
17
|
+
case filter
|
18
|
+
when String, Symbol
|
19
|
+
/\A#{filter}\z/
|
20
|
+
else
|
21
|
+
filter
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
initializer "errorapp.middleware" do |app|
|
28
|
+
if defined?(ActionController::Base)
|
29
|
+
ActionController::Base.send(:include, ErrorappNotifier::ActionControllerMethods)
|
30
|
+
end
|
31
|
+
|
32
|
+
ErrorappNotifier.logger.info("Loading ErrorappNotifier #{ErrorappNotifier::VERSION} for #{Rails::VERSION::STRING}")
|
33
|
+
|
34
|
+
middleware = if defined?(ActionDispatch::DebugExceptions)
|
35
|
+
# rails 3.2.x
|
36
|
+
"ActionDispatch::DebugExceptions"
|
37
|
+
elsif defined?(ActionDispatch::ShowExceptions)
|
38
|
+
# rails 3.0.x && 3.1.x
|
39
|
+
"ActionDispatch::ShowExceptions"
|
40
|
+
end
|
41
|
+
begin
|
42
|
+
app.config.middleware.insert_after middleware, "Rack::RailsErrorappNotifier"
|
43
|
+
rescue
|
44
|
+
app.config.middleware.use "Rack::RailsErrorappNotifier"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ErrorappNotifier
|
2
|
+
class Sanitizer
|
3
|
+
def self.sanitize_hash(hash)
|
4
|
+
case hash
|
5
|
+
when Hash
|
6
|
+
hash.inject({}) do |result, (key, value)|
|
7
|
+
result.update(key => sanitize_hash(value))
|
8
|
+
end
|
9
|
+
when Array
|
10
|
+
hash.collect{|value| sanitize_hash(value)}
|
11
|
+
when Fixnum, String, Bignum
|
12
|
+
hash
|
13
|
+
else
|
14
|
+
hash.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.filter_hash(keys_to_filter, hash)
|
19
|
+
keys_to_filter.map!{ |key| key.to_s }
|
20
|
+
if keys_to_filter.is_a?(Array) && !keys_to_filter.empty?
|
21
|
+
hash.each do |key, value|
|
22
|
+
if key_match?(key, keys_to_filter)
|
23
|
+
hash[key] = '[FILTERED]'
|
24
|
+
elsif value.respond_to?(:to_hash)
|
25
|
+
filter_hash(keys_to_filter, hash[key])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.sanitize_session(request)
|
33
|
+
session_hash = {'session_id' => "", 'data' => {}}
|
34
|
+
|
35
|
+
if request.respond_to?(:session)
|
36
|
+
session = request.session
|
37
|
+
session_hash['session_id'] = request.session_options ? request.session_options[:id] : nil
|
38
|
+
session_hash['session_id'] ||= session.respond_to?(:session_id) ? session.session_id : session.instance_variable_get("@session_id")
|
39
|
+
session_hash['data'] = session.respond_to?(:to_hash) ? session.to_hash : session.instance_variable_get("@data") || {}
|
40
|
+
session_hash['session_id'] ||= session_hash['data'][:session_id]
|
41
|
+
session_hash['data'].delete(:session_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
sanitize_hash(session_hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def self.key_match?(key, keys_to_filter)
|
50
|
+
keys_to_filter.any? do |k|
|
51
|
+
regexp = k.is_a?(Regexp)? k : Regexp.new(k.to_s, true)
|
52
|
+
key =~ regexp
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require 'errorapp_notifier/config'
|
4
|
+
require 'errorapp_notifier/failure_data'
|
5
|
+
require 'errorapp_notifier/notify'
|
6
|
+
require 'errorapp_notifier/controller_failure_data'
|
7
|
+
require 'errorapp_notifier/rack_failure_data'
|
8
|
+
require 'errorapp_notifier/notifier'
|
9
|
+
require 'errorapp_notifier/sanitizer'
|
10
|
+
require 'errorapp_notifier/version'
|
11
|
+
require 'errorapp_notifier/action_controller_methods'
|
12
|
+
require 'errorapp_notifier/notifiers/tester'
|
13
|
+
|
14
|
+
require 'errorapp_notifier/railtie' if defined?(Rails::Railtie)
|
15
|
+
|
16
|
+
module ErrorappNotifier
|
17
|
+
PROTOCOL_VERSION = 1
|
18
|
+
CLIENT_NAME = 'errorapp_notifier-gem'
|
19
|
+
ENVIRONMENT_FILTER = []
|
20
|
+
ENVIRONMENT_WHITELIST = %w(HOME PATH PWD RUBYOPT GEM_HOME RACK_ENV
|
21
|
+
RAILS_ENV BUNDLE_GEMFILE BUNDLE_BIN_PATH)
|
22
|
+
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def configure
|
26
|
+
yield(configuration)
|
27
|
+
end
|
28
|
+
|
29
|
+
def configuration
|
30
|
+
@configuration ||= ErrorappNotifier::Config.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
configuration.logger
|
35
|
+
end
|
36
|
+
|
37
|
+
def notify(exception, name=nil)
|
38
|
+
ErrorappNotifier::Notify.notify(exception, name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def rescue(name=nil, context=nil, &block)
|
42
|
+
begin
|
43
|
+
context(context) unless context.nil?
|
44
|
+
block.call
|
45
|
+
rescue Exception => e
|
46
|
+
ErrorappNotifier::Notify.notify(e, name)
|
47
|
+
ensure
|
48
|
+
clear!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def rescue_and_reraise(name=nil, context=nil, &block)
|
53
|
+
begin
|
54
|
+
context(context) unless context.nil?
|
55
|
+
block.call
|
56
|
+
rescue Exception => e
|
57
|
+
ErrorappNotifier::Notify.notify(e, name)
|
58
|
+
raise(e)
|
59
|
+
ensure
|
60
|
+
clear!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def clear!
|
65
|
+
Thread.current[:notifier_context] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def context(hash = {})
|
69
|
+
Thread.current[:notifier_context] ||= {}
|
70
|
+
Thread.current[:notifier_context].merge!(hash)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ErrorappNotifierGenerator < Rails::Generators::Base
|
4
|
+
desc "Creates the ErrorApp initializer file at config/errorapp_notifier.rb"
|
5
|
+
|
6
|
+
class_option :api_key, :aliases => "-k", :type => :string,
|
7
|
+
:desc => "Your ErrorApp API key"
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
@_errorapp_source_root ||= File.expand_path("../templates", __FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
def install
|
14
|
+
ensure_api_key_was_configured
|
15
|
+
generate_initializer unless api_key_configured?
|
16
|
+
test_errorapp
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def ensure_api_key_was_configured
|
22
|
+
if !options[:api_key] && !api_key_configured?
|
23
|
+
puts "Must pass --api_key or create config/initializers/errorapp_notifier.rb"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def api_key
|
29
|
+
if options[:api_key]
|
30
|
+
"'#{options[:api_key]}'"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_initializer
|
35
|
+
template 'errorapp_notifier.rb', 'config/initializers/errorapp_notifier.rb'
|
36
|
+
end
|
37
|
+
|
38
|
+
def api_key_configured?
|
39
|
+
File.exists?('config/initializers/errorapp_notifier.rb')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_errorapp
|
43
|
+
puts run("rails runner ErrorappNotifier::Integration.test")
|
44
|
+
end
|
45
|
+
|
46
|
+
def configuration_output
|
47
|
+
output = <<-eos
|
48
|
+
ErrorappNotifier.configure do|config|
|
49
|
+
config.api_key = '#{options[:api_key]}'
|
50
|
+
end
|
51
|
+
eos
|
52
|
+
output
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ErrorappNotifier::Config, 'defaults' do
|
4
|
+
it "have sensible defaults" do
|
5
|
+
default_config.ssl.should == true
|
6
|
+
default_config.remote_host.should == 'errorapp.com'
|
7
|
+
default_config.remote_port.should == 443
|
8
|
+
default_config.application_root.should == Dir.pwd
|
9
|
+
default_config.http_proxy_host.should be_nil
|
10
|
+
default_config.http_proxy_port.should be_nil
|
11
|
+
default_config.http_proxy_username.should be_nil
|
12
|
+
default_config.http_proxy_password.should be_nil
|
13
|
+
default_config.http_open_timeout.should == 2
|
14
|
+
default_config.http_read_timeout.should == 4
|
15
|
+
end
|
16
|
+
|
17
|
+
it "have correct defaults when ssl" do
|
18
|
+
default_config.ssl = true
|
19
|
+
default_config.remote_host.should == 'errorapp.com'
|
20
|
+
default_config.remote_port.should == 443
|
21
|
+
end
|
22
|
+
|
23
|
+
it "be disabled based on environment by default" do
|
24
|
+
%w(development test).each do |env|
|
25
|
+
default_config.stub(:application_environment).and_return(env)
|
26
|
+
default_config.should_send_to_api?.should == false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "be enabled based on environment by default" do
|
31
|
+
%w(production staging).each do |env|
|
32
|
+
default_config.stub(:application_environment).and_return(env)
|
33
|
+
default_config.should_send_to_api?.should == true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "load api_key from environment variable" do
|
38
|
+
ENV.stub(:[]).with('ERRORAPP_API_KEY').and_return('env-api-key')
|
39
|
+
default_config.api_key.should == 'env-api-key'
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'production environment' do
|
43
|
+
before :each do
|
44
|
+
default_config.stub(:application_environment).and_return('production')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'loads a errorapp_notifier config' do
|
48
|
+
override_default_value :api_key
|
49
|
+
override_default_value :ssl, true
|
50
|
+
override_default_value :remote_host, 'example.com'
|
51
|
+
override_default_value :remote_port, 3000
|
52
|
+
override_default_value :http_proxy_host, 'annoying-proxy.example.com'
|
53
|
+
override_default_value :http_proxy_port, 1066
|
54
|
+
override_default_value :http_proxy_username, 'username'
|
55
|
+
override_default_value :http_proxy_password, 'password'
|
56
|
+
override_default_value :http_open_timeout, 5
|
57
|
+
override_default_value :http_read_timeout, 10
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'disable' do
|
61
|
+
default_config.api_key = "new-key"
|
62
|
+
default_config.disabled_by_default = %(production)
|
63
|
+
default_config.api_key.should == 'new-key'
|
64
|
+
default_config.should_send_to_api?.should == false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_config
|
70
|
+
@config||= ErrorappNotifier::Config.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def override_default_value(option, value ="value")
|
74
|
+
default_config.send(:"#{option}=", value)
|
75
|
+
expect(default_config.send(option)).to eq(value)
|
76
|
+
end
|