appygram 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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}
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,63 @@
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
+ 'environment' => environment,
14
+ 'env' => extract_environment(ENV),
15
+ 'host' => get_hostname,
16
+ 'run_as_user' => get_username,
17
+ 'application_root_directory' => (application_root.to_s.respond_to?(:force_encoding) ? application_root.to_s.force_encoding("UTF-8") : application_root),
18
+ 'language' => 'ruby',
19
+ 'language_version' => language_version_string,
20
+ 'framework' => framework,
21
+ 'libraries_loaded' => libraries_loaded
22
+ }
23
+ }
24
+ end
25
+
26
+ def self.environment
27
+ Config.application_environment
28
+ end
29
+
30
+ def self.application_root
31
+ Config.application_root
32
+ end
33
+
34
+ def self.extract_environment(env)
35
+ env.reject do |k, v|
36
+ (k =~ /^HTTP_/) || Appygram::ENVIRONMENT_FILTER.include?(k)
37
+ end
38
+ end
39
+
40
+ def self.get_hostname
41
+ require 'socket' unless defined?(Socket)
42
+ Socket.gethostname
43
+ rescue
44
+ 'UNKNOWN'
45
+ end
46
+
47
+ def self.language_version_string
48
+ "#{RUBY_VERSION rescue '?.?.?'} p#{RUBY_PATCHLEVEL rescue '???'} #{RUBY_RELEASE_DATE rescue '????-??-??'} #{RUBY_PLATFORM rescue '????'}"
49
+ end
50
+
51
+ def self.get_username
52
+ ENV['LOGNAME'] || ENV['USER'] || ENV['USERNAME'] || ENV['APACHE_RUN_USER'] || 'UNKNOWN'
53
+ end
54
+
55
+ def self.libraries_loaded
56
+ begin
57
+ return Hash[*Gem.loaded_specs.map{|name, gem_specification| [name, gem_specification.version.to_s]}.flatten]
58
+ rescue
59
+ end
60
+ {}
61
+ end
62
+ end
63
+ 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,87 @@
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 load(config_file=nil)
21
+ if (config_file && File.file?(config_file))
22
+ begin
23
+ config = YAML::load_file(config_file)
24
+ env_config = config[application_environment] || {}
25
+ @api_key = config['api-key'] || env_config['api-key']
26
+
27
+ @http_proxy_host = config['http-proxy-host']
28
+ @http_proxy_port = config['http-proxy-port']
29
+ @http_proxy_username = config['http-proxy-username']
30
+ @http_proxy_password = config['http-proxy-password']
31
+ @http_open_timeout = config['http-open-timeout']
32
+ @http_read_timeout = config['http-read-timeout']
33
+
34
+ @ssl = config['ssl'] || env_config['ssl']
35
+ @enabled = env_config['enabled']
36
+ @remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
37
+ @remote_host = config['remote-host'] unless config['remote-host'].nil?
38
+ rescue Exception => e
39
+ raise ConfigurationException.new("Unable to load configuration #{config_file} for environment #{application_environment} : #{e.message}")
40
+ end
41
+ end
42
+ end
43
+
44
+ def api_key
45
+ return @api_key unless @api_key.nil?
46
+ @api_key ||= ENV['EXCEPTIONAL_API_KEY'] unless ENV['EXCEPTIONAL_API_KEY'].nil?
47
+ end
48
+
49
+ def application_environment
50
+ ENV['RACK_ENV'] || ENV['RAILS_ENV']|| 'development'
51
+ end
52
+
53
+ def should_send_to_api?
54
+ return @enabled unless @enabled.nil?
55
+ @enabled = !(DEFAULTS[:disabled_by_default].include?(application_environment))
56
+ end
57
+
58
+ def application_root
59
+ (defined?(Rails) && Rails.respond_to?(:root)) ? Rails.root : Dir.pwd
60
+ end
61
+
62
+ def ssl?
63
+ @ssl ||= DEFAULTS[:ssl]
64
+ end
65
+
66
+ def remote_host
67
+ @remote_host ||= DEFAULTS[:remote_host_http]
68
+ end
69
+
70
+ def remote_port
71
+ @remote_port ||= ssl? ? 443 : 80
72
+ end
73
+
74
+ def reset
75
+ @enabled = @ssl = @remote_host = @remote_port = @api_key = nil
76
+ end
77
+
78
+ def http_open_timeout
79
+ @http_open_timeout ||= DEFAULTS[:http_open_timeout]
80
+ end
81
+
82
+ def http_read_timeout
83
+ @http_read_timeout ||= DEFAULTS[:http_read_timeout]
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,65 @@
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
+ {
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
+ end
30
+
31
+ def filter_hash(keys_to_filter, hash)
32
+ keys_to_filter.map! {|x| x.to_s}
33
+ if keys_to_filter.is_a?(Array) && !keys_to_filter.empty?
34
+ hash.each do |key, value|
35
+ if key_match?(key, keys_to_filter)
36
+ hash[key] = "[FILTERED]"
37
+ elsif value.respond_to?(:to_hash)
38
+ filter_hash(keys_to_filter, hash[key])
39
+ end
40
+ end
41
+ end
42
+ hash
43
+ end
44
+
45
+ # Closer alignment to latest filtered_params:
46
+ # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb
47
+ # https://github.com/exceptional/exceptional/issues/20
48
+ def key_match?(key, keys_to_filter)
49
+ keys_to_filter.any? { |k|
50
+ regexp = k.is_a?(Regexp)? k : Regexp.new(k, true)
51
+ key =~ regexp
52
+ }
53
+ end
54
+
55
+ def filter_parameters(hash)
56
+ if @request.respond_to?(:env) && @request.env["action_dispatch.parameter_filter"]
57
+ filter_hash(@request.env["action_dispatch.parameter_filter"], hash)
58
+ elsif @controller.respond_to?(:filter_parameters)
59
+ @controller.send(:filter_parameters, hash)
60
+ else
61
+ hash
62
+ end
63
+ end
64
+ end
65
+ 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,11 @@
1
+ module Appygram
2
+ class Alert <StandardError;
3
+ end
4
+
5
+ module Integration
6
+ def self.alert(msg, env={})
7
+ return Appygram::Remote.error(Appygram::AlertData.new(Alert.new(msg), "Alert"))
8
+ end
9
+ end
10
+ end
11
+
@@ -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,15 @@
1
+ if Delayed::Worker.method_defined? :handle_failed_job
2
+ class Delayed::Worker
3
+ def handle_failed_job_with_appygram(job, e)
4
+ Appygram.handle(e, "Delayed::Job #{job.name}")
5
+ handle_failed_job_without_appygram(job, e)
6
+ Appygram.context.clear!
7
+ end
8
+ alias_method_chain :handle_failed_job, :appygram
9
+ Appygram.logger.info "DJ integration enabled"
10
+ end
11
+ else
12
+ message = "\n\n\nThe Appygram gem does not support Delayed Job 1.8.4 or earlier.\n\n\n"
13
+ STDERR.puts(message)
14
+ Appygram.logger.error(message)
15
+ 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,27 @@
1
+ # force Rails < 2.0 to use quote keys as per the JSON standard...
2
+ if defined?(ActiveSupport) && defined?(ActiveSupport::JSON) && ActiveSupport::JSON.respond_to?(:unquote_hash_key_identifiers)
3
+ ActiveSupport::JSON.unquote_hash_key_identifiers = false
4
+ end
5
+
6
+ if defined? ActionController
7
+ module ActionController
8
+ class Base
9
+ def rescue_action_with_appygram(exception)
10
+ unless exception_handled_by_rescue_from?(exception)
11
+ Appygram::Catcher.handle_with_controller(exception, self, request)
12
+ Appygram.context.clear!
13
+ end
14
+ rescue_action_without_appygram exception
15
+ end
16
+
17
+ alias_method :rescue_action_without_appygram, :rescue_action
18
+ alias_method :rescue_action, :rescue_action_with_appygram
19
+ protected :rescue_action
20
+
21
+ private
22
+ def exception_handled_by_rescue_from?(exception)
23
+ respond_to?(:handler_for_rescue) && handler_for_rescue(exception)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ if defined? Sinatra::Request
2
+ error do
3
+ Appygram::Catcher.handle_with_rack(request.env['sinatra.error'], request.env, request)
4
+ raise request.env['sinatra.error']
5
+ end
6
+ 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,10 @@
1
+ class Regexp
2
+ def to_json(options = {})
3
+ "\"#{self.to_s}\""
4
+ end
5
+ end
6
+ class Fixnum
7
+ def to_json(options = {})
8
+ to_s
9
+ end
10
+ 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,23 @@
1
+ require 'appygram'
2
+ require 'rails'
3
+
4
+ module Appygram
5
+ class Railtie < Rails::Railtie
6
+
7
+ initializer "appygram.middleware" do |app|
8
+
9
+ config_file = File.join(Rails.root, "/config/appygram.yml")
10
+ Appygram::Config.load config_file if File.exist?(config_file)
11
+ # On Heroku config is loaded via the ENV so no need to load it from the file
12
+
13
+ if Appygram::Config.should_send_to_api?
14
+ Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
15
+ if defined?(ActionDispatch::DebugExceptions)
16
+ ActionDispatch::DebugExceptions.send(:include,Appygram::DebugExceptions)
17
+ else
18
+ app.config.middleware.use "Rack::RailsAppygram"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
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
+ url = "/api/announcements?api_key=#{::Appygram::Config.api_key}&protocol_version=#{::Appygram::PROTOCOL_VERSION}"
12
+ compressed = Zlib::Deflate.deflate(startup_data.to_json, Zlib::BEST_SPEED)
13
+ call_remote(url, compressed)
14
+ end
15
+
16
+ def error(exception_data)
17
+ uniqueness_hash = exception_data.uniqueness_hash
18
+ hash_param = uniqueness_hash.nil? ? nil: "&hash=#{uniqueness_hash}"
19
+ url = "/api/errors?api_key=#{::Appygram::Config.api_key}&protocol_version=#{::Appygram::PROTOCOL_VERSION}#{hash_param}"
20
+ compressed = Zlib::Deflate.deflate(exception_data.to_json, Zlib::BEST_SPEED)
21
+ call_remote(url, compressed)
22
+ end
23
+
24
+ def call_remote(url, data)
25
+ config = Appygram::Config
26
+ optional_proxy = Net::HTTP::Proxy(config.http_proxy_host,
27
+ config.http_proxy_port,
28
+ config.http_proxy_username,
29
+ config.http_proxy_password)
30
+ client = optional_proxy.new(config.remote_host, config.remote_port)
31
+ client.open_timeout = config.http_open_timeout
32
+ client.read_timeout = config.http_read_timeout
33
+ client.use_ssl = config.ssl?
34
+ client.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.ssl?
35
+ begin
36
+ response = client.post(url, data)
37
+ case response
38
+ when Net::HTTPSuccess
39
+ Appygram.logger.info( "#{url} - #{response.message}")
40
+ return true
41
+ else
42
+ Appygram.logger.error("#{url} - #{response.code} - #{response.message}")
43
+ end
44
+ rescue Exception => e
45
+ Appygram.logger.error('Problem notifying Appygram about the error')
46
+ Appygram.logger.error(e)
47
+ end
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ 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 (/config/appygram.yml)'
9
+ end
10
+ Remote.startup_announce(::Appygram::ApplicationEnvironment.to_hash('rails'))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Appygram
2
+ VERSION = '0.1.0'
3
+ 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 = 'getappygram-gem'
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,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: appygram
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - rfc2616
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &70157985912000 !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: *70157985912000
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/dj.rb
42
+ - lib/appygram/integration/rack.rb
43
+ - lib/appygram/integration/rack_rails.rb
44
+ - lib/appygram/integration/rails.rb
45
+ - lib/appygram/integration/sinatra.rb
46
+ - lib/appygram/log_factory.rb
47
+ - lib/appygram/monkeypatches.rb
48
+ - lib/appygram/rack_exception_data.rb
49
+ - lib/appygram/railtie.rb
50
+ - lib/appygram/remote.rb
51
+ - lib/appygram/startup.rb
52
+ - lib/appygram/version.rb
53
+ - lib/appygram.rb
54
+ - rails/init.rb
55
+ - init.rb
56
+ - appygram.gemspec
57
+ homepage: http://appygram.com/
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements:
76
+ - json_pure, json-jruby or json gem required
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.17
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: appygram is a hosted service for sending messages from mobile/web apps
82
+ test_files: []