appygram-rails 0.9.1
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/appygram.gemspec +16 -0
- data/init.rb +39 -0
- data/lib/appygram/alert_data.rb +15 -0
- data/lib/appygram/application_environment.rb +55 -0
- data/lib/appygram/catcher.rb +34 -0
- data/lib/appygram/config.rb +63 -0
- data/lib/appygram/controller_exception_data.rb +75 -0
- data/lib/appygram/exception_data.rb +108 -0
- data/lib/appygram/integration/alerter.rb +11 -0
- data/lib/appygram/integration/debug_exceptions.rb +16 -0
- data/lib/appygram/integration/rack.rb +28 -0
- data/lib/appygram/integration/rack_rails.rb +26 -0
- data/lib/appygram/log_factory.rb +39 -0
- data/lib/appygram/monkeypatches.rb +10 -0
- data/lib/appygram/rack_exception_data.rb +29 -0
- data/lib/appygram/railtie.rb +20 -0
- data/lib/appygram/remote.rb +48 -0
- data/lib/appygram/startup.rb +14 -0
- data/lib/appygram/version.rb +3 -0
- data/lib/appygram.rb +71 -0
- data/rails/init.rb +1 -0
- metadata +80 -0
data/appygram.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/appygram/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = %q{appygram-rails}
|
6
|
+
gem.version = Appygram::VERSION
|
7
|
+
gem.authors = ["rfc2616"]
|
8
|
+
gem.summary = %q{ appygram is a hosted service for sending messages from mobile/web apps }
|
9
|
+
gem.description = %q{Appygram is the Ruby gem for communicating with http://appygram.com (hosted messaging service). It supports an exception reporting mechanism similar to http://exceptional.io, and this Ruby gem, forked from the Exceptional gem, emulates Exceptional functionality.}
|
10
|
+
gem.email = %q{heittman.rob@gmail.com}
|
11
|
+
gem.files = Dir['lib/**/*'] + Dir['spec/**/*'] + Dir['spec/**/*'] + Dir['rails/**/*'] + Dir['tasks/**/*'] + Dir['*.rb'] + ["appygram.gemspec"]
|
12
|
+
gem.homepage = %q{http://appygram.com/}
|
13
|
+
gem.require_paths = ["lib"]
|
14
|
+
gem.requirements << "json_pure, json-jruby or json gem required"
|
15
|
+
gem.add_dependency 'rack'
|
16
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'appygram'
|
2
|
+
|
3
|
+
# If old plugin still installed then we don't want to install this one.
|
4
|
+
# In production environments we should continue to work as before, but in development/test we should
|
5
|
+
# advise how to correct the problem and exit
|
6
|
+
if (defined?(Appygram::VERSION::STRING) rescue nil) && %w(development test).include?(RAILS_ENV)
|
7
|
+
message = %Q(
|
8
|
+
***********************************************************************
|
9
|
+
You seem to still have an old version of the Appygram plugin installed.
|
10
|
+
Remove it from /vendor/plugins and try again.
|
11
|
+
***********************************************************************
|
12
|
+
)
|
13
|
+
puts message
|
14
|
+
exit -1
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
|
18
|
+
if (Rails::VERSION::MAJOR < 3)
|
19
|
+
Appygram::Config.load(File.join(RAILS_ROOT, "/config/Appygram.yml"))
|
20
|
+
if Appygram::Config.should_send_to_api?
|
21
|
+
Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
|
22
|
+
require File.join('Appygram', 'integration', 'rails')
|
23
|
+
require File.join('Appygram', 'integration', 'dj') if defined?(Delayed::Job)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Appygram::Config.load(File.join(Rails.root, "/config/Appygram.yml"))
|
27
|
+
|
28
|
+
if Appygram::Config.should_send_to_api?
|
29
|
+
Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
|
30
|
+
Rails.configuration.middleware.use "Rack::RailsAppygram"
|
31
|
+
require File.join('Appygram', 'integration', 'dj') if defined?(Delayed::Job)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue => e
|
35
|
+
STDERR.puts "Problem starting Appygram Plugin. Your app will run as normal. #{e.message}"
|
36
|
+
Appygram.logger.error(e.message)
|
37
|
+
Appygram.logger.error(e.backtrace)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Appygram
|
2
|
+
class AlertData < ExceptionData
|
3
|
+
# Overwrite backtrace, since it is irrelevant
|
4
|
+
def extra_data
|
5
|
+
{
|
6
|
+
'exception' => {
|
7
|
+
'exception_class' => @exception.class.to_s,
|
8
|
+
'message' => @exception.message,
|
9
|
+
'backtrace' => "",
|
10
|
+
'occurred_at' => Time.now
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Appygram
|
4
|
+
class ApplicationEnvironment
|
5
|
+
def self.to_hash(framework)
|
6
|
+
{
|
7
|
+
'client' => {
|
8
|
+
'name' => Appygram::CLIENT_NAME,
|
9
|
+
'version' => Appygram::VERSION,
|
10
|
+
'protocol_version' => Appygram::PROTOCOL_VERSION
|
11
|
+
},
|
12
|
+
'application_environment' => {
|
13
|
+
'host' => get_hostname,
|
14
|
+
'run_as_user' => get_username,
|
15
|
+
'application_root_directory' => (application_root.to_s.respond_to?(:force_encoding) ? application_root.to_s.force_encoding("UTF-8") : application_root),
|
16
|
+
'language' => 'ruby',
|
17
|
+
'language_version' => language_version_string,
|
18
|
+
'framework' => framework,
|
19
|
+
'libraries_loaded' => libraries_loaded
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.environment
|
25
|
+
Config.application_environment
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.application_root
|
29
|
+
Config.application_root
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.get_hostname
|
33
|
+
require 'socket' unless defined?(Socket)
|
34
|
+
Socket.gethostname
|
35
|
+
rescue
|
36
|
+
'UNKNOWN'
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.language_version_string
|
40
|
+
"#{RUBY_VERSION rescue '?.?.?'} p#{RUBY_PATCHLEVEL rescue '???'} #{RUBY_RELEASE_DATE rescue '????-??-??'} #{RUBY_PLATFORM rescue '????'}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.get_username
|
44
|
+
ENV['LOGNAME'] || ENV['USER'] || ENV['USERNAME'] || ENV['APACHE_RUN_USER'] || 'UNKNOWN'
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.libraries_loaded
|
48
|
+
begin
|
49
|
+
return Hash[*Gem.loaded_specs.map{|name, gem_specification| [name, gem_specification.version.to_s]}.flatten]
|
50
|
+
rescue
|
51
|
+
end
|
52
|
+
{}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Appygram
|
2
|
+
class Catcher
|
3
|
+
class << self
|
4
|
+
def handle_with_controller(exception, controller=nil, request=nil)
|
5
|
+
if Config.should_send_to_api?
|
6
|
+
data = ControllerExceptionData.new(exception, controller, request)
|
7
|
+
Remote.error(data)
|
8
|
+
else
|
9
|
+
raise exception
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# unspeced
|
14
|
+
def handle_with_rack(exception, environment, request)
|
15
|
+
if Config.should_send_to_api?
|
16
|
+
data = RackExceptionData.new(exception, environment, request)
|
17
|
+
Remote.error(data)
|
18
|
+
else
|
19
|
+
raise exception
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# unspeced
|
24
|
+
def handle(exception, name=nil)
|
25
|
+
if Config.should_send_to_api?
|
26
|
+
data = ExceptionData.new(exception, name)
|
27
|
+
Remote.error(data)
|
28
|
+
else
|
29
|
+
raise exception
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Appygram
|
4
|
+
class Config
|
5
|
+
class ConfigurationException < StandardError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
DEFAULTS = {
|
9
|
+
:ssl => false,
|
10
|
+
:remote_host_http => 'api.appygram.com',
|
11
|
+
:http_open_timeout => 2,
|
12
|
+
:http_read_timeout => 4,
|
13
|
+
:disabled_by_default => %w(development test)
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_accessor :api_key, :enabled
|
17
|
+
attr_accessor :http_proxy_host, :http_proxy_port, :http_proxy_username, :http_proxy_password
|
18
|
+
attr_writer :ssl
|
19
|
+
|
20
|
+
def api_key
|
21
|
+
return @api_key unless @api_key.nil?
|
22
|
+
@api_key ||= ENV['EXCEPTIONAL_API_KEY'] unless ENV['EXCEPTIONAL_API_KEY'].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def application_environment
|
26
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV']|| 'development'
|
27
|
+
end
|
28
|
+
|
29
|
+
def should_send_to_api?
|
30
|
+
return @enabled unless @enabled.nil?
|
31
|
+
@enabled = !(DEFAULTS[:disabled_by_default].include?(application_environment))
|
32
|
+
end
|
33
|
+
|
34
|
+
def application_root
|
35
|
+
(defined?(Rails) && Rails.respond_to?(:root)) ? Rails.root : Dir.pwd
|
36
|
+
end
|
37
|
+
|
38
|
+
def ssl?
|
39
|
+
@ssl ||= DEFAULTS[:ssl]
|
40
|
+
end
|
41
|
+
|
42
|
+
def remote_host
|
43
|
+
@remote_host ||= DEFAULTS[:remote_host_http]
|
44
|
+
end
|
45
|
+
|
46
|
+
def remote_port
|
47
|
+
@remote_port ||= ssl? ? 443 : 80
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset
|
51
|
+
@enabled = @ssl = @remote_host = @remote_port = @api_key = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def http_open_timeout
|
55
|
+
@http_open_timeout ||= DEFAULTS[:http_open_timeout]
|
56
|
+
end
|
57
|
+
|
58
|
+
def http_read_timeout
|
59
|
+
@http_read_timeout ||= DEFAULTS[:http_read_timeout]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Appygram
|
4
|
+
class ControllerExceptionData < ExceptionData
|
5
|
+
def initialize(exception, controller=nil, request=nil)
|
6
|
+
super(exception)
|
7
|
+
@request = request
|
8
|
+
@controller = controller
|
9
|
+
end
|
10
|
+
|
11
|
+
def framework
|
12
|
+
"rails"
|
13
|
+
end
|
14
|
+
|
15
|
+
def extra_stuff
|
16
|
+
return {} if @request.nil?
|
17
|
+
e = {
|
18
|
+
'request' => {
|
19
|
+
'url' => (@request.respond_to?(:url) ? @request.url : "#{@request.protocol}#{@request.host}#{@request.request_uri}"),
|
20
|
+
'controller' => @controller.class.to_s,
|
21
|
+
'action' => (@request.respond_to?(:parameters) ? @request.parameters['action'] : @request.params['action']),
|
22
|
+
'parameters' => filter_parameters(@request.respond_to?(:parameters) ? @request.parameters : @request.params),
|
23
|
+
'request_method' => @request.request_method.to_s,
|
24
|
+
'remote_ip' => (@request.respond_to?(:remote_ip) ? @request.remote_ip : @request.ip),
|
25
|
+
'headers' => extract_http_headers(@request.env),
|
26
|
+
'session' => self.class.sanitize_session(@request)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
if @controller.respond_to?(:current_user)
|
30
|
+
cu = @controller.current_user
|
31
|
+
if cu and cu.email
|
32
|
+
e['request']['current_user'] = {
|
33
|
+
'id' => cu.id,
|
34
|
+
'email'=> cu.email
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return e
|
39
|
+
end
|
40
|
+
|
41
|
+
def filter_hash(keys_to_filter, hash)
|
42
|
+
keys_to_filter.map! {|x| x.to_s}
|
43
|
+
if keys_to_filter.is_a?(Array) && !keys_to_filter.empty?
|
44
|
+
hash.each do |key, value|
|
45
|
+
if key_match?(key, keys_to_filter)
|
46
|
+
hash[key] = "[FILTERED]"
|
47
|
+
elsif value.respond_to?(:to_hash)
|
48
|
+
filter_hash(keys_to_filter, hash[key])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
hash
|
53
|
+
end
|
54
|
+
|
55
|
+
# Closer alignment to latest filtered_params:
|
56
|
+
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb
|
57
|
+
# https://github.com/exceptional/exceptional/issues/20
|
58
|
+
def key_match?(key, keys_to_filter)
|
59
|
+
keys_to_filter.any? { |k|
|
60
|
+
regexp = k.is_a?(Regexp)? k : Regexp.new(k, true)
|
61
|
+
key =~ regexp
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def filter_parameters(hash)
|
66
|
+
if @request.respond_to?(:env) && @request.env["action_dispatch.parameter_filter"]
|
67
|
+
filter_hash(@request.env["action_dispatch.parameter_filter"], hash)
|
68
|
+
elsif @controller.respond_to?(:filter_parameters)
|
69
|
+
@controller.send(:filter_parameters, hash)
|
70
|
+
else
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Appygram
|
5
|
+
class ExceptionData
|
6
|
+
|
7
|
+
def initialize(exception, name=nil)
|
8
|
+
@exception = exception
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
hash = ::Appygram::ApplicationEnvironment.to_hash(framework)
|
14
|
+
hash.merge!({
|
15
|
+
'exception' => {
|
16
|
+
'exception_class' => @exception.class.to_s,
|
17
|
+
'message' => @exception.message,
|
18
|
+
'backtrace' => @exception.backtrace,
|
19
|
+
'occurred_at' => Time.now.utc.iso8601
|
20
|
+
}
|
21
|
+
})
|
22
|
+
hash.merge!(extra_stuff)
|
23
|
+
hash.merge!(context_stuff)
|
24
|
+
self.class.sanitize_hash(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def extra_stuff
|
28
|
+
{ 'rescue_block' => { 'name' => @name} }
|
29
|
+
end
|
30
|
+
|
31
|
+
def context_stuff
|
32
|
+
context = Thread.current[:appygram_context]
|
33
|
+
(context.nil? || context.empty?) ? {} : {'context' => context}
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_json
|
37
|
+
begin
|
38
|
+
to_hash.to_json
|
39
|
+
rescue NoMethodError
|
40
|
+
begin
|
41
|
+
require 'json'
|
42
|
+
return to_hash.to_json
|
43
|
+
rescue StandardError => e
|
44
|
+
Appygram.logger.error(e.message)
|
45
|
+
Appygram.logger.error(e.backtrace)
|
46
|
+
raise StandardError.new("You need a json gem/library installed to send errors to Appygram (Object.to_json not defined). \nInstall json_pure, yajl-ruby, json-jruby, or the c-based json gem")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def framework
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def uniqueness_hash
|
56
|
+
return nil if (@exception.backtrace.nil? || @exception.backtrace.empty?)
|
57
|
+
Digest::MD5.hexdigest(@exception.backtrace.join)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.sanitize_hash(hash)
|
61
|
+
|
62
|
+
case hash
|
63
|
+
when Hash
|
64
|
+
hash.inject({}) do |result, (key, value)|
|
65
|
+
result.update(key => sanitize_hash(value))
|
66
|
+
end
|
67
|
+
when Array
|
68
|
+
hash.collect{|value| sanitize_hash(value)}
|
69
|
+
when Fixnum, String, Bignum
|
70
|
+
hash
|
71
|
+
else
|
72
|
+
hash.to_s
|
73
|
+
end
|
74
|
+
rescue Exception => e
|
75
|
+
Appygram.logger.error(hash)
|
76
|
+
Appygram.logger.error(e.message)
|
77
|
+
Appygram.logger.error(e.backtrace)
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
|
81
|
+
def extract_http_headers(env)
|
82
|
+
headers = {}
|
83
|
+
env.select{|k, v| k =~ /^HTTP_/}.each do |name, value|
|
84
|
+
proper_name = name.sub(/^HTTP_/, '').split('_').map{|upper_case| upper_case.capitalize}.join('-')
|
85
|
+
headers[proper_name] = value
|
86
|
+
end
|
87
|
+
unless headers['Cookie'].nil?
|
88
|
+
headers['Cookie'] = headers['Cookie'].sub(/_session=\S+/, '_session=[FILTERED]')
|
89
|
+
end
|
90
|
+
headers
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.sanitize_session(request)
|
94
|
+
session_hash = {'session_id' => "", 'data' => {}}
|
95
|
+
|
96
|
+
if request.respond_to?(:session)
|
97
|
+
session = request.session
|
98
|
+
session_hash['session_id'] = request.session_options ? request.session_options[:id] : nil
|
99
|
+
session_hash['session_id'] ||= session.respond_to?(:session_id) ? session.session_id : session.instance_variable_get("@session_id")
|
100
|
+
session_hash['data'] = session.respond_to?(:to_hash) ? session.to_hash : session.instance_variable_get("@data") || {}
|
101
|
+
session_hash['session_id'] ||= session_hash['data'][:session_id]
|
102
|
+
session_hash['data'].delete(:session_id)
|
103
|
+
end
|
104
|
+
|
105
|
+
self.sanitize_hash(session_hash)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Appygram
|
2
|
+
module DebugExceptions
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:alias_method_chain,:render_exception,:appygram)
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_exception_with_appygram(env,exception)
|
9
|
+
::Appygram::Catcher.handle_with_controller(exception,
|
10
|
+
env['action_controller.instance'],
|
11
|
+
Rack::Request.new(env))
|
12
|
+
render_exception_without_appygram(env,exception)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class Appygram
|
6
|
+
|
7
|
+
def initialize(app, api_key = nil)
|
8
|
+
@app = app
|
9
|
+
if api_key.nil?
|
10
|
+
appygram_config = "config/appygram.yml"
|
11
|
+
::Appygram::Config.load(appygram_config)
|
12
|
+
else
|
13
|
+
::Appygram.configure(api_key)
|
14
|
+
::Appygram::Config.enabled = true
|
15
|
+
::Appygram.logger.info "Enabling Appygram for Rack"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
begin
|
21
|
+
status, headers, body = @app.call(env)
|
22
|
+
rescue Exception => e
|
23
|
+
::Appygram::Catcher.handle_with_rack(e,env, Rack::Request.new(env))
|
24
|
+
raise(e)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class RailsAppygram
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
begin
|
13
|
+
body = @app.call(env)
|
14
|
+
rescue Exception => e
|
15
|
+
::Appygram::Catcher.handle_with_controller(e,env['action_controller.instance'], Rack::Request.new(env))
|
16
|
+
raise
|
17
|
+
end
|
18
|
+
|
19
|
+
if env['rack.exception']
|
20
|
+
::Appygram::Catcher.handle_with_controller(env['rack.exception'],env['action_controller.instance'], Rack::Request.new(env))
|
21
|
+
end
|
22
|
+
|
23
|
+
body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Appygram
|
4
|
+
class LogFactory
|
5
|
+
def self.logger
|
6
|
+
@logger ||= create_logger_with_fallback
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def self.create_logger_with_fallback
|
11
|
+
begin
|
12
|
+
log_dir = File.join(Config.application_root, 'log')
|
13
|
+
Dir.mkdir(log_dir) unless File.directory?(log_dir)
|
14
|
+
log_path = File.join(log_dir, "/appygram.log")
|
15
|
+
log = Logger.new(log_path)
|
16
|
+
log.level = Logger::INFO
|
17
|
+
def log.format_message(severity, timestamp, progname, msg)
|
18
|
+
"[#{severity.upcase}] (#{[Kernel.caller[2].split('/').last]}) #{timestamp.utc.to_s} - #{msg2str(msg).gsub(/\n/, '').lstrip}\n"
|
19
|
+
end
|
20
|
+
def log.msg2str(msg)
|
21
|
+
case msg
|
22
|
+
when ::String
|
23
|
+
msg
|
24
|
+
when ::Exception
|
25
|
+
"#{ msg.message } (#{ msg.class }): " <<
|
26
|
+
(msg.backtrace || []).join(" | ")
|
27
|
+
else
|
28
|
+
msg.inspect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
log
|
32
|
+
rescue
|
33
|
+
return Rails.logger if defined?(Rails) && defined?(Rails.logger)
|
34
|
+
return RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)
|
35
|
+
return Logger.new(STDERR)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Appygram
|
4
|
+
class RackExceptionData < ExceptionData
|
5
|
+
def initialize(exception, environment, request)
|
6
|
+
super(exception)
|
7
|
+
@environment = environment
|
8
|
+
@request = request
|
9
|
+
end
|
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' => self.class.sanitize_session(@request)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'appygram'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Appygram
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
|
7
|
+
initializer "appygram.middleware" do |app|
|
8
|
+
|
9
|
+
if Appygram::Config.should_send_to_api?
|
10
|
+
Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
|
11
|
+
if defined?(ActionDispatch::DebugExceptions)
|
12
|
+
ActionDispatch::DebugExceptions.send(:include,Appygram::DebugExceptions)
|
13
|
+
else
|
14
|
+
app.config.middleware.use "Rack::RailsAppygram"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
module Appygram
|
8
|
+
class Remote
|
9
|
+
class << self
|
10
|
+
def startup_announce(startup_data)
|
11
|
+
#FIXME this is a noop based on an Exceptional behavior Appygram doesn't have
|
12
|
+
Appygram.logger.info 'Appygram remote exception forwarding started'
|
13
|
+
end
|
14
|
+
|
15
|
+
def error(exception_data)
|
16
|
+
url = "/api/exceptions?api_key=#{::Appygram::Config.api_key}"
|
17
|
+
call_remote(url, exception_data.to_json)
|
18
|
+
end
|
19
|
+
|
20
|
+
def call_remote(url, data)
|
21
|
+
config = Appygram::Config
|
22
|
+
optional_proxy = Net::HTTP::Proxy(config.http_proxy_host,
|
23
|
+
config.http_proxy_port,
|
24
|
+
config.http_proxy_username,
|
25
|
+
config.http_proxy_password)
|
26
|
+
client = optional_proxy.new(config.remote_host, config.remote_port)
|
27
|
+
client.open_timeout = config.http_open_timeout
|
28
|
+
client.read_timeout = config.http_read_timeout
|
29
|
+
client.use_ssl = config.ssl?
|
30
|
+
client.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.ssl?
|
31
|
+
begin
|
32
|
+
response = client.post(url, data)
|
33
|
+
case response
|
34
|
+
when Net::HTTPSuccess
|
35
|
+
Appygram.logger.info( "#{url} - #{response.message}")
|
36
|
+
return true
|
37
|
+
else
|
38
|
+
Appygram.logger.error("#{url} - #{response.code} - #{response.message}")
|
39
|
+
end
|
40
|
+
rescue Exception => e
|
41
|
+
Appygram.logger.error('Problem notifying Appygram about the error')
|
42
|
+
Appygram.logger.error(e)
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Appygram
|
2
|
+
class StartupException < StandardError;
|
3
|
+
end
|
4
|
+
class Startup
|
5
|
+
class << self
|
6
|
+
def announce
|
7
|
+
if Config.api_key.blank?
|
8
|
+
raise StartupException, 'API Key must be configured'
|
9
|
+
end
|
10
|
+
Remote.startup_announce(::Appygram::ApplicationEnvironment.to_hash('rails'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/appygram.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'appygram/monkeypatches'
|
4
|
+
require 'appygram/catcher'
|
5
|
+
require 'appygram/startup'
|
6
|
+
require 'appygram/log_factory'
|
7
|
+
require 'appygram/config'
|
8
|
+
require 'appygram/application_environment'
|
9
|
+
require 'appygram/exception_data'
|
10
|
+
require 'appygram/controller_exception_data'
|
11
|
+
require 'appygram/rack_exception_data'
|
12
|
+
require 'appygram/alert_data'
|
13
|
+
require 'appygram/remote'
|
14
|
+
require 'appygram/integration/rack'
|
15
|
+
require 'appygram/integration/rack_rails'
|
16
|
+
require 'appygram/integration/alerter'
|
17
|
+
require 'appygram/version'
|
18
|
+
require 'appygram/integration/debug_exceptions'
|
19
|
+
|
20
|
+
require 'appygram/railtie' if defined?(Rails::Railtie)
|
21
|
+
|
22
|
+
module Appygram
|
23
|
+
PROTOCOL_VERSION = 5
|
24
|
+
CLIENT_NAME = 'appygram-rails'
|
25
|
+
ENVIRONMENT_FILTER = []
|
26
|
+
|
27
|
+
def self.logger
|
28
|
+
::Appygram::LogFactory.logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configure(api_key)
|
32
|
+
Appygram::Config.api_key = api_key
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.handle(exception, name=nil)
|
36
|
+
Appygram::Catcher.handle(exception, name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.rescue(name=nil, context=nil, &block)
|
40
|
+
begin
|
41
|
+
self.context(context) unless context.nil?
|
42
|
+
block.call
|
43
|
+
rescue Exception => e
|
44
|
+
Appygram::Catcher.handle(e,name)
|
45
|
+
ensure
|
46
|
+
self.clear!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.rescue_and_reraise(name=nil, context=nil, &block)
|
51
|
+
begin
|
52
|
+
self.context(context) unless context.nil?
|
53
|
+
block.call
|
54
|
+
rescue Exception => e
|
55
|
+
Appygram::Catcher.handle(e,name)
|
56
|
+
raise(e)
|
57
|
+
ensure
|
58
|
+
self.clear!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.clear!
|
63
|
+
Thread.current[:appygram_context] = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.context(hash = {})
|
67
|
+
Thread.current[:appygram_context] ||= {}
|
68
|
+
Thread.current[:appygram_context].merge!(hash)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__) , '../init.rb')
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appygram-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- rfc2616
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: &70216562368600 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70216562368600
|
25
|
+
description: Appygram is the Ruby gem for communicating with http://appygram.com (hosted
|
26
|
+
messaging service). It supports an exception reporting mechanism similar to http://exceptional.io,
|
27
|
+
and this Ruby gem, forked from the Exceptional gem, emulates Exceptional functionality.
|
28
|
+
email: heittman.rob@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/appygram/alert_data.rb
|
34
|
+
- lib/appygram/application_environment.rb
|
35
|
+
- lib/appygram/catcher.rb
|
36
|
+
- lib/appygram/config.rb
|
37
|
+
- lib/appygram/controller_exception_data.rb
|
38
|
+
- lib/appygram/exception_data.rb
|
39
|
+
- lib/appygram/integration/alerter.rb
|
40
|
+
- lib/appygram/integration/debug_exceptions.rb
|
41
|
+
- lib/appygram/integration/rack.rb
|
42
|
+
- lib/appygram/integration/rack_rails.rb
|
43
|
+
- lib/appygram/log_factory.rb
|
44
|
+
- lib/appygram/monkeypatches.rb
|
45
|
+
- lib/appygram/rack_exception_data.rb
|
46
|
+
- lib/appygram/railtie.rb
|
47
|
+
- lib/appygram/remote.rb
|
48
|
+
- lib/appygram/startup.rb
|
49
|
+
- lib/appygram/version.rb
|
50
|
+
- lib/appygram.rb
|
51
|
+
- rails/init.rb
|
52
|
+
- init.rb
|
53
|
+
- appygram.gemspec
|
54
|
+
homepage: http://appygram.com/
|
55
|
+
licenses: []
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements:
|
73
|
+
- json_pure, json-jruby or json gem required
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.17
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: appygram is a hosted service for sending messages from mobile/web apps
|
79
|
+
test_files: []
|
80
|
+
has_rdoc:
|